add sanity checking for \r and \n
[fillybot.git] / main.c
blob52be7f8daa3829b5a2a96a24cce2dcbf3173b718
1 #include "core.h"
2 #include <dlfcn.h>
3 #include <getopt.h>
5 static char nick_self[128];
6 static char *channels[128];
7 static unsigned nchannel;
8 static char *password;
9 static char *wanted_nick;
11 static void *dl_mod;
12 static typeof(init_hook) *init_ptr;
13 static typeof(privmsg_hook) *privmsg_ptr;
14 static typeof(command_hook) *command_ptr;
16 static int reload_requested = 1;
17 unsigned ponify;
19 static void reload_handler(int signal)
21 reload_requested = 1;
24 static void handle_connect(struct bio *b, const char *prefix, const char *ident, const char *host,
25 const char *const *args, unsigned nargs)
27 int i;
28 strncpy(nick_self, args[0], sizeof(nick_self)-1);
29 b->writeline(b, "PRIVMSG NICKSERV :identify %s %s", wanted_nick, password);
30 for (i = 0; i < nchannel; ++i)
31 b->writeline(b, "JOIN %s", channels[i]);
34 static void handle_nick(struct bio *b, const char *prefix, const char *ident, const char *host,
35 const char *const *args, unsigned nargs)
37 if (!strcmp(nick_self, prefix)) {
38 strncpy(nick_self, args[0], sizeof(nick_self)-1);
39 return;
41 if (!strcasecmp(args[0], "SweetieBelle") || !strcasecmp(args[0], wanted_nick))
42 b->writeline(b, "PRIVMSG NICKSERV :ghost %s %s", args[0], password);
43 if (dl_mod && command_ptr)
44 command_ptr(b, prefix, ident, host, "NICK", args, nargs);
47 static void handle_ping(struct bio *b, const char *prefix, const char *ident, const char *host,
48 const char *const *args, unsigned nargs)
50 b->writeline(b, "PONG %s", args[0]);
53 static void mod_unload(struct bio *b, const char *target)
55 reload_requested = 0;
56 if (dlclose(dl_mod)) {
57 const char *foo = strchr(dlerror(), ':');
58 if (foo)
59 foo += 2;
60 b->writeline(b, "PRIVMSG %s :UNLOAD ERROR: %s", target, foo ? foo : dlerror());
62 dl_mod = NULL;
63 privmsg_ptr = NULL;
64 command_ptr = NULL;
67 static void mod_load(struct bio *b, const char *target)
69 if (dl_mod)
70 mod_unload(b, target);
71 reload_requested = 0;
72 dl_mod = dlopen("mod.so", RTLD_LOCAL|RTLD_NOW);
73 if (!dl_mod) {
74 const char *foo = strchr(dlerror(), ':');
75 if (foo)
76 foo += 2;
77 b->writeline(b, "PRIVMSG %s :LOAD ERROR: %s", target, foo ? foo : dlerror());
80 init_ptr = dlsym(dl_mod, "init_hook");
81 privmsg_ptr = dlsym(dl_mod, "privmsg_hook");
82 command_ptr = dlsym(dl_mod, "command_hook");
83 if (init_ptr)
84 init_ptr(b, target, nick_self, ponify);
85 else
86 b->writeline(b, "PRIVMSG %s :I feel smarter", target);
89 static void handle_privmsg(struct bio *b, const char *prefix, const char *ident, const char *host,
90 const char *const *args, unsigned nargs)
92 const char *target;
93 if (!ident || !host)
94 return;
95 target = args[0][0] == '#' ? args[0] : prefix;
96 if (admin(host)) {
97 if (!strcmp(args[1], "crash")) {
98 *(char *)0 = 0;
99 return;
100 } else if (!strcmp(args[1], "load")) {
101 mod_load(b, target);
102 return;
103 } else if (!strcmp(args[1], "unload")) {
104 if (!dl_mod)
105 return;
106 mod_unload(b, target);
107 b->writeline(b, "PRIVMSG %s :{ I feel different. }", target);
108 return;
111 if (!strcmp(args[1], "bt")) {
112 crash_write_backtrace(b, prefix);
113 return;
116 if (dl_mod && privmsg_ptr)
117 privmsg_ptr(b, prefix, ident, host, args, nargs);
118 if (reload_requested)
119 mod_load(b, target);
122 struct command {
123 const char *cmd;
124 unsigned min_args;
125 unsigned prefixed;
126 void (*ptr)(struct bio *b, const char *prefix, const char *ident, const char *host,
127 const char *const *args, unsigned nargs);
128 unsigned builtin;
129 } cmds[] = {
130 { "PRIVMSG", 2, 1, handle_privmsg },
131 { "NICK", 1, 1, handle_nick },
132 { "PING", 1, 0, handle_ping, 1 },
133 { "001", 2, 0, handle_connect, 1 },
137 static void ssl_writeline(struct bio *b, const char *fmt, ...)
138 __attribute__ ((__format__ (__printf__, 2, 3)));
140 static void plain_writeline(struct bio *b, const char *fmt, ...)
141 __attribute__ ((__format__ (__printf__, 2, 3)));
143 static void bio_seterr(struct bio *b, ssize_t err)
145 if (b->err == 0)
146 b->err = errno;
149 static void bio_fill(struct bio *b, ssize_t avail)
151 if (b->maxlen < avail + b->len) {
152 memmove(b->priv, b->priv + b->ofs, b->len - b->ofs);
153 b->len -= b->ofs;
154 b->ofs = 0;
155 if (b->maxlen < b->len + avail) {
156 b->priv = realloc(b->priv, b->len + avail);
157 b->maxlen = b->len + avail;
162 static int plain_fill(struct bio *b)
164 ssize_t avail = 0;
165 if (ioctl(b->fd, FIONREAD, &avail) < 0) {
166 bio_seterr(b, -errno);
167 return -errno;
169 bio_fill(b, avail);
170 avail = read(b->fd, b->priv + b->len, avail);
171 b->len += avail;
172 return avail;
175 static int ssl_fill(struct bio *b)
177 ssize_t avail;
178 int ret;
180 bio_fill(b, 1);
181 ret = SSL_read(b->ssl, b->priv + b->len, 1);
182 if (ret <= 0)
183 return 0;
184 b->len += ret;
186 avail = SSL_pending(b->ssl);
187 if (avail <= 0)
188 return 0;
189 bio_fill(b, avail);
190 avail = SSL_read(b->ssl, b->priv + b->len, avail);
191 if (avail <= 0)
192 return 0;
193 b->len += avail;
194 return avail;
197 static int bio_poll(struct bio *b, int timeout)
199 int ret;
200 struct pollfd pfd = {
201 .fd = b->fd,
202 .events = POLLIN|POLLRDHUP|POLLHUP
204 ret = poll(&pfd, 1, timeout);
205 if (ret && pfd.revents & ~POLLIN)
206 return -1;
207 return ret;
210 static int bio_readline(struct bio *b, char **line)
212 int i;
213 *line = NULL;
214 for (i = b->ofs; i < b->len; ++i) {
215 if (b->priv[i] == 0)
216 b->priv[i] = '0';
217 if (b->priv[i] != '\r' && b->priv[i] != '\n')
218 continue;
219 if (i != b->ofs)
220 break;
221 else
222 b->ofs++;
224 if (b->ofs == b->len) {
225 b->len = b->ofs = 0;
226 return 0;
228 if (i == b->len)
229 return 0;
230 *line = b->priv + b->ofs;
231 i -= b->ofs;
232 (*line)[i] = 0;
233 if (i == b->len-1)
234 b->len = b->ofs = 0;
235 else
236 b->ofs += i + 1;
237 return i;
240 static void plain_writeline(struct bio *b, const char *fmt, ...)
242 char *buffer, *ofs;
243 ssize_t ret, rem;
244 va_list va;
245 va_start(va, fmt);
246 ret = vasprintf(&buffer, fmt, va);
247 va_end(va);
248 if (ret <= 0) {
249 bio_seterr(b, ret);
250 return;
252 buffer[ret] = '\n'; /* Replace \0 with \n */
253 rem = ret+1;
254 ofs = buffer;
255 do {
256 ret = write(b->fd, ofs, rem);
257 if (ret < 0)
258 break;
259 ofs += ret;
260 rem -= ret;
261 } while (rem);
262 free(buffer);
265 static void ssl_writeline(struct bio *b, const char *fmt, ...)
267 char *buffer, *ofs;
268 ssize_t ret, rem;
269 va_list va;
271 va_start(va, fmt);
272 ret = vasprintf(&buffer, fmt, va);
273 va_end(va);
274 if (ret <= 0) {
275 bio_seterr(b, ret);
276 return;
278 buffer[ret] = '\n'; /* Replace \0 with \n */
279 rem = ret+1;
280 ofs = buffer;
282 do {
283 ret = SSL_write(b->ssl, ofs, rem);
284 if (ret <= 0) {
285 bio_seterr(b, SSL_get_error(b->ssl, ret));
286 break;
288 ofs += ret;
289 rem -= ret;
290 } while (rem);
291 free(buffer);
294 static int get_connection(const char *server, const char *port)
296 int sfd, s;
297 struct addrinfo hints, *result, *rp;
299 memset(&hints, 0, sizeof(struct addrinfo));
300 hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
301 hints.ai_socktype = SOCK_STREAM;
302 hints.ai_flags = 0;
303 hints.ai_protocol = 0;
305 s = getaddrinfo(server, port, &hints, &result);
306 if (s != 0) {
307 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
308 return -1;
311 for (rp = result; rp != NULL; rp = rp->ai_next) {
312 sfd = socket(rp->ai_family, rp->ai_socktype,
313 rp->ai_protocol);
314 if (sfd == -1)
315 continue;
317 if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
318 break; /* Success */
320 close(sfd);
323 if (rp == NULL) {
324 fprintf(stderr, "Could not connect\n");
325 return -1;
328 freeaddrinfo(result);
329 return sfd;
332 static char *token(char **foo)
334 char *line = *foo, *ret;
335 ret = line;
336 while (*line && *line != ' ')
337 line++;
338 if (*line) {
339 *(line++) = 0;
340 while (*line == ' ')
341 line++;
342 *foo = line;
343 } else
344 *foo = NULL;
345 return ret;
348 static inline void handle_line(struct bio *b, char *line)
350 char *prefix = NULL, *command, *ident = NULL, *host = NULL;
351 const char *args[128];
352 int i, nargs = 0;
353 if (line[0] == ':') {
354 line++;
355 prefix = token(&line);
356 ident = strchr(prefix, '!');
357 if (ident) {
358 *(ident++) = 0;
359 host = strchr(ident, '@');
360 if (host)
361 *(host++) = 0;
363 if (!line)
364 return;
366 command = token(&line);
368 while (line && nargs < sizeof(args)/sizeof(*args)) {
369 if (line[0] == ':') {
370 args[nargs++] = ++line;
371 break;
373 args[nargs++] = token(&line);
375 for (i = 0; i < sizeof(cmds)/sizeof(*cmds)-1; ++i) {
376 typeof(*cmds) *c = cmds+i;
377 if (strcasecmp(c->cmd, command))
378 continue;
379 if (nargs < c->min_args) {
380 fprintf(stderr, "Dropped %s because of %u < %u\n", command, nargs, c->min_args);
381 continue;
383 if (!prefix && c->prefixed) {
384 fprintf(stderr, "Dropped %s because not prefixed\n", command);
385 continue;
387 if (!c->builtin) {
388 int sig = sigsetjmp(crash_buf, 1);
389 if (!sig) {
390 set_signals(1);
391 c->ptr(b, prefix, ident, host, args, nargs);
393 set_signals(0);
394 if (sig)
395 crash_mod(b, channels[0]);
396 } else
397 c->ptr(b, prefix, ident, host, args, nargs);
398 break;
400 if (!cmds[i].cmd && dl_mod && command_ptr) {
401 int sig = setjmp(crash_buf);
402 if (!sig) {
403 set_signals(1);
404 command_ptr(b, prefix, ident, host, command, args, nargs);
406 set_signals(0);
407 if (sig)
408 crash_mod(b, channels[0]);
412 static void mainloop(struct bio *b, const char *server)
414 int ret = 0, pinged = 0;
415 b->writeline(b, "NICK %s", wanted_nick);
416 if (ponify)
417 b->writeline(b, "USER %s localhost %s :{ Cutie Mark Acquisition Program }", wanted_nick, server);
418 else
419 b->writeline(b, "USER %s localhost %s :Robotic overlord", wanted_nick, server);
420 while (ret >= 0) {
421 char *line;
422 ret = b->poll(b, pinged ? 50000 : 250000);
423 if (!ret) {
424 if (pinged)
425 break;
426 b->writeline(b, "PING %s", nick_self);
427 pinged = 1;
428 continue;
430 pinged = 0;
431 if (ret > 0)
432 ret = b->fill(b);
433 if (ret <= 0)
434 continue;
435 while ((ret = b->readline(b, &line) > 0))
436 handle_line(b, line);
440 static void usage(char *prog, FILE *out)
442 fprintf(out, "Usage: %s\n", prog);
443 fprintf(out, "\t-s\t --server <server>\n");
444 fprintf(out, "\t-p\t --port <port>\n");
445 fprintf(out, "\t-P\t --password <password>\n");
446 fprintf(out, "\t-c\t --channel <channel>\n");
447 fprintf(out, "\t-n\t --nick <nick>\n");
448 fprintf(out, "\t-S\t --ssl\n");
451 int main(int argc, char *argv[])
453 char *server = NULL, *port = NULL;
454 int use_ssl = 0, c, option_index;
455 SSL_CTX *ssl_ctx = NULL;
456 SSL *ssl = NULL;
457 struct option options[] = {
458 { "server", required_argument, NULL, 's' },
459 { "port", required_argument, NULL, 'p' },
460 { "password", required_argument, NULL, 'P' },
461 { "channel", required_argument, NULL, 'c' },
462 { "nick", required_argument, NULL, 'n' },
463 { "ssl", no_argument, NULL, 'S' },
464 { "help", no_argument, NULL, 'h' },
465 { 0, 0, 0, 0 }
467 ponify = 0;
468 while ((c = getopt_long(argc, argv, "s:p:P:c:n:Sh", options, &option_index)) >= 0) {
469 switch (c) {
470 case 'c': channels[nchannel++] = optarg; break;
471 case 's': server = optarg; break;
472 case 'p': port = optarg; break;
473 case 'P': password = optarg; break;
474 case 'n': wanted_nick = optarg; break;
475 case 'S': use_ssl = 1; break;
476 case 'h': usage(argv[0], stdout); return 0;
477 default: usage(argv[0], stderr); return -1;
479 if (optarg) {
480 ponify |= !!strcasestr(optarg, "pony");
481 ponify |= !!strcasestr(optarg, "bron");
484 crash_init();
485 if (!server || !port || !nchannel || !wanted_nick || !password) {
486 usage(argv[0], stderr);
487 return -1;
489 if (use_ssl) {
490 SSL_library_init();
491 ssl_ctx = SSL_CTX_new(SSLv3_client_method());
492 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
494 signal(SIGUSR1, reload_handler);
495 while (1) {
496 int fd = get_connection(server, port);
497 if (fd < 0) {
498 sleep(5);
499 continue;
501 if (use_ssl) {
502 int ssl_err;
503 ssl = SSL_new(ssl_ctx);
504 if (!ssl)
505 break;
506 ssl_err = SSL_set_fd(ssl, fd);
507 if (ssl_err <= 0)
508 goto retry;
510 ssl_err = SSL_get_error(ssl, SSL_connect(ssl));
511 if (ssl_err != SSL_ERROR_NONE) {
512 fprintf(stderr, "error code: %i\n", ssl_err);
513 goto retry;
515 struct bio b = {
516 .readline = bio_readline,
517 .writeline = ssl_writeline,
518 .poll = bio_poll,
519 .fill = ssl_fill,
520 .ssl = ssl,
521 .fd = fd
523 mainloop(&b, server);
524 SSL_shutdown(ssl);
525 free(b.priv);
526 retry:
527 SSL_free(ssl);
528 } else {
529 struct bio b = {
530 .readline = bio_readline,
531 .writeline = plain_writeline,
532 .poll = bio_poll,
533 .fill = plain_fill,
534 .fd = fd
536 mainloop(&b, server);
537 free(b.priv);
539 close(fd);
540 sleep(3);
542 if (use_ssl)
543 SSL_CTX_free(ssl_ctx);
544 crash_fini();
545 return 0;