ii.c 20 KB

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