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