ii

git clone https://orangeshoelaces.net/git/ii.git

/ii.c

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