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