]> Pileus Git - ~andy/lamechat/blob - irc.c
XMPP OpenSSL
[~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 #define _GNU_SOURCE
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stdarg.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <arpa/inet.h>
29 #include <netinet/tcp.h>
30 #include <netdb.h>
31
32 #include "util.h"
33 #include "conf.h"
34 #include "chat.h"
35
36 /* IRC Constants */
37 #define BUF_LEN 1024
38
39 /* IRC types */
40 typedef enum {
41         IRC_DEAD,
42         IRC_NICK,
43         IRC_USER,
44         IRC_JOIN,
45         IRC_READY,
46 } irc_state_t;
47
48 typedef struct irc_server_t {
49         const char *name;
50         const char *protocol;
51         int         connect;
52         const char *host;
53         int         port;
54         const char *nick;
55         const char *auth;
56         const char *pass;
57
58         irc_state_t state;
59         poll_t      poll;
60
61         char        in_buf[BUF_LEN];
62         int         in_len;
63         char        out_buf[BUF_LEN];
64         int         out_pos;
65         int         out_len;
66
67         struct irc_server_t *next;
68 } irc_server_t;
69
70 typedef struct irc_channel_t {
71         const char *name;
72         const char *channel;
73         const char *server;
74         int         join;
75         struct irc_channel_t *next;
76 } irc_channel_t;
77
78 /* Local data */
79 static irc_server_t  *servers;
80 static irc_channel_t *channels;
81
82 /* Local functions */
83 static irc_server_t *find_server(const char *name, int create)
84 {
85         irc_server_t *cur = NULL, *last = NULL;
86         for (cur = servers; cur; last = cur, cur = cur->next)
87                 if (match(cur->name, name))
88                         break;
89         if (!cur && create) {
90                 cur = new0(irc_server_t);
91                 cur->name = get_name(name);
92                 if (last)
93                         last->next = cur;
94                 else
95                         servers = cur;
96         }
97         return cur;
98 }
99
100 static irc_channel_t *find_channel(const char *name, int create)
101 {
102         irc_channel_t *cur = NULL, *last = NULL;
103         for (cur = channels; cur; last = cur, cur = cur->next)
104                 if (match(cur->name, name))
105                         break;
106         if (!cur && create) {
107                 cur = new0(irc_channel_t);
108                 cur->name = get_name(name);
109                 if (last)
110                         last->next = cur;
111                 else
112                         channels = cur;
113         }
114         return cur;
115 }
116
117 static int send_line(irc_server_t *srv, const char *fmt, ...)
118 {
119         int len, sent;
120         va_list ap;
121         if (srv->out_len)
122                 return 0;
123
124         va_start(ap, fmt);
125         len = vsnprintf(srv->out_buf, BUF_LEN, fmt, ap);
126         va_end(ap);
127         if (len <= 0)
128                 return 0;
129         if (len >= BUF_LEN)
130                 len = BUF_LEN-1;
131         srv->out_buf[len] = '\n';
132         len++;
133
134         sent = send(srv->poll.fd, srv->out_buf, len, 0);
135         if (sent == len)
136                 return 1;
137
138         srv->out_len = len;
139         srv->out_pos = sent;
140         return 0;
141 }
142
143 static void recv_line(irc_server_t *srv, const char *line)
144 {
145         static char src[BUF_LEN]; // (:([^ ]+) +)?   
146         static char cmd[BUF_LEN]; // (([A-Z0-9]+) +) 
147         static char dst[BUF_LEN]; // (([^ ]+)[= ]+)? 
148         static char arg[BUF_LEN]; // (([^: ]+) *)?   
149         static char msg[BUF_LEN]; // (:(.*))?        
150         static char from[BUF_LEN];
151
152         int i;
153         const char *c = line;
154
155         // Clear strings
156         src[0] = cmd[0] = dst[0] = arg[0] = msg[0] = from[0] = '\0';
157
158         // Read src
159         if (*c == ':') {
160                 c++;
161                 for (i = 0; *c && *c != ' '; i++)
162                         src[i] = *c++;
163                 while (*c == ' ')
164                         c++;
165                 src[i] = '\0';
166                 for (i = 0; src[i] && src[i] != '!' && src[i] != ' '; i++)
167                         from[i] = src[i];
168                 from[i] = '\0';
169         }
170
171         // Read cmd
172         for (i = 0; isalnum(*c); i++)
173                 cmd[i] = *c++;
174         while (*c == ' ')
175                 c++;
176         cmd[i] = '\0';
177
178         // Read dst
179         if ((*c && *c != ' ') &&
180             (strchr(c+1, ' ') || strchr(c+1, '='))) {
181                 for (i = 0; *c && *c != ' '; i++)
182                         dst[i] = *c++;
183                 while (*c == '=' || *c == ' ')
184                         c++;
185                 dst[i] = '\0';
186         }
187
188         // Read arg
189         if  (*c && *c != ':' && *c != ' ') {
190                 for (i = 0; *c && *c != ' '; i++)
191                         arg[i] = *c++;
192                 while (*c == ' ')
193                         c++;
194                 arg[i] = '\0';
195         }
196
197         // Read msg
198         if (*c == ':') {
199                 c++;
200                 for (i = 0; *c; i++)
201                         msg[i] = *c++;
202                 msg[i] = '\0';
203         }
204
205         debug("got line: %s", line);
206         //debug("  src %s", src);
207         //debug("  cmd %s", cmd);
208         //debug("  dst %s", dst);
209         //debug("  arg %s", arg);
210         //debug("  msg %s", msg);
211
212         // Parse messages
213         if (match(cmd, "001") || strstr(msg, "Welcome"))
214                 srv->state = IRC_JOIN;
215         if (match(cmd, "PING"))
216                 send_line(srv, "PING %s", msg);
217         if (match(cmd, "PRIVMSG"))
218                 chat_recv(dst, from, msg);
219 }
220
221 static void on_poll(void *_srv)
222 {
223         static char buf[BUF_LEN];
224         static char hostname[512];
225
226         irc_server_t *srv = _srv;
227         int len;
228
229         /* Handle Input */
230         len = recv(srv->poll.fd, buf, sizeof(buf), 0);
231         if (len < 0) {
232                 debug("recv: error: %s -- %s", srv->name, strerror(errno));
233         }
234         if (len == 0) {
235                 debug("recv: close: %s", srv->name);
236                 srv->state = IRC_DEAD;
237                 poll_del(&srv->poll);
238                 return;
239         }
240         if (len > 0) {
241                 debug("recv: ready: %s -- [%.*s]", srv->name, len, buf);
242         }
243         for (int i = 0; i < len; i++) {
244                 if (srv->in_len < BUF_LEN) {
245                         srv->in_buf[srv->in_len] = buf[i];
246                         srv->in_len++;
247                 }
248                 if (buf[i] == '\n' || buf[i] == '\r') {
249                         srv->in_buf[srv->in_len-1] = '\0';
250                         if (srv->in_len > 1)
251                                 recv_line(srv, srv->in_buf);
252                         srv->in_len = 0;
253                 }
254         }
255
256         /* Handle Output */
257         if (srv->out_len > 0) {
258                 len = send(srv->poll.fd, &srv->out_buf[srv->out_pos],
259                                srv->out_len-srv->out_pos, 0);
260                 if (len > 0)
261                         srv->out_pos += len;
262                 if (srv->out_pos == srv->out_len) {
263                         srv->out_pos = 0;
264                         srv->out_len = 0;
265                 }
266         }
267
268         /* Handle Errors */
269         int err = 0;
270         socklen_t elen = sizeof(err);
271         if (getsockopt(srv->poll.fd, SOL_SOCKET, SO_ERROR, &err, &elen))
272                 error("Error getting socket opt");
273         if (err) {
274                 debug("disconnect: %s -- %s", srv->name, strerror(err));
275                 srv->state = IRC_DEAD;
276                 poll_del(&srv->poll);
277                 return;
278         }
279
280         /* State machine */
281         if (srv->state == IRC_USER) {
282                 if (gethostname(hostname, sizeof(hostname)))
283                         error("Error getting hostname");
284                 if (send_line(srv, "USER %s %s %s :%s",
285                                 getenv("USER") ?: "lameuser",
286                                 hostname, srv->host, srv->nick))
287                         srv->state = IRC_NICK;
288         }
289         if (srv->state == IRC_NICK) {
290                 if (send_line(srv, "NICK %s", srv->nick))
291                         srv->state = IRC_READY;
292         }
293         if (srv->state == IRC_JOIN) {
294                 for (irc_channel_t *chan = channels; chan; chan = chan->next)
295                         if (chan->join)
296                                 send_line(srv, "JOIN %s", chan->channel);
297                 srv->state = IRC_READY;
298         }
299
300         /* Enable output poll */
301         if (srv->out_len)
302                 poll_ctl(&srv->poll, 1, 1, 1);
303         else
304                 poll_ctl(&srv->poll, 1, 0, 1);
305 }
306
307 static void irc_connect(irc_server_t *srv)
308 {
309         int sock, flags;
310         struct addrinfo *addrs = NULL;
311         struct addrinfo hints = {};
312         char service[16];
313
314         snprintf(service, sizeof(service), "%d", srv->port);
315         hints.ai_family   = AF_INET;
316         hints.ai_socktype = SOCK_STREAM;
317
318         /* Setup address */
319         if (getaddrinfo(srv->host, service, &hints, &addrs))
320                 error("Error getting address info");
321
322         if ((sock = socket(addrs->ai_family,
323                            addrs->ai_socktype,
324                            addrs->ai_protocol)) < 0)
325                 error("Error opening irc socket");
326
327         if ((flags = fcntl(sock, F_GETFL, 0)) < 0)
328                 error("Error getting irc socket flags");
329
330         if (fcntl(sock, F_SETFL, flags|O_NONBLOCK) < 0)
331                 error("Error setting irc socket non-blocking");
332
333         if (connect(sock, addrs->ai_addr, addrs->ai_addrlen) < 0)
334                 if (errno != EINPROGRESS)
335                         error("Error connecting socket");
336
337         freeaddrinfo(addrs);
338
339         /* Setup server */
340         srv->state = IRC_USER;
341         poll_add(&srv->poll, sock, on_poll, srv);
342 }
343
344 /* IRC functions */
345 void irc_init(void)
346 {
347         for (irc_server_t *cur = servers; cur; cur = cur->next) {
348                 if (!match(cur->protocol, "irc"))
349                         continue;
350                 if (!cur->port)
351                         cur->port = 6667;
352                 if (!cur->nick)
353                         cur->nick = strcopy(getenv("USER"));
354                 if (!cur->nick)
355                         cur->nick = strcopy("lameuser");
356                 if (cur->connect)
357                         irc_connect(cur);
358         }
359 }
360
361 void irc_config(const char *group, const char *name, const char *key, const char *value)
362 {
363         irc_server_t  *srv;
364         irc_channel_t *chan;
365
366         if (match(group, "server")) {
367                 srv = find_server(name, 1);
368                 if (match(key, "protocol") &&
369                     match(value, "irc"))
370                         srv->protocol = get_string(value);
371                 if (match(srv->protocol, "irc")) {
372                         if (match(key, "connect"))
373                                 srv->connect = get_bool(value);
374                         else if (match(key, "host"))
375                                 srv->host = get_string(value);
376                         else if (match(key, "port"))
377                                 srv->port = get_number(value);
378                         else if (match(key, "nick"))
379                                 srv->nick = get_string(value);
380                         else if (match(key, "auth"))
381                                 srv->auth = get_string(value);
382                         else if (match(key, "pass"))
383                                 srv->pass = get_string(value);
384                 }
385         } else if (match(group, "channel")) {
386                 chan = find_channel(name, 1);
387                 if (match(key, "server") &&
388                     find_server(value, 0))
389                         chan->server = get_string(value);
390                 if (chan->server) {
391                         if (match(key, "channel"))
392                                 chan->channel = get_string(value);
393                         else if (match(key, "join"))
394                                 chan->join = get_bool(value);
395                 }
396         }
397 }
398
399 void irc_send(const char *channel, const char *msg)
400 {
401         irc_channel_t *chan = find_channel(channel, 0);
402         if (!chan)
403                 return;
404         irc_server_t *srv = find_server(chan->server, 0);
405         if (!srv)
406                 return;
407         send_line(srv, "PRIVMSG %s :%s", chan->channel, msg);
408 }
409
410 void irc_exit(void)
411 {
412 }
413