#include #include #include #include char help[] = "cmd explanation/example\n" "--------------------------------------------\n" "/m privmsg #chan/nick message\n" "/M mode #chan +nt\n" "/j join #chan\n" "/p part #chan\n" "/q send parameters raw to the server\n" "/l list #chan\n" "/n nick newnick\n" "/N notice #chan/nick message\n" "/t set victim\n" "/T topic #chan newtopic\n" "/W whois nick\n" "/w who nick (a shorter whois)\n"; #define NPAR 14 enum state { Cmd, Prefix, Middle, Trail, Ok }; typedef struct handler Handler; struct handler { char *cmd; int (*fun)(int fd, char *pre, char *cmd, char *par[]); }; QLock lck; int server_in; int server_out; int scr; char *victim; char *nick; int inacme; /* running in acme? */ int linewidth; /* terminal width in # of characters */ int replay; /* just print the log ma'am */ int quiet; /* omit chatty irc metainfo */ void setwintitle(char *chan); int rtcs(int fd, char *cset); int wtcs(int fd, char *cset); int follow(int fd); void getwidth(void); /* establish the width of the terminal, from mc.c */ int pmsg(int fd, char *pre, char *cmd, char *par[]); int ntc(int fd, char *pre, char *cmd, char *par[]); int generic(int fd, char *pre, char *cmd, char *par[]); int misc(int fd, char *pre, char *cmd, char *par[]); int numeric(int fd, char *pre, char *cmd, char *par[]); Handler handlers[] = { {"PRIVMSG", pmsg}, {"NOTICE", ntc}, {"JOIN", misc}, {"PART", misc}, {"MODE", misc}, {"QUIT", misc}, {"TOPIC", misc}, {"332", numeric}, {"333", numeric}, {"352", numeric}, {"315", numeric}, {"311", numeric}, {"319", numeric}, {"312", numeric}, {"320", numeric}, {"317", numeric}, {"318", numeric}, {nil, nil} }; int srvparse(char *line, char **pre, char **cmd, char *par[], int npar); int usrparse(char *ln, char *cmd, char *par[], int npar); void usage(void) { char usage[] = "usage: irc [-q] [-c charset] [-t victim] [-b lines] [-r file] [/srv/irc [/tmp/irc]]\n"; write(1, usage, sizeof(usage)-1); exits("usage"); } void setwintitle(char *chan) { int fd; if ((fd = open("/dev/label", OWRITE)) >= 0) { fprint(fd, "%s", chan); close(fd); } if ((fd = open("/dev/acme/ctl", OWRITE)) >= 0) { fprint(fd, "name -IRC/%s\n", chan); close(fd); inacme = 1; } } /* try to find out whether we're running in acme's win -e */ int testacme(void) { return access("/dev/acme", OREAD) >= 0 ? 1 : 0; } void usrin(void) { char *line, *p; char *par[2]; char cmd; int n, i; Biobuf kbd; Binit(&kbd, 0, OREAD); while ((line = Brdstr(&kbd, '\n', 0)) != nil) { n = utflen(line); if(!inacme) { p = malloc(n); for (i = 0; i < n; ++i) p[i] = '\b'; write(scr, p, i); free(p); } qlock(&lck); if (!usrparse(line, &cmd, par, 2)) { switch(cmd) { case 'q': /* quote, just send the params ... */ if(par[0]) { fprint(server_out, "%s %s\r\n", par[0], par[1] ? par[1] : ""); } else { fprint(scr, "/q %s %s: not enough arguments\n", par[0], par[1]); } break; case 'M': if(par[0] && par[1]) { fprint(server_out, "MODE %s %s\r\n", par[0], par[1]); } else { fprint(scr, "/M %s %s: not enough arguments\n", par[0], par[1]); } break; case 'm': if(par[0] && par[1]) { fprint(server_out, "PRIVMSG %s :%s\r\n", par[0], par[1]); } else { fprint(scr, "/m %s %s: not enough arguments\n", par[0], par[1]); } break; case 't': if(par[0] != nil) { free(victim); victim = strdup(par[0]); setwintitle(par[0]); } fprint(scr, "*** default victim set to '%s'\n", par[0]); break; case 'T': if(par[0] == nil) fprint(server_out, "TOPIC %s\r\n", victim); else if(par[1] == nil) fprint(server_out, "TOPIC %s\r\n", par[0]); else fprint(server_out, "TOPIC %s :%s\r\n", par[0], par[1]); break; case 'j': fprint(server_out, "JOIN %s\r\n", par[0] == nil ? victim : par[0]); break; case 'p': fprint(server_out, "PART %s\r\n", par[0] == nil ? victim : par[0]); break; case 'n': if(par[0] != nil) { fprint(server_out, "NICK %s\r\n", par[0]); free(nick); nick = strdup(par[0]); } else { fprint(scr, "%s", help); } break; case 'N': if(par[1] != nil) fprint(server_out, "NOTICE %s :%s\r\n", par[0] == nil ? victim : par[0], par[1]); break; case 'W': fprint(server_out, "WHOIS %s %s\r\n", par[0] == nil ? victim : par[0], par[0]); case 'w': fprint(server_out, "WHO %s\r\n", par[0] == nil ? victim : par[0]); break; case 'l': fprint(server_out, "LIST %s\r\n", par[0] == nil ? victim : par[0]); break; case 'L': fprint(server_out, "NAMES %s\r\n", par[0] == nil ? victim : par[0]); break; case 'f': break; case 'h': case 'H': fprint(scr, "%s", help); break; } } else { fprint(scr, "%s", help); } qunlock(&lck); free(line); } exits(0); } void srvin(void) { char *line; char *pre, *cmd, *par[NPAR]; Biobuf srv; Binit(&srv, server_in, OREAD); while ((line = Brdstr(&srv, '\n', 0)) != nil) { if (!srvparse(line, &pre, &cmd, par, NPAR)) { Handler *hp = handlers; qlock(&lck); while (hp->cmd != nil) { if (!strcmp(hp->cmd, cmd)) { hp->fun(server_out, pre, cmd, par); break; } ++hp; } if (hp->cmd == nil) generic(server_out, pre, cmd, par); qunlock(&lck); } free(line); } } void replayfile(void) { char *line; char *pre, *cmd, *par[NPAR]; Biobuf srv; Binit(&srv, server_in, OREAD); while ((line = Brdstr(&srv, '\n', 0)) != nil) { if (!srvparse(line, &pre, &cmd, par, NPAR)) { Handler *hp = handlers; qlock(&lck); while (hp->cmd != nil) { if (!strcmp(hp->cmd, cmd)) { hp->fun(server_out, pre, cmd, par); break; } ++hp; } if (hp->cmd == nil) generic(server_out, pre, cmd, par); qunlock(&lck); } free(line); } } /* * display the last N lines from the conversation * if we have a default target only the conversation with * that target will be shown */ void seekback(int fd, int lines) { Biobuf srv; int found = 0, off; char c, *line; if(lines < 0) return; Binit(&srv, fd, OREAD); Bseek(&srv, -2, 2); while(((off = Boffset(&srv)) > 0) && found < lines) { c = Bgetc(&srv); Bungetc(&srv); if(c == '\n') { Bseek(&srv, 1, 1); line = Brdstr(&srv, '\n', '\0'); if(victim) { if(cistrstr(line, victim)) found++; } else { found++; } free(line); } Bseek(&srv, off-1, 0); } Bterm(&srv); } void main(int argc, char *argv[]) { char *charset = nil; char buf[32], buf2[32], *username, *out = nil, *in = nil; char *arg; int sb = 10; /* how many lines are we displaying initially */ int uipid; ARGBEGIN { case 't': victim = strdup(EARGF(usage())); break; case 'b': arg = ARGF(); if(arg != nil && arg[0] != '-') sb = atoi(arg); else sb = 0; /* show all text */ break; case 'c': charset = EARGF(usage()); break; case 'r': replay = 1; sb = 0; break; case 'q': quiet = 1; break; default: usage(); } ARGEND; switch(argc) { case 0: break; case 1: if(replay) in = argv[0]; else out = argv[0]; break; case 2: out = argv[0]; in = argv[1]; break; default: usage(); } username = getuser(); if(out == nil) { snprint(buf, sizeof buf, "/srv/%sirc", username); out = buf; } if(in == nil) { snprint(buf2, sizeof buf2, "/tmp/%sirc", username); in = buf2; } if(!replay && (server_out = open(out, OWRITE)) < 0) sysfatal("open write: %s %r", out); if ((server_in = open(in, OREAD)) < 0) sysfatal("open read: %s %r", in); inacme = testacme(); getwidth(); if(sb) seekback(server_in, sb); while(read(server_in, buf, 1) > 0) if(*buf == '\n') break; if(victim && cistrncmp(victim, "MSGS", 4)){ setwintitle(victim); fprint(server_out, "JOIN %s\r\n", victim); } scr = 1; server_in = follow(server_in); if (charset != nil && strcmp(charset, "utf")) { server_out = wtcs(server_out, charset); server_in = rtcs(server_in, charset); } if(replay) { replayfile(); } else { if ((uipid = rfork(RFPROC|RFFDG|RFMEM)) == 0) srvin(); usrin(); postnote(PNPROC, uipid, "kill"); while (waitpid() != uipid); } exits(0); } int wtcs(int fd, char *cset) { int totcs[2]; int pid; pipe(totcs); pid = fork(); if (pid == 0) { dup(totcs[0], 0); dup(fd, 1); close(totcs[1]); execl("/bin/tcs", "tcs", "-f", "utf", "-t", cset, nil); exits("execfailure"); } close(totcs[0]); return totcs[1]; } int rtcs(int fd, char *cset) { int fromtcs[2]; int pid; pipe(fromtcs); pid = fork(); if (pid == 0){ dup(fromtcs[1], 1); dup(fd, 0); close(fromtcs[0]); execl("/bin/tcs", "tcs", "-f", cset, "-t", "utf", nil); exits("execfailure"); } close(fromtcs[1]); return fromtcs[0]; } int follow(int fd) { int p[2], pid; long n; char buf[1024]; Dir *dp; pipe(p); pid = fork(); if (pid == 0){ dup(p[1], 1); dup(fd, 0); close(p[0]); for(;;){ while((n = read(0, buf, sizeof(buf))) > 0) write(1, buf, n); sleep(1000); dp = dirfstat(0); free(dp); } } close(p[1]); return p[0]; } char * prenick(char *p) { char *n = p; if (p != nil) { while (*p != '\0' && *p != '!') ++p; if (*p != '!') n = nil; *p = '\0'; } return n; } int pmsg(int, char *pre, char *, char *par[]) { int n = 0; char buf[8192]; char *c, *tc; /* * if sent to victim, or comes from victim to non-channel, print. * otherwise bail out. */ pre = prenick(pre); if(victim) { if((cistrncmp(victim, "MSGS", 4) == 0) && *par[0] != '#') { /* catch-all for messages, fall through */ } else if(cistrcmp(par[0], victim)) if(!pre || cistrcmp(pre, victim) || *par[0] == '#') return 0; } if(!pre) sprint(buf, "(%s) ⇐ %s\n", par[0], par[1]); else if(*par[0] != '#') sprint(buf, "(%s) ⇒ %s\n", pre, par[1]); else sprint(buf, "(%s) %s → %s\n", par[0], pre, par[1]); c = buf; again: if(strlen(c) >= linewidth) { for(tc = c + linewidth; tc > c; tc--) { switch(*tc) { case ' ': *tc = '\0'; n += fprint(scr, "%s\n", c); c = tc+1; goto again; break; default: break; } } } n += fprint(scr, "%s", c); return n; } int ntc(int, char *pre, char *, char *par[]) { int n; /* * if sent to victim, or comes from victim to non-channel, print. * otherwise bail out. */ pre = prenick(pre); if(victim && cistrcmp(par[0], victim)) if(!pre || cistrcmp(pre, victim) || *par[0] == '#') return 0; if(!pre) n = fprint(scr, "[%s] ⇐\t%s\n", par[0], par[1]); else if(*par[0] != '#') n = fprint(scr, "[%s] ⇒\t%s\n", pre, par[1]); else n = fprint(scr, "[%s] %s →\t%s\n", par[0], pre, par[1]); return n; } int generic(int, char *pre, char *cmd, char *par[]) { int i = 0, r; char *nick = prenick(pre); /* * don't print crud on screens with victim set */ if(victim) return 0; if (nick != nil) r = fprint(scr, "%s (%s)\t", cmd, nick); else r = fprint(scr, "%s (%s)\t", cmd, par[i++]); for (; par[i] != nil; ++i) r += fprint(scr, " %s", par[i]); r += fprint(scr, "\n"); return r; } int misc(int, char *pre, char *cmd, char *par[]) { int i = 0, r = 0; char *nick = prenick(pre); if(cistrcmp(cmd,"QUIT")) if(victim && par[0] && cistrcmp(par[0], victim)) return 0; if(!quiet) { if (nick != nil) r = fprint(scr, "%s (%s)\t", cmd, nick); else r = fprint(scr, "%s %s\t", cmd, par[i++]); for (; par[i] != nil; ++i) r += fprint(scr, " %s", par[i]); r += fprint(scr, "\n"); } return r; } int numeric(int, char *pre, char *cmd, char *par[]) { int i = 0, r; char *nick = prenick(pre); if(victim && par[1] && cistrcmp(par[1], victim)) return 0; if (nick != nil) r = fprint(scr, "%s (%s)\t", cmd, nick); else r = fprint(scr, "%s (%s)\t", cmd, par[i++]); for (; par[i] != nil; ++i) r += fprint(scr, " %s", par[i]); r += fprint(scr, "\n"); return r; } int usrparse(char *ln, char *cmd, char *par[], int npar) { enum state st = Cmd; int i; for(i = 0; i < npar; i++) par[i] = nil; if (ln[0] == '/' && npar >= 2) { *cmd = ln[1]; for (i = 1; ln[i] != '\0'; ++i) { switch(st) { case Cmd: if (ln[i] == ' ') { ln[i] = '\0'; par[0] = ln+i+1; st = Middle; } else if(ln[i] == '\n') { /* enable commands with no arguments */ ln[i] = '\0'; par[0] = nil; par[1] = nil; st = Ok; } break; case Middle: if (ln[i] == '\r' || ln[i] == '\n') { ln[i] = '\0'; st = Ok; } if (ln[i] == ' ') { ln[i] = '\0'; par[1] = ln+i+1; st = Trail; } break; case Trail: if (ln[i] == '\r' || ln[i] == '\n') { ln[i] = '\0'; st = Ok; } break; case Ok: if (ln[i] == '\r' || ln[i] == '\n') ln[i] = '\0'; break; } } } else { /* send line to victim by default */ st = Ok; *cmd = 'm'; for (i = 0; ln[i] != '\0'; ++i) if (ln[i] == '\r' || ln[i] == '\n') ln[i] = '\0'; par[0] = victim; par[1] = ln; } return st == Ok ? 0 : 1; } int srvparse(char *line, char **pre, char **cmd, char *par[], int npar) { int i; char *p; enum state st = Cmd; *pre = *cmd = nil; for (*cmd = p = line, i = 0; *p != '\0'; ++p) { switch (st) { case Cmd: if (*p == ':') { *p = '\0'; *pre = p + 1; st = Prefix; } else if (*p == ' ') { *p = '\0'; par[i] = p + 1; st = Middle; } break; case Prefix: if (*p == ' ') { *p = '\0'; *cmd = p + 1; st = Cmd; } break; case Middle: if (*p == '\r' || *p == '\n') { *p = '\0'; st = Ok; } else if (*p == ':') { *p = '\0'; par[i] = p + 1; st = Trail; } else if (*p == ' ') { *p = '\0'; i = (i + 1) % npar; par[i] = p + 1; st = Middle; } break; case Trail: if (*p == '\r' || *p == '\n') { *p = '\0'; st = Ok; } break; case Ok: *p = '\0'; break; } } par[(i + 1) % npar] = nil; return st == Ok ? 0 : 1; } void getwidth(void) { Font *font; int n, fd, mintab; char buf[128], *f[10], *p; if(inacme){ if((fd = open("/dev/acme/ctl", OREAD)) < 0) return; n = read(fd, buf, sizeof buf-1); close(fd); if(n <= 0) return; buf[n] = 0; n = tokenize(buf, f, nelem(f)); if(n < 7) return; if((font = openfont(nil, f[6])) == nil) return; mintab = stringwidth(font, "0"); linewidth = atoi(f[5]); linewidth = linewidth/mintab; return; } if((p = getenv("font")) == nil) return; if((font = openfont(nil, p)) == nil) return; if((fd = open("/dev/window", OREAD)) < 0) return; n = read(fd, buf, 5*12); close(fd); if(n < 5*12) return; buf[n] = 0; /* window stucture: 4 bit left edge 1 bit gap 12 bit scrollbar 4 bit gap text 4 bit right edge */ linewidth = atoi(buf+3*12) - atoi(buf+1*12) - (4+1+12+4+4); mintab = stringwidth(font, "0"); linewidth = linewidth/mintab; }