#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dump.h" #define MAX_ARGS 50 #define MAX_SUBPROCS 50 #define max(a,b) ((a)>(b)?(a):(b)) bool running = true; char *handler = NULL; char *msgdir = NULL; struct timeval output_interval = {0}; void maybe_setenv(char *key, char *val) { if (val) { setenv(key, val, 1); } } void irc_filter(const char *str) { char buf[4096]; char *line = buf; char *parts[20] = {0}; int nparts; char snick[20]; char *cmd; char *text = NULL; char *prefix = NULL; char *sender = NULL; char *forum = NULL; int i; strncpy(buf, str, sizeof buf); /* Tokenize IRC line */ nparts = 0; if (':' == *line) { prefix = line + 1; } else { parts[nparts++] = line; } while (*line) { if (' ' == *line) { *line++ = '\0'; if (':' == *line) { text = line+1; break; } else { parts[nparts++] = line; } } else { line += 1; } } /* Strip trailing carriage return */ while (*line) line += 1; if ('\r' == *(line-1)) *(line-1) = '\0'; /* Set command, converting to upper case */ cmd = parts[0]; for (i = 0; cmd[i]; i += 1) { cmd[i] = toupper(cmd[i]); } /* Extract prefix nickname */ for (i = 0; prefix && (prefix[i] != '!'); i += 1) { if (i == sizeof(snick) - 1) { i = 0; break; } snick[i] = prefix[i]; } snick[i] = '\0'; if (i) { sender = snick; } /* Determine forum */ if ((0 == strcmp(cmd, "PRIVMSG")) || (0 == strcmp(cmd, "NOTICE"))) { /* :neale!user@127.0.0.1 PRIVMSG #hydra :foo */ switch (parts[1][0]) { case '#': case '&': case '+': case '!': forum = parts[1]; break; default: forum = snick; break; } } else if ((0 == strcmp(cmd, "PART")) || (0 == strcmp(cmd, "MODE")) || (0 == strcmp(cmd, "TOPIC")) || (0 == strcmp(cmd, "KICK"))) { forum = parts[1]; } else if (0 == strcmp(cmd, "JOIN")) { if (0 == nparts) { forum = text; text = NULL; } else { forum = parts[1]; } } else if (0 == strcmp(cmd, "INVITE")) { forum = text?text:parts[2]; text = NULL; } else if (0 == strcmp(cmd, "NICK")) { sender = parts[1]; forum = sender; } else if (0 == strcmp(cmd, "PING")) { printf("PONG :%s\r\n", text); fflush(stdout); } { int _argc; char *_argv[MAX_ARGS + 1]; maybe_setenv("handler", handler); maybe_setenv("prefix", prefix); maybe_setenv("command", cmd); maybe_setenv("sender", sender); maybe_setenv("forum", forum); maybe_setenv("text", text); _argc = 0; _argv[_argc++] = handler; for (i = 1; (i < nparts) && (_argc < MAX_ARGS); i += 1) { _argv[_argc++] = parts[i]; } _argv[_argc] = NULL; execvp(handler, _argv); perror(handler); } } void unblock(int fd) { int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); } FILE *subprocs[MAX_SUBPROCS] = { 0 }; void sigchld(int signum) { while (0 < waitpid(-1, NULL, WNOHANG)); } void dispatch(char *text) { int subout[2]; int i; for (i = 0; i < MAX_SUBPROCS; i += 1) { if (NULL == subprocs[i]) { break; } } if (MAX_SUBPROCS == i) { fprintf(stderr, "warning: dropping message (too many children)\n"); return; } if (-1 == pipe(subout)) { perror("pipe"); return; } subprocs[i] = fdopen(subout[0], "r"); if (! subprocs[i]) { close(subout[0]); close(subout[1]); perror("fdopen"); return; } if (0 == fork()) { /* * Child */ int null; if ((-1 == (null = open("/dev/null", O_RDONLY))) || (-1 == dup2(null, 0)) || (-1 == dup2(subout[1], 1))) { perror("fd setup"); exit(EX_OSERR); } /* * We'll be a good citizen and only close file descriptors we opened. */ close(null); close(subout[1]); for (i = 0; i < MAX_SUBPROCS; i += 1) { if (subprocs[i]) { fclose(subprocs[i]); } } irc_filter(text); exit(0); } unblock(subout[0]); close(subout[1]); } void delay_output() { struct timeval now; struct timeval diff; static struct timeval output_last = { 0 }; gettimeofday(&now, NULL); timersub(&now, &output_last, &diff); if (timercmp(&diff, &output_interval, <)) { struct timeval delay; struct timespec ts; int ret; timersub(&output_interval, &diff, &delay); ts.tv_sec = (time_t) delay.tv_sec; ts.tv_nsec = (long) (delay.tv_usec * 1000); do { ret = nanosleep(&ts, &ts); } while ((-1 == ret) && (EINTR == errno)); gettimeofday(&output_last, NULL); } else { output_last = now; } } /** Writes all of buf to stdout, possibly blocking. */ void output(char *buf) { if (timerisset(&output_interval)) { delay_output(); } puts(buf); } void handle_file(FILE *f, void (*func) (char *)) { char line[2048]; size_t linelen; // Read a line. If we didn't have enough space, drop it. while (fgets(line, sizeof line, f)) { linelen = strlen(line); if (line[linelen-1] != '\n') { fprintf(stderr, "warning: dropping %u bytes (no trailing newline)\n", (unsigned int)linelen); } else { line[linelen-1] = '\0'; func(line); } } } void handle_input() { handle_file(stdin, dispatch); if (feof(stdin)) { running = false; } } void handle_subproc(FILE *s) { handle_file(s, output); } void loop() { int i; int ret; int nfds = 0; fd_set rfds; static time_t last_pulse = 0; time_t now; // Look for messages in msgdir if (msgdir) { DIR *d = opendir(msgdir); while (d) { struct dirent *ent = readdir(d); if (! ent) { break; } if (ent->d_type == DT_REG) { char fn[PATH_MAX]; FILE *f; snprintf(fn, sizeof fn, "%s/%s", msgdir, ent->d_name); f = fopen(fn, "r"); if (f) { // This one is blocking handle_subproc(f); fclose(f); remove(fn); } } } if (d) { closedir(d); } } // Check subprocs for input FD_ZERO(&rfds); FD_SET(0, &rfds); for (i = 0; i < MAX_SUBPROCS; i += 1) { if (subprocs[i]) { int fd = fileno(subprocs[i]); FD_SET(fd, &rfds); nfds = max(nfds, fd); } } do { struct timeval timeout = {1, 0}; ret = select(nfds + 1, &rfds, NULL, NULL, &timeout); } while ((-1 == ret) && (EINTR == errno)); if (-1 == ret) { perror("select"); exit(EX_IOERR); } if (FD_ISSET(0, &rfds)) { handle_input(); } for (i = 0; i < MAX_SUBPROCS; i += 1) { FILE *f = subprocs[i]; if (f && FD_ISSET(fileno(f), &rfds)) { handle_subproc(f); if (feof(f)) { fclose(f); subprocs[i] = NULL; } } } // Heartbeat now = time(NULL); if (now - last_pulse > 5) { last_pulse = now; dispatch("PULSE"); } } void usage(char *self) { fprintf(stderr, "Usage: %s [OPTIONS] HANDLER\n", self); fprintf(stderr, "\n"); fprintf(stderr, "-h Display help.\n"); fprintf(stderr, "-d DIR Also dispatch messages from DIR, one per file.\n"); fprintf(stderr, "-i INTERVAL Wait at least INTERVAL microseconds between\n"); fprintf(stderr, " sending each line.\n"); } int main(int argc, char *argv[]) { /* * Parse command line */ while (!handler) { switch (getopt(argc, argv, "hd:i:")) { case -1: if (optind >= argc) { fprintf(stderr, "error: must specify event handler.\n"); usage(argv[0]); return EX_USAGE; } handler = argv[optind]; break; case 'd': msgdir = optarg; break; case 'i': { char *end; long long int interval; interval = strtoll(optarg, &end, 10); if (*end) { fprintf(stderr, "error: not an integer number: %s\n", optarg); return EX_USAGE; } output_interval.tv_sec = interval / 1000000; output_interval.tv_usec = interval % 1000000; } break; case 'h': usage(argv[0]); return 0; default: fprintf(stderr, "error: unknown option.\n"); usage(argv[0]); return EX_USAGE; } } /* * tcpclient uses fds 6 and 7. If these aren't open, we keep the * original fds 0 and 1. */ if (-1 != dup2(6, 0)) { close(6); } if (-1 != dup2(7, 1)) { close(7); } unblock(0); setvbuf(stdout, NULL, _IOLBF, 0); signal(SIGCHLD, sigchld); // Let handler know we're starting up dispatch("_INIT_"); while (running) { loop(); } // Let handler know we're shutting down dispatch("_END_"); return 0; }