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