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