]> Pileus Git - ~andy/lamechat/blob - irc.c
Get IRC working.
[~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 *protocol;
50         int         connect;
51         const char *name;
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(int fd, irc_server_t *srv)
222 {
223         static char buf[BUF_LEN];
224         static char hostname[512];
225         int len;
226
227         /* Handle Input */
228         len = recv(fd, buf, sizeof(buf), 0);
229         for (int i = 0; i < len; i++) {
230                 if (srv->in_len < BUF_LEN) {
231                         srv->in_buf[srv->in_len] = buf[i];
232                         srv->in_len++;
233                 }
234                 if (buf[i] == '\n' || buf[i] == '\r') {
235                         srv->in_buf[srv->in_len-1] = '\0';
236                         if (srv->in_len > 1)
237                                 recv_line(srv, srv->in_buf);
238                         srv->in_len = 0;
239                 }
240         }
241
242         /* Handle Output */
243         if (srv->out_len > 0) {
244                 len = send(fd, &srv->out_buf[srv->out_pos],
245                                srv->out_len-srv->out_pos, 0);
246                 if (len > 0)
247                         srv->out_pos += len;
248                 if (srv->out_pos == srv->out_len) {
249                         srv->out_pos = 0;
250                         srv->out_len = 0;
251                 }
252         }
253
254         /* Handle Errors */
255         int err = 0;
256         socklen_t elen = sizeof(err);
257         if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &elen))
258                 error("Error getting socket opt");
259         if (err) {
260                 debug("disconnect: %s -- %s", srv->name, strerror(err));
261                 srv->state = IRC_DEAD;
262                 poll_del(&srv->poll);
263                 return;
264         }
265
266         /* State machine */
267         if (srv->state == IRC_USER) {
268                 if (gethostname(hostname, sizeof(hostname)))
269                         error("Error getting hostname");
270                 if (send_line(srv, "USER %s %s %s :%s",
271                                 getenv("USER") ?: "lameuser",
272                                 hostname, srv->host, srv->nick))
273                         srv->state = IRC_NICK;
274         }
275         if (srv->state == IRC_NICK) {
276                 if (send_line(srv, "NICK %s", srv->nick))
277                         srv->state = IRC_READY;
278         }
279         if (srv->state == IRC_JOIN) {
280                 for (irc_channel_t *chan = channels; chan; chan = chan->next)
281                         if (chan->join)
282                                 send_line(srv, "JOIN %s", chan->channel);
283                 srv->state = IRC_READY;
284         }
285
286         /* Enable output poll */
287         if (srv->out_len)
288                 poll_ctl(&srv->poll, 1, 1, 1);
289         else
290                 poll_ctl(&srv->poll, 1, 0, 1);
291 }
292
293 static void irc_connect(irc_server_t *srv)
294 {
295         int sock, flags;
296         struct addrinfo *addrs = NULL;
297         struct addrinfo hints = {};
298         char service[16];
299
300         snprintf(service, sizeof(service), "%d", srv->port);
301         hints.ai_family   = AF_INET;
302         hints.ai_socktype = SOCK_STREAM;
303
304         /* Setup address */
305         if (getaddrinfo(srv->host, service, &hints, &addrs))
306                 error("Error getting address info");
307
308         if ((sock = socket(addrs->ai_family,
309                            addrs->ai_socktype,
310                            addrs->ai_protocol)) < 0)
311                 error("Error opening web socket");
312
313         if ((flags = fcntl(sock, F_GETFL, 0)) < 0)
314                 error("Error getting web socket flags");
315
316         if (fcntl(sock, F_SETFL, flags|O_NONBLOCK) < 0)
317                 error("Error setting web socket non-blocking");
318
319         if (connect(sock, addrs->ai_addr, addrs->ai_addrlen) < 0)
320                 if (errno != EINPROGRESS)
321                         error("Error connecting socket");
322
323         freeaddrinfo(addrs);
324
325         /* Setup server */
326         srv->state = IRC_USER;
327         poll_add(&srv->poll, sock, (cb_t)on_poll, srv);
328 }
329
330 /* IRC functions */
331 void irc_init(void)
332 {
333         for (irc_server_t *cur = servers; cur; cur = cur->next) {
334                 if (!match(cur->protocol, "irc"))
335                         continue;
336                 if (!cur->port)
337                         cur->port = 6667;
338                 if (!cur->nick)
339                         cur->nick = strcopy(getenv("USER"));
340                 if (!cur->nick)
341                         cur->nick = strcopy("lameuser");
342                 if (cur->connect)
343                         irc_connect(cur);
344         }
345 }
346
347 void irc_config(const char *group, const char *name, const char *key, const char *value)
348 {
349         irc_server_t  *srv;
350         irc_channel_t *chan;
351
352         if (match(group, "server")) {
353                 srv = find_server(name, 1);
354                 if (match(key, "protocol") &&
355                     match(value, "irc"))
356                         srv->protocol = get_string(value);
357                 if (match(srv->protocol, "irc")) {
358                         if (match(key, "connect"))
359                                 srv->connect = get_bool(value);
360                         else if (match(key, "host"))
361                                 srv->host = get_string(value);
362                         else if (match(key, "port"))
363                                 srv->port = get_number(value);
364                         else if (match(key, "nick"))
365                                 srv->nick = get_string(value);
366                         else if (match(key, "auth"))
367                                 srv->auth = get_string(value);
368                         else if (match(key, "pass"))
369                                 srv->pass = get_string(value);
370                 }
371         } else if (match(group, "channel")) {
372                 chan = find_channel(name, 1);
373                 if (match(key, "server") &&
374                     find_server(value, 0))
375                         chan->server = get_string(value);
376                 if (chan->server) {
377                         if (match(key, "channel"))
378                                 chan->channel = get_string(value);
379                         else if (match(key, "join"))
380                                 chan->join = get_bool(value);
381                 }
382         }
383 }
384
385 void irc_send(const char *channel, const char *msg)
386 {
387         irc_channel_t *chan = find_channel(channel, 0);
388         if (!chan)
389                 return;
390         irc_server_t *srv = find_server(chan->server, 0);
391         if (!srv)
392                 return;
393         send_line(srv, "PRIVMSG %s :%s", chan->channel, msg);
394 }
395
396 void irc_exit(void)
397 {
398 }
399