ii.c - ii - irc it, simple FIFO based irc client
 (HTM) git clone git://git.suckless.org/ii
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       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 }