ii.c - ii - FIFO and filesystem based IRC client | |
git clone git://git.codemadness.org/ii | |
Log | |
Files | |
Refs | |
README | |
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, … | |
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 cha… | |
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(".#&+!-"… | |
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, s… | |
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\… | |
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(s… | |
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_proto… | |
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(res… | |
417 delim = ':'; /* workaround non-RFC compliant mes… | |
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 para… | |
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… | |
482 else | |
483 snprintf(msg, sizeof(msg), "JOIN… | |
484 channel_join(&buf[3]); | |
485 } else if (p) { | |
486 if ((c = channel_join(&buf[3]))) | |
487 proc_channels_privmsg(ircfd, c, … | |
488 return; | |
489 } | |
490 break; | |
491 case 't': /* topic */ | |
492 if (buflen >= 3) | |
493 snprintf(msg, sizeof(msg), "TOPIC %s :%s… | |
494 break; | |
495 case 'a': /* away */ | |
496 if (buflen >= 3) { | |
497 snprintf(msg, sizeof(msg), "-!- %s is aw… | |
498 channel_print(c, msg); | |
499 } | |
500 if (buflen >= 3) | |
501 snprintf(msg, sizeof(msg), "AWAY :%s\r\n… | |
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"… | |
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\… | |
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… | |
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] : "unkno… | |
606 else if (!strcmp("JOIN", argv[TOK_CMD]) && (argv[TOK_CHAN] || ar… | |
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[… | |
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[… | |
614 /* if user itself leaves, don't write to channel (don't … | |
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… | |
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\"",… | |
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, st… | |
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 shu… | |
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 } |