]> Pileus Git - ~andy/lamechat/blob - irc.c
Split chat by channels.
[~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_DEAD,
35
36         IRC_SEND_TLS,
37         IRC_RECV_TLS,
38         IRC_SEND_STARTTLS,
39         IRC_RECV_STARTTLS,
40
41         IRC_SEND_SASL,
42         IRC_RECV_SASL,
43         IRC_SEND_PLAIN,
44         IRC_SEND_AUTH,
45         IRC_RECV_STATUS,
46         IRC_SEND_END,
47
48         IRC_SEND_USER,
49         IRC_SEND_NICK,
50         IRC_RECV_WELCOME,
51
52         IRC_ENCRYPT,
53         IRC_JOIN,
54         IRC_READY,
55 } irc_state_t;
56
57 typedef struct {
58         server_t    server;
59
60         int         connect;
61         const char *host;
62         int         port;
63         int         tls;
64         const char *nick;
65         const char *auth;
66         const char *pass;
67
68         net_t       net;
69         irc_state_t state;
70         char        line[IRC_LINE];
71         int         pos;
72 } irc_server_t;
73
74 typedef struct {
75         channel_t   channel;
76
77         const char *dest;
78         int         join;
79 } irc_channel_t;
80
81 /* Local functions */
82 static void recv_line(irc_server_t *srv, const char *line)
83 {
84         static char src[IRC_LINE]; // (:([^ ]+) +)?   
85         static char cmd[IRC_LINE]; // (([A-Z0-9]+) +) 
86         static char dst[IRC_LINE]; // (([^ ]+)[= ]+)? 
87         static char arg[IRC_LINE]; // (([^: ]+) *)?   
88         static char msg[IRC_LINE]; // (:(.*))?        
89         static char from[IRC_LINE];
90
91         int i;
92         const char *c = line;
93
94         /* Clear strings */
95         src[0] = cmd[0] = dst[0] = arg[0] = msg[0] = from[0] = '\0';
96
97         /* Read src */
98         if (*c == ':') {
99                 c++;
100                 for (i = 0; *c && *c != ' '; i++)
101                         src[i] = *c++;
102                 while (*c == ' ')
103                         c++;
104                 src[i] = '\0';
105                 for (i = 0; src[i] && src[i] != '!' && src[i] != ' '; i++)
106                         from[i] = src[i];
107                 from[i] = '\0';
108         }
109
110         /* Read cmd */
111         for (i = 0; isalnum(*c); i++)
112                 cmd[i] = *c++;
113         while (*c == ' ')
114                 c++;
115         cmd[i] = '\0';
116
117         /* Read dst */
118         if ((*c && *c != ' ') &&
119             (strchr(c+1, ' ') || strchr(c+1, '='))) {
120                 for (i = 0; *c && *c != ' '; i++)
121                         dst[i] = *c++;
122                 while (*c == '=' || *c == ' ')
123                         c++;
124                 dst[i] = '\0';
125         }
126
127         /* Read arg */
128         if  (*c && *c != ':' && *c != ' ') {
129                 for (i = 0; *c && *c != ' '; i++)
130                         arg[i] = *c++;
131                 while (*c == ' ')
132                         c++;
133                 arg[i] = '\0';
134         }
135
136         /* Read msg */
137         if (*c == ':') {
138                 c++;
139                 for (i = 0; *c; i++)
140                         msg[i] = *c++;
141                 msg[i] = '\0';
142         }
143
144         debug("got line: [%s]", line);
145         //debug("  src %s", src);
146         //debug("  cmd %s", cmd);
147         //debug("  dst %s", dst);
148         //debug("  arg %s", arg);
149         //debug("  msg %s", msg);
150
151         /* Start TLS */
152         if (srv->state == IRC_RECV_TLS) {
153                 if (match(cmd, "CAP") && match(arg, "ACK")) {
154                         chat_notice(NULL, from, "Start TLS proceeding");
155                         srv->state = IRC_SEND_STARTTLS;
156                 }
157                 if (match(cmd, "CAP") && match(arg, "NAK")) {
158                         chat_notice(NULL, from, "Start TLS unsupported");
159                         srv->state = IRC_SEND_END;
160                 }
161         }
162         if (srv->state == IRC_RECV_STARTTLS) {
163                 if (prefix(msg, "STARTTLS successful", NULL))
164                         srv->state = IRC_ENCRYPT;
165         }
166
167         /* SASL Authentication */
168         if (srv->state == IRC_RECV_SASL) {
169                 if (match(cmd, "CAP") && match(arg, "ACK")) {
170                         chat_notice(NULL, from, "SASL auth proceeding");
171                         srv->state = IRC_SEND_PLAIN;
172                 }
173                 if (match(cmd, "CAP") && match(arg, "NAK")) {
174                         chat_notice(NULL, from, "SASL auth unsupported");
175                         srv->state = IRC_SEND_END;
176                 }
177         }
178         if (srv->state == IRC_RECV_STATUS) {
179                 if (match(cmd, "903")) {
180                         chat_notice(NULL, from, "SASL auth succeeded");
181                         srv->state = IRC_SEND_END;
182                 }
183                 if (match(cmd, "904") ||
184                     match(cmd, "905") ||
185                     match(cmd, "906") ||
186                     match(cmd, "907")) {
187                         chat_notice(NULL, from, "SASL auth failed");
188                         srv->state = IRC_DEAD;
189                 }
190         }
191
192         /* Connection setup */
193         if (srv->state == IRC_RECV_WELCOME)
194                 if (match(cmd, "001") && strstr(msg, "Welcome"))
195                         srv->state = IRC_JOIN;
196
197         /* Receive messages */
198         if (srv->state == IRC_READY) {
199                 if (match(cmd, "PING"))
200                         net_print(&srv->net, "PING %s\n", msg);
201                 if (match(cmd, "PRIVMSG"))
202                         chat_recv(find_channel(dst), from, msg);
203         }
204
205         /* Receive notices */
206         if (match(cmd, "NOTICE") ||
207             match(cmd, "001")    ||
208             match(cmd, "372")    ||
209             match(cmd, "375")    ||
210             match(cmd, "376"))
211                 chat_notice(NULL, from, "%s", msg);
212 }
213
214 static void on_recv(void *_srv, char *buf, int len)
215 {
216         static char plain[IRC_LINE];
217         static char coded[IRC_LINE];
218         irc_server_t *srv = _srv;
219
220         /* Handle Input */
221         for (int i = 0; i < len; i++) {
222                 if (srv->pos < IRC_LINE) {
223                         srv->line[srv->pos] = buf[i];
224                         srv->pos++;
225                 }
226                 if (buf[i] == '\n' || buf[i] == '\r') {
227                         srv->line[srv->pos-1] = '\0';
228                         if (srv->pos > 1)
229                                 recv_line(srv, srv->line);
230                         srv->pos = 0;
231                 }
232         }
233
234         /* Encryption */
235         if (srv->state == IRC_ENCRYPT) {
236                 net_encrypt(&srv->net);
237                 srv->state = IRC_SEND_SASL;
238         }
239
240         /* Start TLS */
241         if (srv->state == IRC_SEND_TLS) {
242                 if (net_print(&srv->net, "CAP REQ :tls\n"))
243                         srv->state = IRC_RECV_TLS;
244         }
245         if (srv->state == IRC_SEND_STARTTLS) {
246                 if (net_print(&srv->net, "STARTTLS\n"))
247                         srv->state = IRC_RECV_STARTTLS;
248         }
249
250         /* SASL authentication */
251         if (srv->state == IRC_SEND_SASL) {
252                 if (net_print(&srv->net, "CAP REQ :sasl\n"))
253                         srv->state = IRC_RECV_SASL;
254         }
255         if (srv->state == IRC_SEND_PLAIN) {
256                 if (net_print(&srv->net, "AUTHENTICATE PLAIN\n"))
257                         srv->state = IRC_SEND_AUTH;
258         }
259         if (srv->state == IRC_SEND_AUTH) {
260                 len = snprintf(plain, IRC_LINE, "%s%c%s%c%s",
261                                 srv->auth, '\0', srv->auth, '\0', srv->pass);
262                 len = base64(plain, len, coded, IRC_LINE);
263                 if (net_print(&srv->net, "AUTHENTICATE %.*s\n", len, coded))
264                         srv->state = IRC_RECV_STATUS;
265         }
266         if (srv->state == IRC_SEND_END) {
267                 if (net_print(&srv->net, "CAP END\n"))
268                         srv->state = IRC_SEND_USER;
269         }
270
271         /* Connection setup */
272         if (srv->state == IRC_SEND_USER) {
273                 if (net_print(&srv->net, "USER %s %s %s :%s\n",
274                                 getenv("USER") ?: "lameuser",
275                                 get_hostname(), srv->host, srv->nick))
276                         srv->state = IRC_SEND_NICK;
277         }
278         if (srv->state == IRC_SEND_NICK) {
279                 if (net_print(&srv->net, "NICK %s\n", srv->nick))
280                         srv->state = IRC_RECV_WELCOME;
281         }
282         if (srv->state == IRC_JOIN) {
283                 for (channel_t *cur = channels; cur; cur = cur->next) {
284                         irc_channel_t *chan = (irc_channel_t*)cur;
285                         if (cur->server != &srv->server || !chan->join)
286                                 continue;
287                         net_print(&srv->net, "JOIN %s\n", chan->dest);
288                 }
289                 srv->state = IRC_READY;
290         }
291 }
292
293 static void irc_connect(irc_server_t *srv)
294 {
295         /* Net connect */
296         srv->net.recv = on_recv;
297         srv->net.data = srv;
298         net_open(&srv->net, srv->host, srv->port);
299
300         /* Setup server */
301         if (srv->tls)
302                 srv->state = IRC_ENCRYPT;
303         else
304                 srv->state = IRC_SEND_TLS;
305 }
306
307 /* IRC functions */
308 void irc_init(void)
309 {
310         for (server_t *cur = servers; cur; cur = cur->next) {
311                 if (cur->protocol != IRC)
312                         continue;
313
314                 irc_server_t *srv = (irc_server_t*)cur;
315                 if (!srv->port)
316                         srv->port = srv->tls ? 6697 : 6667;
317                 if (!srv->nick)
318                         srv->nick = strcopy(getenv("USER"));
319                 if (!srv->nick)
320                         srv->nick = strcopy("lameuser");
321                 if (srv->connect)
322                         irc_connect(srv);
323         }
324         for (channel_t *cur = channels; cur; cur = cur->next) {
325                 if (cur->server->protocol != IRC)
326                         continue;
327
328                 irc_channel_t *chan = (irc_channel_t*)cur;
329                 if (!chan->dest)
330                         chan->dest = strcopy(cur->name);
331         }
332 }
333
334 server_t *irc_server(void)
335 {
336         return new0(irc_server_t);
337 }
338
339 channel_t *irc_channel(void)
340 {
341         return new0(irc_channel_t);
342 }
343
344 void irc_config(server_t *server, channel_t *channel,
345                 const char *group, const char *name,
346                 const char *key, const char *value)
347 {
348         irc_server_t  *srv  = (irc_server_t*)server;
349         irc_channel_t *chan = (irc_channel_t*)channel;
350
351         if (server) {
352                 if (match(key, "connect"))
353                         srv->connect = get_bool(value);
354                 else if (match(key, "host"))
355                         srv->host = get_string(value);
356                 else if (match(key, "port"))
357                         srv->port = get_number(value);
358                 else if (match(key, "tls"))
359                         srv->tls = get_bool(value);
360                 else if (match(key, "nick"))
361                         srv->nick = get_string(value);
362                 else if (match(key, "auth"))
363                         srv->auth = get_string(value);
364                 else if (match(key, "pass"))
365                         srv->pass = get_string(value);
366         }
367         if (channel) {
368                 if (match(key, "dest"))
369                         chan->dest = get_string(value);
370                 else if (match(key, "join"))
371                         chan->join = get_bool(value);
372         }
373 }
374
375 void irc_send(message_t *msg)
376 {
377         irc_channel_t *chan = (irc_channel_t*)msg->channel;
378         irc_server_t  *srv  = (irc_server_t*)msg->channel->server;
379
380         net_print(&srv->net, "PRIVMSG %s :%s\n", chan->channel, msg);
381 }
382
383 void irc_exit(void)
384 {
385 }
386