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