ii.c - ii - irc it, simple FIFO based irc client
(HTM) git clone git://git.suckless.org/ii
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
ii.c (21988B)
---
1 /* See LICENSE file for license details. */
2 #include <sys/select.h>
3 #include <sys/socket.h>
4 #include <sys/stat.h>
5 #include <sys/types.h>
6 #include <sys/un.h>
7
8 #include <ctype.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <limits.h>
12 #include <netdb.h>
13 #include <netinet/in.h>
14 #include <pwd.h>
15 #include <signal.h>
16 #include <stdarg.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <time.h>
21 #include <unistd.h>
22 #include <tls.h>
23
24 char *argv0;
25
26 #include "arg.h"
27
28 #ifdef NEED_STRLCPY
29 size_t strlcpy(char *, const char *, size_t);
30 #endif /* NEED_STRLCPY */
31
32 #define IRC_CHANNEL_MAX 200
33 #define IRC_MSG_MAX 512 /* guaranteed to be <= than PIPE_BUF */
34 #define PING_TIMEOUT 600
35
36 enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST };
37
38 typedef struct Channel Channel;
39 struct Channel {
40 int fdin;
41 char name[IRC_CHANNEL_MAX]; /* channel name (normalized) */
42 char inpath[PATH_MAX]; /* input path */
43 char outpath[PATH_MAX]; /* output path */
44 Channel *next;
45 };
46
47 static Channel * channel_add(const char *);
48 static Channel * channel_find(const char *);
49 static Channel * channel_join(const char *);
50 static void channel_leave(Channel *);
51 static Channel * channel_new(const char *);
52 static void channel_normalize_name(char *);
53 static void channel_normalize_path(char *);
54 static int channel_open(Channel *);
55 static void channel_print(Channel *, const char *);
56 static int channel_reopen(Channel *);
57 static void channel_rm(Channel *);
58 static void cleanup(void);
59 static void create_dirtree(const char *);
60 static void create_filepath(char *, size_t, const char *, const char *, const char *);
61 static void die(const char *, ...);
62 static void ewritestr(int, const char *);
63 static void handle_channels_input(int, Channel *);
64 static void handle_server_output(int);
65 static int isnumeric(const char *);
66 static void loginkey(int, const char *);
67 static void loginuser(int, const char *, const char *);
68 static void proc_channels_input(int, Channel *, char *);
69 static void proc_channels_privmsg(int, Channel *, char *);
70 static void proc_server_cmd(int, char *);
71 static int read_line(int, char *, size_t, int);
72 static void run(int, const char *);
73 static void setup(void);
74 static void sighandler(int);
75 static int tcpopen(const char *, const char *, int);
76 static size_t tokenize(char **, size_t, char *, int);
77 static int udsopen(const char *);
78 static void usage(void);
79
80 static int isrunning = 1;
81 static time_t last_response = 0;
82 static Channel *channels = NULL;
83 static Channel *channelmaster = NULL;
84 static char nick[32]; /* active nickname at runtime */
85 static char _nick[32]; /* nickname at startup */
86 static char ircpath[PATH_MAX]; /* irc dir (-i) */
87 static char msg[IRC_MSG_MAX]; /* message buf used for communication */
88
89 static int usetls = 0;
90 static struct tls *tls = NULL;
91 static struct tls_config *tlscfg = NULL;
92
93 static void
94 die(const char *fmt, ...)
95 {
96 va_list ap;
97
98 va_start(ap, fmt);
99 vfprintf(stderr, fmt, ap);
100 va_end(ap);
101
102 cleanup();
103 exit(1);
104 }
105
106 static void
107 usage(void)
108 {
109 die("usage: %s [-46] -s host [-tv] [-p port | -u sockname] [-i ircdir]\n"
110 " [-n nickname] [-f fullname] [-k env_pass]\n", argv0);
111 }
112
113 static void
114 ewritestr(int fd, const char *s)
115 {
116 size_t len, off = 0;
117 int w = -1;
118
119 len = strlen(s);
120 for (off = 0; off < len; off += w) {
121 if (usetls) {
122 if ((w = tls_write(tls, s + off, len - off)) == -1)
123 break;
124 } else {
125 if ((w = write(fd, s + off, len - off)) == -1)
126 break;
127 }
128 }
129 if (w == -1)
130 die("%s: write: %s\n", argv0, strerror(errno));
131 }
132
133 /* creates directories bottom-up, if necessary */
134 static void
135 create_dirtree(const char *dir)
136 {
137 char tmp[PATH_MAX], *p;
138 struct stat st;
139 size_t len;
140
141 strlcpy(tmp, dir, sizeof(tmp));
142 len = strlen(tmp);
143 if (len > 0 && tmp[len - 1] == '/')
144 tmp[len - 1] = '\0';
145
146 if ((stat(tmp, &st) != -1) && S_ISDIR(st.st_mode))
147 return; /* dir exists */
148
149 for (p = tmp + 1; *p; p++) {
150 if (*p != '/')
151 continue;
152 *p = '\0';
153 mkdir(tmp, S_IRWXU);
154 *p = '/';
155 }
156 mkdir(tmp, S_IRWXU);
157 }
158
159 static void
160 channel_normalize_path(char *s)
161 {
162 for (; *s; s++) {
163 if (isalpha((unsigned char)*s))
164 *s = tolower((unsigned char)*s);
165 else if (!isdigit((unsigned char)*s) && !strchr(".#&+!-", *s))
166 *s = '_';
167 }
168 }
169
170 static void
171 channel_normalize_name(char *s)
172 {
173 char *p;
174
175 while (*s == '&' || *s == '#')
176 s++;
177 for (p = s; *s; s++) {
178 if (!strchr(" ,&#\x07", *s)) {
179 *p = *s;
180 p++;
181 }
182 }
183 *p = '\0';
184 }
185
186 static void
187 cleanup(void)
188 {
189 Channel *c, *tmp;
190
191 if (channelmaster)
192 channel_leave(channelmaster);
193
194 for (c = channels; c; c = tmp) {
195 tmp = c->next;
196 channel_leave(c);
197 }
198 }
199
200 static void
201 create_filepath(char *filepath, size_t len, const char *path,
202 const char *channel, const char *suffix)
203 {
204 int r;
205
206 if (channel[0]) {
207 r = snprintf(filepath, len, "%s/%s", path, channel);
208 if (r < 0 || (size_t)r >= len)
209 goto error;
210 create_dirtree(filepath);
211 r = snprintf(filepath, len, "%s/%s/%s", path, channel, suffix);
212 if (r < 0 || (size_t)r >= len)
213 goto error;
214 } else {
215 r = snprintf(filepath, len, "%s/%s", path, suffix);
216 if (r < 0 || (size_t)r >= len)
217 goto error;
218 }
219 return;
220
221 error:
222 die("%s: path to irc directory too long\n", argv0);
223 }
224
225 static int
226 channel_open(Channel *c)
227 {
228 int fd;
229 struct stat st;
230
231 /* make "in" fifo if it doesn't exist already. */
232 if (lstat(c->inpath, &st) != -1) {
233 if (!(st.st_mode & S_IFIFO))
234 return -1;
235 } else if (mkfifo(c->inpath, S_IRWXU)) {
236 return -1;
237 }
238 c->fdin = -1;
239 fd = open(c->inpath, O_RDONLY | O_NONBLOCK, 0);
240 if (fd == -1)
241 return -1;
242 c->fdin = fd;
243
244 return 0;
245 }
246
247 static int
248 channel_reopen(Channel *c)
249 {
250 if (c->fdin > 2) {
251 close(c->fdin);
252 c->fdin = -1;
253 }
254 return channel_open(c);
255 }
256
257 static Channel *
258 channel_new(const char *name)
259 {
260 Channel *c;
261 char channelpath[PATH_MAX];
262
263 strlcpy(channelpath, name, sizeof(channelpath));
264 channel_normalize_path(channelpath);
265
266 if (!(c = calloc(1, sizeof(Channel))))
267 die("%s: calloc: %s\n", argv0, strerror(errno));
268
269 strlcpy(c->name, name, sizeof(c->name));
270 channel_normalize_name(c->name);
271
272 create_filepath(c->inpath, sizeof(c->inpath), ircpath,
273 channelpath, "in");
274 create_filepath(c->outpath, sizeof(c->outpath), ircpath,
275 channelpath, "out");
276 return c;
277 }
278
279 static Channel *
280 channel_find(const char *name)
281 {
282 Channel *c;
283 char chan[IRC_CHANNEL_MAX];
284
285 strlcpy(chan, name, sizeof(chan));
286 channel_normalize_name(chan);
287 for (c = channels; c; c = c->next) {
288 if (!strcmp(chan, c->name))
289 return c; /* already handled */
290 }
291 return NULL;
292 }
293
294 static Channel *
295 channel_add(const char *name)
296 {
297 Channel *c;
298
299 c = channel_new(name);
300 if (channel_open(c) == -1) {
301 fprintf(stderr, "%s: cannot create channel: %s: %s\n",
302 argv0, name, strerror(errno));
303 free(c);
304 return NULL;
305 }
306 if (!channels) {
307 channels = c;
308 } else {
309 c->next = channels;
310 channels = c;
311 }
312 return c;
313 }
314
315 static Channel *
316 channel_join(const char *name)
317 {
318 Channel *c;
319
320 if (!(c = channel_find(name)))
321 c = channel_add(name);
322 return c;
323 }
324
325 static void
326 channel_rm(Channel *c)
327 {
328 Channel *p;
329
330 if (channels == c) {
331 channels = channels->next;
332 } else {
333 for (p = channels; p && p->next != c; p = p->next)
334 ;
335 if (p && p->next == c)
336 p->next = c->next;
337 }
338 free(c);
339 }
340
341 static void
342 channel_leave(Channel *c)
343 {
344 if (c->fdin > 2) {
345 close(c->fdin);
346 c->fdin = -1;
347 }
348 /* remove "in" file on leaving the channel */
349 unlink(c->inpath);
350 channel_rm(c);
351 }
352
353 static void
354 loginkey(int ircfd, const char *key)
355 {
356 snprintf(msg, sizeof(msg), "PASS %s\r\n", key);
357 ewritestr(ircfd, msg);
358 }
359
360 static void
361 loginuser(int ircfd, const char *host, const char *fullname)
362 {
363 snprintf(msg, sizeof(msg), "NICK %s\r\nUSER %s localhost %s :%s\r\n",
364 nick, nick, host, fullname);
365 puts(msg);
366 ewritestr(ircfd, msg);
367 }
368
369 static int
370 udsopen(const char *uds)
371 {
372 struct sockaddr_un sun;
373 size_t len;
374 int fd;
375
376 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
377 die("%s: socket: %s\n", argv0, strerror(errno));
378
379 sun.sun_family = AF_UNIX;
380 if (strlcpy(sun.sun_path, uds, sizeof(sun.sun_path)) >= sizeof(sun.sun_path))
381 die("%s: UNIX domain socket path truncation\n", argv0);
382
383 len = strlen(sun.sun_path) + 1 + sizeof(sun.sun_family);
384 if (connect(fd, (struct sockaddr *)&sun, len) == -1)
385 die("%s: connect: %s\n", argv0, strerror(errno));
386
387 return fd;
388 }
389
390 static int
391 tcpopen(const char *host, const char *service, int af)
392 {
393 struct addrinfo hints, *res = NULL, *rp;
394 int fd = -1, e;
395
396 memset(&hints, 0, sizeof(hints));
397 hints.ai_family = af;
398 hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */
399 hints.ai_socktype = SOCK_STREAM;
400
401 if ((e = getaddrinfo(host, service, &hints, &res)))
402 die("%s: getaddrinfo: %s\n", argv0, gai_strerror(e));
403
404 for (rp = res; rp; rp = rp->ai_next) {
405 fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
406 if (fd == -1)
407 continue;
408 if (connect(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
409 close(fd);
410 fd = -1;
411 continue;
412 }
413 break; /* success */
414 }
415 if (fd == -1)
416 die("%s: could not connect to %s:%s: %s\n",
417 argv0, host, service, strerror(errno));
418
419 freeaddrinfo(res);
420 return fd;
421 }
422
423 static int
424 isnumeric(const char *s)
425 {
426 errno = 0;
427 strtol(s, NULL, 10);
428 return errno == 0;
429 }
430
431 static size_t
432 tokenize(char **result, size_t reslen, char *str, int delim)
433 {
434 char *p = NULL, *n = NULL;
435 size_t i = 0;
436
437 for (n = str; *n == ' '; n++)
438 ;
439 p = n;
440 while (*n != '\0') {
441 if (i >= reslen)
442 return 0;
443 if (i > TOK_CHAN - TOK_CMD && result[0] && isnumeric(result[0]))
444 delim = ':'; /* workaround non-RFC compliant messages */
445 if (*n == delim) {
446 *n = '\0';
447 result[i++] = p;
448 p = ++n;
449 } else {
450 n++;
451 }
452 }
453 /* add last entry */
454 if (i < reslen && p < n && p && *p)
455 result[i++] = p;
456 return i; /* number of tokens */
457 }
458
459 static void
460 channel_print(Channel *c, const char *buf)
461 {
462 FILE *fp = NULL;
463 time_t t = time(NULL);
464
465 if (!(fp = fopen(c->outpath, "a")))
466 return;
467 fprintf(fp, "%lu %s\n", (unsigned long)t, buf);
468 fclose(fp);
469 }
470
471 static void
472 proc_channels_privmsg(int ircfd, Channel *c, char *buf)
473 {
474 snprintf(msg, sizeof(msg), "<%s> %s", nick, buf);
475 channel_print(c, msg);
476 snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", c->name, buf);
477 ewritestr(ircfd, msg);
478 }
479
480 static void
481 proc_channels_input(int ircfd, Channel *c, char *buf)
482 {
483 char *p = NULL;
484 size_t buflen;
485
486 if (buf[0] == '\0')
487 return;
488 if (buf[0] != '/') {
489 proc_channels_privmsg(ircfd, c, buf);
490 return;
491 }
492
493 msg[0] = '\0';
494 if ((buflen = strlen(buf)) < 2)
495 return;
496 if (buf[2] == ' ' || buf[2] == '\0') {
497 switch (buf[1]) {
498 case 'j': /* join */
499 if (buflen < 3)
500 return;
501 if ((p = strchr(&buf[3], ' '))) /* password parameter */
502 *p = '\0';
503 if ((buf[3] == '#') || (buf[3] == '&') || (buf[3] == '+') ||
504 (buf[3] == '!'))
505 {
506 /* password protected channel */
507 if (p)
508 snprintf(msg, sizeof(msg), "JOIN %s %s\r\n", &buf[3], p + 1);
509 else
510 snprintf(msg, sizeof(msg), "JOIN %s\r\n", &buf[3]);
511 channel_join(&buf[3]);
512 } else if (p) {
513 if ((c = channel_join(&buf[3])))
514 proc_channels_privmsg(ircfd, c, p + 1);
515 return;
516 }
517 break;
518 case 't': /* topic */
519 if (buflen >= 3)
520 snprintf(msg, sizeof(msg), "TOPIC %s :%s\r\n", c->name, &buf[3]);
521 break;
522 case 'a': /* away */
523 if (buflen >= 3) {
524 snprintf(msg, sizeof(msg), "-!- %s is away \"%s\"", nick, &buf[3]);
525 channel_print(c, msg);
526 }
527 if (buflen >= 3)
528 snprintf(msg, sizeof(msg), "AWAY :%s\r\n", &buf[3]);
529 else
530 snprintf(msg, sizeof(msg), "AWAY\r\n");
531 break;
532 case 'n': /* change nick */
533 if (buflen >= 3) {
534 strlcpy(_nick, &buf[3], sizeof(_nick));
535 snprintf(msg, sizeof(msg), "NICK %s\r\n", &buf[3]);
536 }
537 break;
538 case 'l': /* leave */
539 if (c == channelmaster)
540 return;
541 if (buflen >= 3)
542 snprintf(msg, sizeof(msg), "PART %s :%s\r\n", c->name, &buf[3]);
543 else
544 snprintf(msg, sizeof(msg),
545 "PART %s :leaving\r\n", c->name);
546 ewritestr(ircfd, msg);
547 channel_leave(c);
548 return;
549 break;
550 case 'q': /* quit */
551 if (buflen >= 3)
552 snprintf(msg, sizeof(msg), "QUIT :%s\r\n", &buf[3]);
553 else
554 snprintf(msg, sizeof(msg),
555 "QUIT %s\r\n", "bye");
556 ewritestr(ircfd, msg);
557 isrunning = 0;
558 return;
559 break;
560 default: /* raw IRC command */
561 snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]);
562 break;
563 }
564 } else {
565 /* raw IRC command */
566 snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]);
567 }
568 if (msg[0] != '\0')
569 ewritestr(ircfd, msg);
570 }
571
572 static void
573 proc_server_cmd(int fd, char *buf)
574 {
575 Channel *c;
576 const char *channel;
577 char *argv[TOK_LAST], *cmd = NULL, *p = NULL;
578 unsigned int i;
579
580 channel_print(channelmaster, buf);
581 if (!buf || buf[0] == '\0')
582 return;
583
584 /* clear tokens */
585 for (i = 0; i < TOK_LAST; i++)
586 argv[i] = NULL;
587
588 /* check prefix */
589 if (buf[0] == ':') {
590 if (!(p = strchr(buf, ' ')))
591 return;
592 *p = '\0';
593 for (++p; *p == ' '; p++)
594 ;
595 cmd = p;
596 argv[TOK_NICKSRV] = &buf[1];
597 if ((p = strchr(buf, '!'))) {
598 *p = '\0';
599 argv[TOK_USER] = ++p;
600 }
601 } else {
602 cmd = buf;
603 }
604
605 /* remove CRLFs */
606 for (p = cmd; p && *p != '\0'; p++) {
607 if (*p == '\r' || *p == '\n')
608 *p = '\0';
609 }
610
611 if ((p = strchr(cmd, ':'))) {
612 *p = '\0';
613 argv[TOK_TEXT] = ++p;
614 }
615
616 tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' ');
617
618 if (!argv[TOK_CMD] || !strcmp("PONG", argv[TOK_CMD])) {
619 snprintf(msg, sizeof(msg), "-!- %s %s", argv[TOK_CMD], argv[TOK_TEXT]);
620 channel_print(channelmaster, msg);
621 return;
622 } else if (!strcmp("PING", argv[TOK_CMD])) {
623 channel_print(channelmaster, "-!- sending PONG to PING request");
624 snprintf(msg, sizeof(msg), "PONG %s\r\n", argv[TOK_TEXT]);
625 channel_print(channelmaster, msg);
626 ewritestr(fd, msg);
627 return;
628 } else if (!argv[TOK_NICKSRV] || !argv[TOK_USER]) {
629 /* server command */
630 snprintf(msg, sizeof(msg), "%s%s",
631 argv[TOK_ARG] ? argv[TOK_ARG] : "",
632 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
633 channel_print(channelmaster, msg);
634 return; /* don't process further */
635 } else if (!strcmp("ERROR", argv[TOK_CMD]))
636 snprintf(msg, sizeof(msg), "-!- error %s",
637 argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown");
638 else if (!strcmp("JOIN", argv[TOK_CMD]) && (argv[TOK_CHAN] || argv[TOK_TEXT])) {
639 if (argv[TOK_TEXT])
640 argv[TOK_CHAN] = argv[TOK_TEXT];
641 snprintf(msg, sizeof(msg), "-!- %s(%s) has joined %s",
642 argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
643 } else if (!strcmp("PART", argv[TOK_CMD]) && argv[TOK_CHAN]) {
644 snprintf(msg, sizeof(msg), "-!- %s(%s) has left %s",
645 argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
646 /* if user itself leaves, don't write to channel (don't reopen channel). */
647 if (!strcmp(argv[TOK_NICKSRV], nick))
648 return;
649 } else if (!strcmp("MODE", argv[TOK_CMD])) {
650 snprintf(msg, sizeof(msg), "-!- %s changed mode/%s -> %s %s",
651 argv[TOK_NICKSRV],
652 argv[TOK_CHAN] ? argv[TOK_CHAN] : "",
653 argv[TOK_ARG] ? argv[TOK_ARG] : "",
654 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
655 } else if (!strcmp("QUIT", argv[TOK_CMD])) {
656 snprintf(msg, sizeof(msg), "-!- %s(%s) has quit \"%s\"",
657 argv[TOK_NICKSRV], argv[TOK_USER],
658 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
659 } else if (!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] &&
660 !strcmp(_nick, argv[TOK_TEXT])) {
661 strlcpy(nick, _nick, sizeof(nick));
662 snprintf(msg, sizeof(msg), "-!- changed nick to \"%s\"", nick);
663 channel_print(channelmaster, msg);
664 } else if (!strcmp("NICK", argv[TOK_CMD]) && argv[TOK_TEXT]) {
665 snprintf(msg, sizeof(msg), "-!- %s changed nick to %s",
666 argv[TOK_NICKSRV], argv[TOK_TEXT]);
667 } else if (!strcmp("TOPIC", argv[TOK_CMD])) {
668 snprintf(msg, sizeof(msg), "-!- %s changed topic to \"%s\"",
669 argv[TOK_NICKSRV],
670 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
671 } else if (!strcmp("KICK", argv[TOK_CMD]) && argv[TOK_ARG]) {
672 snprintf(msg, sizeof(msg), "-!- %s kicked %s (\"%s\")",
673 argv[TOK_NICKSRV], argv[TOK_ARG],
674 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
675 } else if (!strcmp("NOTICE", argv[TOK_CMD])) {
676 snprintf(msg, sizeof(msg), "-!- \"%s\"",
677 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
678 } else if (!strcmp("PRIVMSG", argv[TOK_CMD])) {
679 snprintf(msg, sizeof(msg), "<%s> %s", argv[TOK_NICKSRV],
680 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
681 } else {
682 snprintf(msg, sizeof(msg), "-!- unknown cmd %s", argv[TOK_TEXT]);
683 channel_print(channelmaster, msg);
684 return; /* can't read this message */
685 }
686 if (argv[TOK_CHAN] && !strcmp(argv[TOK_CHAN], nick))
687 channel = argv[TOK_NICKSRV];
688 else
689 channel = argv[TOK_CHAN];
690
691 if (!channel || channel[0] == '\0')
692 c = channelmaster;
693 else
694 c = channel_join(channel);
695 if (c)
696 channel_print(c, msg);
697 }
698
699 static int
700 read_line(int fd, char *buf, size_t bufsiz, int readtls)
701 {
702 size_t i = 0;
703 char c = '\0';
704 ssize_t sread = 0;
705
706 do {
707 if (usetls && readtls) {
708 sread = tls_read(tls, &c, sizeof(char));
709 /*
710 * Only try twice. If things go wrong, this is
711 * the best heuristics to fail.
712 */
713 if (sread == TLS_WANT_POLLIN)
714 sread = tls_read(tls, &c, sizeof(char));
715 if (sread == TLS_WANT_POLLIN)
716 sread = tls_read(tls, &c, sizeof(char));
717 if (sread != sizeof(char))
718 return -1;
719 } else {
720 if (read(fd, &c, sizeof(char)) != sizeof(char))
721 return -1;
722 }
723 buf[i++] = c;
724 } while (c != '\n' && i < bufsiz);
725 buf[i - 1] = '\0'; /* eliminates '\n' */
726 return 0;
727 }
728
729 static void
730 handle_channels_input(int ircfd, Channel *c)
731 {
732 /*
733 * Do not allow to read this fully, since commands will be
734 * prepended. It will result in too long lines sent to the
735 * server.
736 * TODO: Make this depend on the maximum metadata given by the
737 * server at the beginning of the connection.
738 */
739 char buf[IRC_MSG_MAX-64];
740
741 if (read_line(c->fdin, buf, sizeof(buf), 0) == -1) {
742 if (channel_reopen(c) == -1)
743 channel_rm(c);
744 return;
745 }
746 proc_channels_input(ircfd, c, buf);
747 }
748
749 static void
750 handle_server_output(int ircfd)
751 {
752 char buf[IRC_MSG_MAX];
753
754 if (read_line(ircfd, buf, sizeof(buf), 1) == -1)
755 die("%s: remote host closed connection: %s\n", argv0, strerror(errno));
756
757 fprintf(stdout, "%lu %s\n", (unsigned long)time(NULL), buf);
758 fflush(stdout);
759 proc_server_cmd(ircfd, buf);
760 }
761
762 static void
763 sighandler(int sig)
764 {
765 if (sig == SIGTERM || sig == SIGINT)
766 isrunning = 0;
767 /* Ignore SIGPIPE */
768 }
769
770 static void
771 setup(void)
772 {
773 struct sigaction sa;
774
775 memset(&sa, 0, sizeof(sa));
776 sa.sa_handler = sighandler;
777 sigaction(SIGTERM, &sa, NULL);
778 sigaction(SIGINT, &sa, NULL);
779 sigaction(SIGPIPE, &sa, NULL);
780 }
781
782 static void
783 run(int ircfd, const char *host)
784 {
785 Channel *c, *tmp;
786 fd_set rdset;
787 struct timeval tv;
788 char ping_msg[IRC_MSG_MAX];
789 int r, maxfd;
790
791 snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host);
792 while (isrunning) {
793 maxfd = ircfd;
794 FD_ZERO(&rdset);
795 FD_SET(ircfd, &rdset);
796 for (c = channels; c; c = c->next) {
797 if (c->fdin > maxfd)
798 maxfd = c->fdin;
799 FD_SET(c->fdin, &rdset);
800 }
801 memset(&tv, 0, sizeof(tv));
802 tv.tv_sec = 120;
803 r = select(maxfd + 1, &rdset, 0, 0, &tv);
804 if (r < 0) {
805 if (errno == EINTR)
806 continue;
807 die("%s: select: %s\n", argv0, strerror(errno));
808 } else if (r == 0) {
809 if (time(NULL) - last_response >= PING_TIMEOUT) {
810 channel_print(channelmaster, "-!- ii shutting down: ping timeout");
811 cleanup();
812 exit(2); /* status code 2 for timeout */
813 }
814 ewritestr(ircfd, ping_msg);
815 continue;
816 }
817 if (FD_ISSET(ircfd, &rdset)) {
818 handle_server_output(ircfd);
819 last_response = time(NULL);
820 }
821 for (c = channels; c; c = tmp) {
822 tmp = c->next;
823 if (FD_ISSET(c->fdin, &rdset))
824 handle_channels_input(ircfd, c);
825 }
826 }
827 }
828
829 int
830 main(int argc, char *argv[])
831 {
832 struct passwd *spw;
833 const char *key = NULL, *fullname = NULL, *host = "";
834 const char *uds = NULL, *service = "6667";
835 char prefix[PATH_MAX];
836 int ircfd, r, af = AF_UNSPEC, doverifytls = 1;
837
838 /* use nickname and home dir of user by default */
839 if (!(spw = getpwuid(getuid())))
840 die("%s: getpwuid: %s\n", argv0, strerror(errno));
841
842 strlcpy(nick, spw->pw_name, sizeof(nick));
843 snprintf(prefix, sizeof(prefix), "%s/irc", spw->pw_dir);
844
845 ARGBEGIN {
846 case '4':
847 af = AF_INET;
848 break;
849 case '6':
850 af = AF_INET6;
851 break;
852 case 'f':
853 fullname = EARGF(usage());
854 break;
855 case 'i':
856 strlcpy(prefix, EARGF(usage()), sizeof(prefix));
857 break;
858 case 'k':
859 key = getenv(EARGF(usage()));
860 break;
861 case 'n':
862 strlcpy(nick, EARGF(usage()), sizeof(nick));
863 break;
864 case 'p':
865 service = EARGF(usage());
866 break;
867 case 's':
868 host = EARGF(usage());
869 break;
870 case 't':
871 usetls = 1;
872 break;
873 case 'v':
874 doverifytls = 0;
875 break;
876 case 'u':
877 uds = EARGF(usage());
878 break;
879 default:
880 usage();
881 break;
882 } ARGEND
883
884 if (!*host)
885 usage();
886
887 if (uds)
888 ircfd = udsopen(uds);
889 else
890 ircfd = tcpopen(host, service, af);
891
892 if (usetls) {
893 if (tls_init() < 0)
894 die("%s: tls_init: %s\n", strerror(errno));
895 if ((tlscfg = tls_config_new()) == NULL)
896 die("%s: tls_config_new: %s\n", strerror(errno));
897 if (!doverifytls)
898 tls_config_insecure_noverifycert(tlscfg);
899 if (!(tls = tls_client()))
900 die("%s: tls_client: %s\n", tls_error(tls));
901 if (tls_configure(tls, tlscfg))
902 die("%s: tls_configure: %s\n", tls_error(tls));
903 if (tls_connect_socket(tls, ircfd, host) < 0)
904 die("%s: tls_connect_socket: %s\n", tls_error(tls));
905 }
906
907 r = snprintf(ircpath, sizeof(ircpath), "%s/%s", prefix, host);
908 if (r < 0 || (size_t)r >= sizeof(ircpath))
909 die("%s: path to irc directory too long\n", argv0);
910 create_dirtree(ircpath);
911
912 #ifdef __OpenBSD__
913 if (unveil(ircpath, "rwc") == -1)
914 die("%s: unveil: %s: %s\n", argv0, ircpath, strerror(errno));
915 if (pledge("stdio rpath wpath cpath dpath", NULL) == -1)
916 die("%s: pledge: %s\n", argv0, strerror(errno));
917 #endif
918
919 channelmaster = channel_add(""); /* master channel */
920 if (key)
921 loginkey(ircfd, key);
922 loginuser(ircfd, host, fullname && *fullname ? fullname : nick);
923 setup();
924 run(ircfd, host);
925 cleanup();
926
927 if (tls)
928 tls_close(tls);
929
930 return 0;
931 }