]> Pileus Git - ~andy/lamechat/blob - irc.c
d2be0c322bdcf6621f7db1baa7f65fb029211b43
[~andy/lamechat] / irc.c
1 /*
2  * Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <stdarg.h>
21 #include <string.h>
22 #include <ctype.h>
23
24 #include "util.h"
25 #include "conf.h"
26 #include "chat.h"
27 #include "net.h"
28
29 /* IRC constants */
30 #define IRC_LINE 512
31
32 /* IRC types */
33 typedef enum {
34         IRC_CONNECT,
35         IRC_ENCRYPT,
36
37         IRC_SEND_TLS,
38         IRC_RECV_TLS,
39         IRC_SEND_STARTTLS,
40         IRC_RECV_STARTTLS,
41
42         IRC_SEND_SASL,
43         IRC_RECV_SASL,
44         IRC_SEND_PLAIN,
45         IRC_SEND_AUTH,
46         IRC_RECV_STATUS,
47         IRC_SEND_END,
48
49         IRC_SEND_USER,
50         IRC_SEND_NICK,
51         IRC_RECV_WELCOME,
52
53         IRC_JOIN,
54         IRC_READY,
55         IRC_DEAD,
56 } irc_state_t;
57
58 typedef struct {
59         user_t        user;
60
61         const char   *nick;
62 } irc_user_t;
63
64 typedef struct {
65         channel_t     channel;
66
67         const char   *dest;
68         int           join;
69 } irc_channel_t;
70
71 typedef struct {
72         server_t      server;
73
74         int           connect;
75         const char   *host;
76         int           port;
77         int           tls;
78         const char   *nick;
79         const char   *auth;
80         const char   *pass;
81
82         irc_user_t    myself;
83         irc_channel_t system;
84         net_t         net;
85         irc_state_t   state;
86         char          line[IRC_LINE];
87         int           pos;
88 } irc_server_t;
89
90 typedef struct {
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]; // (:(.*))?
96         char from[IRC_LINE];
97 } irc_command_t;
98
99 /* Local functions */
100 static void srv_notice(irc_server_t *srv, const char *fmt, ...)
101 {
102         static char buf[1024];
103
104         va_list ap;
105         va_start(ap, fmt);
106         vsnprintf(buf, sizeof(buf), fmt, ap);
107         va_end(ap);
108
109         chat_recv(&srv->system.channel, NULL, buf);
110 }
111
112 static void chan_notice(irc_channel_t *chan, const char *fmt, ...)
113 {
114         static char buf[1024];
115
116         va_list ap;
117         va_start(ap, fmt);
118         vsnprintf(buf, sizeof(buf), fmt, ap);
119         va_end(ap);
120
121         chat_recv(&chan->channel, NULL, buf);
122 }
123
124 static irc_channel_t *find_dest(irc_server_t *srv, const char *dest, int create)
125 {
126         irc_channel_t *chan;
127
128         /* Find existing channels */
129         for (channel_t *cur = channels; cur; cur = cur->next) {
130                 if (cur->server != &srv->server)
131                         continue;
132                 chan = (irc_channel_t *)cur;
133                 if (match(chan->dest, dest))
134                         return chan;
135         }
136
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);
143         return chan;
144 }
145
146 static irc_user_t *find_nick(irc_server_t *srv, const char *from, int create)
147 {
148         irc_user_t *usr;
149
150         /* Find existing channels */
151         for (user_t *cur = users; cur; cur = cur->next) {
152                 if (cur->server != &srv->server)
153                         continue;
154                 usr = (irc_user_t *)cur;
155                 if (match(usr->nick, from))
156                         return usr;
157         }
158
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);
165         return usr;
166 }
167
168 static void join_channel(irc_server_t *srv, irc_channel_t *chan)
169 {
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);
174 }
175
176 static void part_channel(irc_server_t *srv, irc_channel_t *chan)
177 {
178         chan_notice(chan, "Leaving Channel: %s", chan->channel.name);
179         net_print(&srv->net, "PART %s\n",  chan->dest);
180 }
181
182 static void parse_line(irc_server_t *srv, const char *line,
183                        irc_command_t *_cmd)
184 {
185         int i;
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;
193
194         /* Clear strings */
195         src[0] = cmd[0] = dst[0] = arg[0] = msg[0] = from[0] = '\0';
196
197         /* Read src */
198         if (*c == ':') {
199                 c++;
200                 for (i = 0; *c && *c != ' '; i++)
201                         src[i] = *c++;
202                 while (*c == ' ')
203                         c++;
204                 src[i] = '\0';
205                 for (i = 0; src[i] && src[i] != '!' && src[i] != ' '; i++)
206                         from[i] = src[i];
207                 from[i] = '\0';
208         }
209
210         /* Read cmd */
211         for (i = 0; isalnum(*c); i++)
212                 cmd[i] = *c++;
213         while (*c == ' ')
214                 c++;
215         cmd[i] = '\0';
216
217         /* Read dst */
218         if ((*c && *c != ' ') &&
219             (strchr(c+1, ' ') || strchr(c+1, '='))) {
220                 for (i = 0; *c && *c != ' '; i++)
221                         dst[i] = *c++;
222                 while (*c == '=' || *c == ' ')
223                         c++;
224                 dst[i] = '\0';
225         }
226
227         /* Read arg */
228         if  (*c && *c != ':' && *c != ' ') {
229                 for (i = 0; *c && *c != ':'; i++)
230                         arg[i] = *c++;
231                 while (*c == ' ')
232                         c++;
233                 do
234                         arg[i--] = '\0';
235                 while (i >= 0 && arg[i] == ' ');
236         }
237
238         /* Read msg */
239         if (*c == ':') {
240                 c++;
241                 for (i = 0; *c; i++)
242                         msg[i] = *c++;
243                 msg[i] = '\0';
244         }
245
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);
252 }
253
254 /* Callback functions */
255 static void irc_run(irc_server_t *srv, const char *line);
256
257 static void on_send(void *_srv)
258 {
259         irc_server_t *srv = _srv;
260         irc_run(srv, "");
261 }
262
263 static void on_recv(void *_srv, char *buf, int len)
264 {
265         irc_server_t *srv = _srv;
266
267         for (int i = 0; i < len; i++) {
268                 if (srv->pos < IRC_LINE) {
269                         srv->line[srv->pos] = buf[i];
270                         srv->pos++;
271                 }
272                 if (buf[i] == '\n' || buf[i] == '\r') {
273                         srv->line[srv->pos-1] = '\0';
274                         if (srv->pos > 1)
275                                 irc_run(srv, srv->line);
276                         srv->pos = 0;
277                 }
278         }
279
280         irc_run(srv, "");
281 }
282
283 static void on_err(void *_srv, int errno)
284 {
285         irc_server_t *srv = _srv;
286         irc_run(srv, "");
287 }
288
289 /* IRC State machine */
290 static void irc_run(irc_server_t *srv, const char *line)
291 {
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;
298
299         /* Parse line */
300         parse_line(srv, line, &_cmd);
301
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;
307                 srv->net.data = srv;
308
309                 srv_notice(srv, "Joining Server: %s", srv->server.name);
310                 net_open(&srv->net, srv->host, srv->port);
311
312                 if (srv->tls)
313                         srv->state = IRC_ENCRYPT;
314                 else
315                         srv->state = IRC_SEND_TLS;
316         }
317
318         /* Start TLS */
319         if (srv->state == IRC_SEND_TLS) {
320                 if (net_print(&srv->net, "CAP REQ :tls\n"))
321                         srv->state = IRC_RECV_TLS;
322         }
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;
327                 }
328                 if (match(cmd, "CAP") && match(arg, "NAK")) {
329                         srv_notice(srv, "Start TLS unsupported");
330                         srv->state = IRC_SEND_END;
331                 }
332         }
333         if (srv->state == IRC_SEND_STARTTLS) {
334                 if (net_print(&srv->net, "STARTTLS\n"))
335                         srv->state = IRC_RECV_STARTTLS;
336         }
337         if (srv->state == IRC_RECV_STARTTLS) {
338                 if (prefix(msg, "STARTTLS successful", NULL))
339                         srv->state = IRC_ENCRYPT;
340         }
341
342         /* Encryption */
343         if (srv->state == IRC_ENCRYPT) {
344                 net_encrypt(&srv->net);
345                 srv->state = IRC_SEND_SASL;
346         }
347
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;
352         }
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;
357                 }
358                 if (match(cmd, "CAP") && match(arg, "NAK")) {
359                         srv_notice(srv, "SASL auth unsupported");
360                         srv->state = IRC_SEND_END;
361                 }
362         }
363         if (srv->state == IRC_SEND_PLAIN) {
364                 if (net_print(&srv->net, "AUTHENTICATE PLAIN\n"))
365                         srv->state = IRC_SEND_AUTH;
366         }
367         if (srv->state == IRC_SEND_AUTH) {
368                 static char plain[IRC_LINE];
369                 static char coded[IRC_LINE];
370                 int len;
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;
376         }
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;
381                 }
382                 if (match(cmd, "904") ||
383                     match(cmd, "905") ||
384                     match(cmd, "906") ||
385                     match(cmd, "907")) {
386                         srv_notice(srv, "SASL auth failed");
387                         srv->state = IRC_DEAD;
388                 }
389         }
390         if (srv->state == IRC_SEND_END) {
391                 if (net_print(&srv->net, "CAP END\n"))
392                         srv->state = IRC_SEND_USER;
393         }
394
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;
401         }
402         if (srv->state == IRC_SEND_NICK) {
403                 if (net_print(&srv->net, "NICK %s\n", srv->nick))
404                         srv->state = IRC_RECV_WELCOME;
405         }
406         if (srv->state == IRC_RECV_WELCOME) {
407                 if (match(cmd, "001") && strstr(msg, "Welcome"))
408                         srv->state = IRC_JOIN;
409         }
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)
414                                 continue;
415                         join_channel(srv, chan);
416                 }
417                 srv->state = IRC_READY;
418         }
419
420         /* Receive messages */
421         if (srv->state == IRC_READY) {
422                 irc_channel_t *chan;
423                 irc_user_t    *usr;
424                 if (match(cmd, "PING")) {
425                         net_print(&srv->net, "PING %s\n", msg);
426                 }
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);
431                 }
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);
436                 }
437                 if (match(cmd, "353") && prefix(arg, "@", &arg)) {
438                         chan = find_dest(srv, arg, 1);
439                         chan_notice(chan, "Members: %s", msg);
440                 }
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);
445                 }
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);
450                 }
451         }
452
453         /* Receive notices */
454         if (match(cmd, "NOTICE") ||
455             match(cmd, "001")    ||
456             match(cmd, "372")    ||
457             match(cmd, "375")    ||
458             match(cmd, "376"))
459                 srv_notice(srv, "%s", msg);
460 }
461
462 /* IRC functions */
463 void irc_init(void)
464 {
465         for (server_t *cur = servers; cur; cur = cur->next) {
466                 if (cur->protocol != IRC)
467                         continue;
468
469                 irc_server_t *srv = (irc_server_t*)cur;
470                 srv->system.channel.server = &srv->server;
471                 srv->system.channel.name   = srv->server.name;
472
473                 if (!srv->port)
474                         srv->port = srv->tls ? 6697 : 6667;
475                 if (!srv->nick)
476                         srv->nick = strcopy(getenv("USER"));
477                 if (!srv->nick)
478                         srv->nick = strcopy("lameuser");
479                 if (srv->connect)
480                         irc_run(srv, "");
481         }
482         for (channel_t *cur = channels; cur; cur = cur->next) {
483                 if (cur->server->protocol != IRC)
484                         continue;
485
486                 irc_channel_t *chan = (irc_channel_t*)cur;
487                 if (!chan->dest)
488                         chan->dest = strcopy(cur->name);
489         }
490 }
491
492 void irc_config(server_t *server, channel_t *channel,
493                 const char *group, const char *name,
494                 const char *key, const char *value)
495 {
496         irc_server_t  *srv  = (irc_server_t*)server;
497         irc_channel_t *chan = (irc_channel_t*)channel;
498
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);
505                 }
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);
520         }
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);
527                 }
528                 else if (match(key, "dest"))
529                         chan->dest = get_string(value);
530                 else if (match(key, "join"))
531                         chan->join = get_bool(value);
532         }
533 }
534
535 void irc_send(channel_t *channel, const char *text)
536 {
537         irc_channel_t *chan = (irc_channel_t*)channel;
538         irc_server_t  *srv  = (irc_server_t*)channel->server;
539         const char *arg;
540
541         if (text[0] == '/') {
542                 if (prefix(text, "/join", &arg)) {
543                         if (arg)
544                                 chan = find_dest(srv, arg, 1);
545                         if (chan)
546                                 join_channel(srv, chan);
547                 }
548                 else if (prefix(text, "/part", &arg)) {
549                         if (arg)
550                                 chan = find_dest(srv, arg, 0);
551                         if (chan)
552                                 part_channel(srv, chan);
553                 }
554                 else if (prefix(text, "/query", &arg)) {
555                         if (!arg) {
556                                 chan_notice(chan, "usage: /query <user>");
557                                 return;
558                         }
559                         chan = find_dest(srv, arg, 0);
560                         chan_notice(chan, "User: %s", arg);
561                 }
562                 else {
563                         chan_notice(chan, "Unknown command %s", text);
564                 }
565         } else {
566                 if (chan == &srv->system) {
567                         chan_notice(chan, "Cannot send to server");
568                 }
569                 else if (!chan->dest) {
570                         chan_notice(chan, "No destination for message");
571                 }
572                 else {
573                         net_print(&srv->net, "PRIVMSG %s :%s\n", chan->dest, text);
574                         chat_recv(channel, &srv->myself.user, text);
575                 }
576         }
577 }
578
579 void irc_exit(void)
580 {
581 }
582