2 * Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
91 char src[IRC_LINE]; // (:([^ ]+) +)?
92 char cmd[IRC_LINE]; // (([A-Z0-9]+) +)
93 char dst[IRC_LINE]; // (([^ ]+)[= ]+)?
94 char arg[IRC_LINE]; // (([^: ]+) *)?
95 char msg[IRC_LINE]; // (:(.*))?
100 static void srv_notice(irc_server_t *srv, const char *fmt, ...)
102 static char buf[1024];
106 vsnprintf(buf, sizeof(buf), fmt, ap);
109 chat_recv(&srv->system.channel, NULL, buf);
112 static void chan_notice(irc_channel_t *chan, const char *fmt, ...)
114 static char buf[1024];
118 vsnprintf(buf, sizeof(buf), fmt, ap);
121 chat_recv(&chan->channel, NULL, buf);
124 static irc_channel_t *find_dest(irc_server_t *srv, const char *dest, int create)
128 /* Find existing channels */
129 for (channel_t *cur = channels; cur; cur = cur->next) {
130 if (cur->server != &srv->server)
132 chan = (irc_channel_t *)cur;
133 if (match(chan->dest, dest))
137 /* Create a new channel */
138 chan = new0(irc_channel_t);
139 chan->channel.server = &srv->server;
140 chan->channel.name = strcopy(dest);
141 chan->dest = strcopy(dest);
142 add_channel(&chan->channel);
146 static irc_user_t *find_nick(irc_server_t *srv, const char *from, int create)
150 /* Find existing channels */
151 for (user_t *cur = users; cur; cur = cur->next) {
152 if (cur->server != &srv->server)
154 usr = (irc_user_t *)cur;
155 if (match(usr->nick, from))
159 /* Create a new channel */
160 usr = new0(irc_user_t);
161 usr->user.server = &srv->server;
162 usr->user.name = strcopy(from);
163 usr->nick = strcopy(from);
164 add_user(&usr->user);
168 static void join_channel(irc_server_t *srv, irc_channel_t *chan)
170 chan_notice(chan, "Joining Channel: %s", chan->channel.name);
171 net_print(&srv->net, "JOIN %s\n", chan->dest);
172 net_print(&srv->net, "TOPIC %s\n", chan->dest);
173 net_print(&srv->net, "WHO %s\n", chan->dest);
176 static void part_channel(irc_server_t *srv, irc_channel_t *chan)
178 chan_notice(chan, "Leaving Channel: %s", chan->channel.name);
179 net_print(&srv->net, "PART %s\n", chan->dest);
182 static void parse_line(irc_server_t *srv, const char *line,
186 const char *c = line;
187 char *src = _cmd->src;
188 char *cmd = _cmd->cmd;
189 char *dst = _cmd->dst;
190 char *arg = _cmd->arg;
191 char *msg = _cmd->msg;
192 char *from = _cmd->from;
195 src[0] = cmd[0] = dst[0] = arg[0] = msg[0] = from[0] = '\0';
200 for (i = 0; *c && *c != ' '; i++)
205 for (i = 0; src[i] && src[i] != '!' && src[i] != ' '; i++)
211 for (i = 0; isalnum(*c); i++)
218 if ((*c && *c != ' ') &&
219 (strchr(c+1, ' ') || strchr(c+1, '='))) {
220 for (i = 0; *c && *c != ' '; i++)
222 while (*c == '=' || *c == ' ')
228 if (*c && *c != ':' && *c != ' ') {
229 for (i = 0; *c && *c != ':'; i++)
235 while (i >= 0 && arg[i] == ' ');
246 debug("got line: [%s]", line);
247 //debug(" src %s", src);
248 //debug(" cmd %s", cmd);
249 //debug(" dst %s", dst);
250 //debug(" arg %s", arg);
251 //debug(" msg %s", msg);
254 /* Callback functions */
255 static void irc_run(irc_server_t *srv, const char *line);
257 static void on_send(void *_srv)
259 irc_server_t *srv = _srv;
263 static void on_recv(void *_srv, char *buf, int len)
265 irc_server_t *srv = _srv;
267 for (int i = 0; i < len; i++) {
268 if (srv->pos < IRC_LINE) {
269 srv->line[srv->pos] = buf[i];
272 if (buf[i] == '\n' || buf[i] == '\r') {
273 srv->line[srv->pos-1] = '\0';
275 irc_run(srv, srv->line);
283 static void on_err(void *_srv, int errno)
285 irc_server_t *srv = _srv;
289 /* IRC State machine */
290 static void irc_run(irc_server_t *srv, const char *line)
292 static irc_command_t _cmd;
293 const char *cmd = _cmd.cmd;
294 const char *dst = _cmd.dst;
295 const char *arg = _cmd.arg;
296 const char *msg = _cmd.msg;
297 const char *from = _cmd.from;
300 parse_line(srv, line, &_cmd);
302 /* Connection Handling */
303 if (srv->state == IRC_CONNECT) {
304 srv->net.send = on_send;
305 srv->net.recv = on_recv;
306 srv->net.err = on_err;
309 srv_notice(srv, "Joining Server: %s", srv->server.name);
310 net_open(&srv->net, srv->host, srv->port);
313 srv->state = IRC_ENCRYPT;
315 srv->state = IRC_SEND_TLS;
319 if (srv->state == IRC_SEND_TLS) {
320 if (net_print(&srv->net, "CAP REQ :tls\n"))
321 srv->state = IRC_RECV_TLS;
323 if (srv->state == IRC_RECV_TLS) {
324 if (match(cmd, "CAP") && match(arg, "ACK")) {
325 srv_notice(srv, "Start TLS proceeding");
326 srv->state = IRC_SEND_STARTTLS;
328 if (match(cmd, "CAP") && match(arg, "NAK")) {
329 srv_notice(srv, "Start TLS unsupported");
330 srv->state = IRC_SEND_END;
333 if (srv->state == IRC_SEND_STARTTLS) {
334 if (net_print(&srv->net, "STARTTLS\n"))
335 srv->state = IRC_RECV_STARTTLS;
337 if (srv->state == IRC_RECV_STARTTLS) {
338 if (prefix(msg, "STARTTLS successful", NULL))
339 srv->state = IRC_ENCRYPT;
343 if (srv->state == IRC_ENCRYPT) {
344 net_encrypt(&srv->net);
345 srv->state = IRC_SEND_SASL;
348 /* SASL authentication */
349 if (srv->state == IRC_SEND_SASL) {
350 if (net_print(&srv->net, "CAP REQ :sasl\n"))
351 srv->state = IRC_RECV_SASL;
353 if (srv->state == IRC_RECV_SASL) {
354 if (match(cmd, "CAP") && match(arg, "ACK")) {
355 srv_notice(srv, "SASL auth proceeding");
356 srv->state = IRC_SEND_PLAIN;
358 if (match(cmd, "CAP") && match(arg, "NAK")) {
359 srv_notice(srv, "SASL auth unsupported");
360 srv->state = IRC_SEND_END;
363 if (srv->state == IRC_SEND_PLAIN) {
364 if (net_print(&srv->net, "AUTHENTICATE PLAIN\n"))
365 srv->state = IRC_SEND_AUTH;
367 if (srv->state == IRC_SEND_AUTH) {
368 static char plain[IRC_LINE];
369 static char coded[IRC_LINE];
371 len = snprintf(plain, IRC_LINE, "%s%c%s%c%s",
372 srv->auth, '\0', srv->auth, '\0', srv->pass);
373 len = base64(plain, len, coded, IRC_LINE);
374 if (net_print(&srv->net, "AUTHENTICATE %.*s\n", len, coded))
375 srv->state = IRC_RECV_STATUS;
377 if (srv->state == IRC_RECV_STATUS) {
378 if (match(cmd, "903")) {
379 srv_notice(srv, "SASL auth succeeded");
380 srv->state = IRC_SEND_END;
382 if (match(cmd, "904") ||
386 srv_notice(srv, "SASL auth failed");
387 srv->state = IRC_DEAD;
390 if (srv->state == IRC_SEND_END) {
391 if (net_print(&srv->net, "CAP END\n"))
392 srv->state = IRC_SEND_USER;
395 /* Connection setup */
396 if (srv->state == IRC_SEND_USER) {
397 if (net_print(&srv->net, "USER %s %s %s :%s\n",
398 getenv("USER") ?: "lameuser",
399 get_hostname(), srv->host, srv->nick))
400 srv->state = IRC_SEND_NICK;
402 if (srv->state == IRC_SEND_NICK) {
403 if (net_print(&srv->net, "NICK %s\n", srv->nick))
404 srv->state = IRC_RECV_WELCOME;
406 if (srv->state == IRC_RECV_WELCOME) {
407 if (match(cmd, "001") && strstr(msg, "Welcome"))
408 srv->state = IRC_JOIN;
410 if (srv->state == IRC_JOIN) {
411 for (channel_t *cur = channels; cur; cur = cur->next) {
412 irc_channel_t *chan = (irc_channel_t*)cur;
413 if (cur->server != &srv->server || !chan->join)
415 join_channel(srv, chan);
417 srv->state = IRC_READY;
420 /* Receive messages */
421 if (srv->state == IRC_READY) {
424 if (match(cmd, "PING")) {
425 net_print(&srv->net, "PING %s\n", msg);
427 if (match(cmd, "TOPIC")) {
428 chan = find_dest(srv, arg, 1);
429 chan_notice(chan, "Topic changed to %s", msg);
430 strset(&chan->channel.topic, msg);
432 if (match(cmd, "331") || match(cmd, "332")) {
433 chan = find_dest(srv, arg, 1);
434 chan_notice(chan, "Topic: %s", msg);
435 strset(&chan->channel.topic, msg);
437 if (match(cmd, "353") && prefix(arg, "@", &arg)) {
438 chan = find_dest(srv, arg, 1);
439 chan_notice(chan, "Members: %s", msg);
441 if (match(cmd, "PRIVMSG") && dst[0] == '#') {
442 chan = find_dest(srv, dst, 1);
443 usr = find_nick(srv, from, 1);
444 chat_recv(&chan->channel, &usr->user, msg);
446 if (match(cmd, "PRIVMSG") && dst[0] != '#') {
447 chan = find_dest(srv, from, 1);
448 usr = find_nick(srv, from, 1);
449 chat_recv(&chan->channel, &usr->user, msg);
453 /* Receive notices */
454 if (match(cmd, "NOTICE") ||
459 srv_notice(srv, "%s", msg);
465 for (server_t *cur = servers; cur; cur = cur->next) {
466 if (cur->protocol != IRC)
469 irc_server_t *srv = (irc_server_t*)cur;
470 srv->system.channel.server = &srv->server;
471 srv->system.channel.name = srv->server.name;
474 srv->port = srv->tls ? 6697 : 6667;
476 srv->nick = strcopy(getenv("USER"));
478 srv->nick = strcopy("lameuser");
482 for (channel_t *cur = channels; cur; cur = cur->next) {
483 if (cur->server->protocol != IRC)
486 irc_channel_t *chan = (irc_channel_t*)cur;
488 chan->dest = strcopy(cur->name);
492 void irc_config(server_t *server, channel_t *channel,
493 const char *group, const char *name,
494 const char *key, const char *value)
496 irc_server_t *srv = (irc_server_t*)server;
497 irc_channel_t *chan = (irc_channel_t*)channel;
499 if (match(group, "server")) {
500 if (match(key, "protocol")) {
501 irc_server_t *srv = new0(irc_server_t);
502 srv->server.protocol = IRC;
503 srv->server.name = strcopy(get_name(name));
504 add_server(&srv->server);
506 else if (match(key, "connect"))
507 srv->connect = get_bool(value);
508 else if (match(key, "host"))
509 srv->host = get_string(value);
510 else if (match(key, "port"))
511 srv->port = get_number(value);
512 else if (match(key, "tls"))
513 srv->tls = get_bool(value);
514 else if (match(key, "nick"))
515 srv->nick = get_string(value);
516 else if (match(key, "auth"))
517 srv->auth = get_string(value);
518 else if (match(key, "pass"))
519 srv->pass = get_string(value);
521 if (match(group, "channel")) {
522 if (match(key, "server")) {
523 irc_channel_t *chan = new0(irc_channel_t);
524 chan->channel.server = &srv->server;
525 chan->channel.name = strcopy(get_name(name));
526 add_channel(&chan->channel);
528 else if (match(key, "dest"))
529 chan->dest = get_string(value);
530 else if (match(key, "join"))
531 chan->join = get_bool(value);
535 void irc_send(channel_t *channel, const char *text)
537 irc_channel_t *chan = (irc_channel_t*)channel;
538 irc_server_t *srv = (irc_server_t*)channel->server;
541 if (text[0] == '/') {
542 if (prefix(text, "/join", &arg)) {
544 chan = find_dest(srv, arg, 1);
546 join_channel(srv, chan);
548 else if (prefix(text, "/part", &arg)) {
550 chan = find_dest(srv, arg, 0);
552 part_channel(srv, chan);
554 else if (prefix(text, "/query", &arg)) {
556 chan_notice(chan, "usage: /query <user>");
559 chan = find_dest(srv, arg, 0);
560 chan_notice(chan, "User: %s", arg);
563 chan_notice(chan, "Unknown command %s", text);
566 if (chan == &srv->system) {
567 chan_notice(chan, "Cannot send to server");
569 else if (!chan->dest) {
570 chan_notice(chan, "No destination for message");
573 net_print(&srv->net, "PRIVMSG %s :%s\n", chan->dest, text);
574 chat_recv(channel, &srv->myself.user, text);