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 }