1#include <u.h>2#include <libc.h>3#include <bio.h>4#include <draw.h>56char help[] =7"cmd explanation/example\n"8"--------------------------------------------\n"9"/m privmsg #chan/nick message\n"10"/M mode #chan +nt\n"11"/j join #chan\n"12"/p part #chan\n"13"/q send parameters raw to the server\n"14"/l list #chan\n"15"/n nick newnick\n"16"/N notice #chan/nick message\n"17"/t set victim\n"18"/T topic #chan newtopic\n"19"/W whois nick\n"20"/w who nick (a shorter whois)\n";2122#define NPAR 142324enum state { Cmd, Prefix, Middle, Trail, Ok };2526typedef struct handler Handler;2728struct handler {29 char *cmd;30 int (*fun)(int fd, char *pre, char *cmd, char *par[]);31};3233QLock lck;34int server_in;35int server_out;36int scr;37char *victim;38char *nick;39char *unick;40int inacme; /* running in acme? */41int linewidth; /* terminal width in # of characters */4243int replay; /* just print the log ma'am */4445void setwintitle(char *chan);4647int rtcs(int fd, char *cset);48int wtcs(int fd, char *cset);49int follow(int fd);50void getwidth(void); /* establish the width of the terminal, from mc.c */5152int pmsg(int fd, char *pre, char *cmd, char *par[]);53int ntc(int fd, char *pre, char *cmd, char *par[]);54int generic(int fd, char *pre, char *cmd, char *par[]);55int misc(int fd, char *pre, char *cmd, char *par[]);56int numeric(int fd, char *pre, char *cmd, char *par[]);5758Handler handlers[] = {59 {"PRIVMSG", pmsg},60 {"NOTICE", ntc},61 {"JOIN", misc},62 {"PART", misc},63 {"MODE", misc},64 {"QUIT", misc},65 {"TOPIC", misc},66 {"332", numeric},67 {"333", numeric},68 {"352", numeric},69 {"315", numeric},70 {"311", numeric},71 {"319", numeric},72 {"312", numeric},73 {"320", numeric},74 {"317", numeric},75 {"318", numeric},76 {nil, nil}77};7879int srvparse(char *line, char **pre, char **cmd, char *par[], int npar);80int usrparse(char *ln, char *cmd, char *par[], int npar);8182void83usage(void)84{85 char usage[] = "usage: irc [-S] [-c charset] [-t victim] [-b lines] [-n nick] [-r file] [/srv/irc] [/tmp/irc]\n";86 write(1, usage, sizeof(usage)-1);87 exits("usage");88}8990void91setwintitle(char *chan)92{93 int fd;9495 // if /dev/acme doesn't exist you're probably in a normal window96 // otherwise you can't set the title in a windowless window97 if ((fd = open("/dev/acme/ctl", OWRITE)) >= 0) {98 fprint(fd, "name -IRC/%s/guide\n", chan);99 close(fd);100 inacme = 1;101 } else if ((fd = open("/dev/label", OWRITE)) >= 0) {102 fprint(fd, "%s", chan);103 close(fd);104 }105}106107/* try to find out whether we're running in acme's win -e */108int109testacme(void)110{111 return access("/dev/acme", OREAD) >= 0 ? 1 : 0;112}113114void115usrin(void)116{117 char *line, *p;118 char *par[2];119 char cmd;120 int n, i;121122 Biobuf kbd;123 Binit(&kbd, 0, OREAD);124 while ((line = Brdstr(&kbd, '\n', 0)) != nil) {125 n = utflen(line);126 if(!inacme) {127 p = malloc(n);128 for (i = 0; i < n; ++i)129 p[i] = '\b';130 write(scr, p, i);131 free(p);132 }133 qlock(&lck);134 if (!usrparse(line, &cmd, par, 2)) {135 switch(cmd) {136 case 'q': /* quote, just send the params ... */137 if(par[0]) {138 fprint(server_out, "%s %s\r\n", par[0], par[1] ? par[1] : "");139 } else {140 fprint(scr, "/q %s %s: not enough arguments\n", par[0], par[1]);141 }142 break;143 case 'M':144 if(par[0] && par[1]) {145 fprint(server_out, "MODE %s %s\r\n", par[0], par[1]);146 } else {147 fprint(scr, "/M %s %s: not enough arguments\n", par[0], par[1]);148 }149 break;150 case 'm':151 if(par[0] && par[1]) {152 fprint(server_out, "PRIVMSG %s :%s\r\n", par[0], par[1]);153 } else {154 fprint(scr, "/m %s %s: not enough arguments\n", par[0], par[1]);155 }156 break;157 case 't':158 if(par[0] != nil) {159 free(victim);160 victim = strdup(par[0]);161 setwintitle(par[0]);162 }163 fprint(scr, "*** default victim set to '%s'\n", par[0]);164 break;165 case 'T':166 if(par[0] == nil)167 fprint(server_out, "TOPIC %s\r\n", victim);168 else if(par[1] == nil)169 fprint(server_out, "TOPIC %s\r\n", par[0]);170 else171 fprint(server_out, "TOPIC %s :%s\r\n", par[0], par[1]);172 break;173 case 'j':174 fprint(server_out, "JOIN %s\r\n", par[0] == nil ? victim : par[0]);175 break;176 case 'p':177 fprint(server_out, "PART %s\r\n", par[0] == nil ? victim : par[0]);178 break;179 case 'n':180 if(par[0] != nil) {181 fprint(server_out, "NICK %s\r\n", par[0]);182 free(nick);183 nick = strdup(par[0]);184 unick = strdup(par[0]);185 } else {186 fprint(scr, "%s", help);187 }188 break;189 case 'N':190 if(par[1] != nil)191 fprint(server_out, "NOTICE %s :%s\r\n", par[0] == nil ? victim : par[0], par[1]);192 break;193 case 'W':194 fprint(server_out, "WHOIS %s %s\r\n", par[0] == nil ? victim : par[0], par[0]);195 case 'w':196 fprint(server_out, "WHO %s\r\n", par[0] == nil ? victim : par[0]);197 break;198 case 'l':199 fprint(server_out, "LIST %s\r\n", par[0] == nil ? victim : par[0]);200 break;201 case 'L':202 fprint(server_out, "NAMES %s\r\n", par[0] == nil ? victim : par[0]);203 break;204 case 'f':205 break;206 case 'h':207 case 'H':208 fprint(scr, "%s", help);209 break;210 }211 } else {212 fprint(scr, "%s", help);213 }214 qunlock(&lck);215 free(line);216 }217 exits(0);218}219220void221srvin(void)222{223 char *line;224 char *pre, *cmd, *par[NPAR];225 Biobuf srv;226 Binit(&srv, server_in, OREAD);227228 while ((line = Brdstr(&srv, '\n', 0)) != nil) {229 if (!srvparse(line, &pre, &cmd, par, NPAR)) {230 Handler *hp = handlers;231 qlock(&lck);232 while (hp->cmd != nil) {233 if (!strcmp(hp->cmd, cmd)) {234 hp->fun(server_out, pre, cmd, par);235 break;236 }237 ++hp;238 }239 if (hp->cmd == nil)240 generic(server_out, pre, cmd, par);241 qunlock(&lck);242 }243 free(line);244 }245}246247void248replayfile(void)249{250 char *line;251 char *pre, *cmd, *par[NPAR];252 Biobuf srv;253 Binit(&srv, server_in, OREAD);254255 while ((line = Brdstr(&srv, '\n', 0)) != nil) {256 if (!srvparse(line, &pre, &cmd, par, NPAR)) {257 Handler *hp = handlers;258 qlock(&lck);259 while (hp->cmd != nil) {260 if (!strcmp(hp->cmd, cmd)) {261 hp->fun(server_out, pre, cmd, par);262 break;263 }264 ++hp;265 }266 if (hp->cmd == nil)267 generic(server_out, pre, cmd, par);268 qunlock(&lck);269 }270 free(line);271 }272}273274/*275 * display the last N lines from the conversation276 * if we have a default target only the conversation with277 * that target will be shown278 */279void280seekback(int fd, int lines)281{282 Biobuf srv;283 int found = 0, off;284 char c, *line;285286 if(lines < 0)287 return;288289 Binit(&srv, fd, OREAD);290291 Bseek(&srv, -2, 2);292 while(((off = Boffset(&srv)) > 0) && found < lines) {293 c = Bgetc(&srv);294 Bungetc(&srv);295 if(c == '\n') {296 Bseek(&srv, 1, 1);297 line = Brdstr(&srv, '\n', '\0');298 if(victim) {299 if(cistrstr(line, victim))300 found++;301 } else {302 found++;303 }304 free(line);305 }306 Bseek(&srv, off-1, 0);307 }308309 Bterm(&srv);310}311312void313main(int argc, char *argv[])314{315 char *charset = nil;316 char buf[32], buf2[32], *out = nil, *in = nil;317 char *user;318 int olduser = 0;319 char *arg;320 int sb = 10; /* how many lines are we displaying initially */321 int uipid;322 int scroll = 1;323 int wctlfd;324325 unick = nil;326 user = getuser();327 ARGBEGIN {328 case 't':329 victim = strdup(EARGF(usage()));330 break;331 case 'b':332 arg = ARGF();333 if(arg != nil && arg[0] != '-')334 sb = atoi(arg);335 else336 sb = 0; /* show all text */337 break;338 case 'c':339 charset = EARGF(usage());340 break;341 case 'r':342 replay = 1;343 sb = 0;344 break;345 case 'o':346 olduser = 1;347 break;348 case 'n':349 unick = strdup(EARGF(usage()));350 break;351 case 'S':352 scroll = 0;353 break;354 default:355 usage();356 } ARGEND;357358 switch(argc) {359 case 0:360 break;361 case 1:362 if(replay)363 in = argv[0];364 else365 out = argv[0];366 break;367 case 2:368 out = argv[0];369 in = argv[1];370 break;371 default:372 usage();373 }374375 if(out == nil && in == nil){376 if(olduser == 1 && strlen(user) > 4)377 user[4] = '\0';378 snprint(buf, sizeof buf, "/srv/%sirc", user);379 snprint(buf2, sizeof buf, "/tmp/%sirc", user);380 out = strdup(buf);381 in = strdup(buf2);382 }383 if(unick == nil)384 unick = strdup(user);385386 if(scroll == 1){387 if((wctlfd = open("/dev/wctl", OWRITE)) < 0)388 print("irc: could not open /dev/wctl: %r\n");389 else {390 fprint(wctlfd, "scroll\n");391 close(wctlfd);392 }393 }394395 if(!replay && (server_out = open(out, OWRITE)) < 0)396 sysfatal("open write: %s %r", out);397 if ((server_in = open(in, OREAD)) < 0)398 sysfatal("open read: %s %r", in);399400 inacme = testacme();401 getwidth();402403 if(sb)404 seekback(server_in, sb);405406 while(read(server_in, buf, 1) > 0)407 if(*buf == '\n')408 break;409410 if(victim && cistrncmp(victim, "MSGS", 4)){411 setwintitle(victim);412 fprint(server_out, "JOIN %s\r\n", victim);413 }414 scr = 1;415416 server_in = follow(server_in);417418 if (charset != nil && strcmp(charset, "utf")) {419 server_out = wtcs(server_out, charset);420 server_in = rtcs(server_in, charset);421 }422423 if(replay) {424 replayfile();425 } else {426 if ((uipid = rfork(RFPROC|RFFDG|RFMEM)) == 0)427 srvin();428429 usrin();430431 postnote(PNPROC, uipid, "kill");432 while (waitpid() != uipid);433 }434435 exits(0);436}437438int439wtcs(int fd, char *cset)440{441 int totcs[2];442 int pid;443444 pipe(totcs);445446 pid = fork();447448 if (pid == 0) {449 dup(totcs[0], 0);450 dup(fd, 1);451 close(totcs[1]);452 execl("/bin/tcs", "tcs", "-f", "utf", "-t", cset, nil);453 exits("execfailure");454 }455 close(totcs[0]);456457 return totcs[1];458}459460int461rtcs(int fd, char *cset)462{463 int fromtcs[2];464 int pid;465466 pipe(fromtcs);467468 pid = fork();469470 if (pid == 0){471 dup(fromtcs[1], 1);472 dup(fd, 0);473 close(fromtcs[0]);474 execl("/bin/tcs", "tcs", "-f", cset, "-t", "utf", nil);475 exits("execfailure");476 }477 close(fromtcs[1]);478479 return fromtcs[0];480}481482int483follow(int fd)484{485 int p[2], pid;486 long n;487 char buf[1024];488 Dir *dp;489490 pipe(p);491492 pid = fork();493 if (pid == 0){494 dup(p[1], 1);495 dup(fd, 0);496 close(p[0]);497 for(;;){498 while((n = read(0, buf, sizeof(buf))) > 0)499 write(1, buf, n);500 sleep(1000);501 dp = dirfstat(0);502 free(dp);503 }504 }505 close(p[1]);506507 return p[0];508}509510char *511prenick(char *p)512{513 char *n = p;514 if (p != nil) {515 while (*p != '\0' && *p != '!') ++p;516 if (*p != '!')517 n = nil;518 *p = '\0';519 }520 return n;521}522523int524pmsg(int, char *pre, char *, char *par[])525{526 int n = 0;527 char buf[8192];528 //char *c, *tc;529 char *c;530 int privmsg = 0;531532/*533 * if sent to victim, or comes from victim to non-channel, print.534 * otherwise bail out.535 */536 pre = prenick(pre);537 if(victim) {538 if((cistrncmp(victim, "MSGS", 4) == 0) && *par[0] != '#') {539 /* catch-all for messages, fall through */540541 } else if(cistrcmp(par[0], victim)){542 if(*par[0] != '#' && cistrcmp(victim, "privmsg") == 0){543 if(cistrcmp(par[0], unick) == 0 &&544 cistrcmp(par[0], victim) != 0)545 privmsg = 1;546 } else if(!pre || cistrcmp(pre, victim) || *par[0] == '#')547 return 0;548 }549 }550551 if(!pre)552 sprint(buf, "(%s) ⇐ %s\n", par[0], par[1]);553 else if(privmsg == 1)554 sprint(buf, "(privmsg) %s → %s\n", pre, par[1]);555 else if(*par[0] != '#')556 sprint(buf, "(%s) ⇒ %s\n", pre, par[1]);557 else558 sprint(buf, "(%s) %s → %s\n", par[0], pre, par[1]);559560 c = buf;561 n += fprint(scr, "%s", c);562 return n;563}564565int566ntc(int, char *pre, char *, char *par[])567{568 int n;569570/*571 * if sent to victim, or comes from victim to non-channel, print.572 * otherwise bail out.573 */574 pre = prenick(pre);575 if(victim && cistrcmp(par[0], victim))576 if(!pre || cistrcmp(pre, victim) || *par[0] == '#')577 return 0;578579 if(!pre)580 n = fprint(scr, "[%s] ⇐\t%s\n", par[0], par[1]);581 else if(*par[0] != '#')582 n = fprint(scr, "[%s] ⇒\t%s\n", pre, par[1]);583 else584 n = fprint(scr, "[%s] %s →\t%s\n", par[0], pre, par[1]);585 return n;586}587588int589generic(int, char *pre, char *cmd, char *par[])590{591 int i = 0, r;592 char *n = prenick(pre);593594/*595 * don't print crud on screens with victim set596 */597 if(victim)598 return 0;599600 if (n != nil)601 r = fprint(scr, "%s (%s)\t", cmd, n);602 else603 r = fprint(scr, "%s (%s)\t", cmd, par[i++]);604605 for (; par[i] != nil; ++i)606 r += fprint(scr, " %s", par[i]);607608 r += fprint(scr, "\n");609610 return r;611}612613int614misc(int, char *pre, char *cmd, char *par[])615{616 int i = 0, r;617 char *n = prenick(pre);618619 if(victim != nil)620 if(cistrcmp(victim, "privmsg") == 0)621 return 0;622 if(cistrcmp(cmd,"QUIT"))623 if(victim && par[0] && cistrcmp(par[0], victim))624 return 0;625626 if (n != nil)627 r = fprint(scr, "%s (%s)\t", cmd, n);628 else629 r = fprint(scr, "%s %s\t", cmd, par[i++]);630631 for (; par[i] != nil; ++i)632 r += fprint(scr, " %s", par[i]);633634 r += fprint(scr, "\n");635636 return r;637}638639int640numeric(int, char *pre, char *cmd, char *par[])641{642 int i = 0, r;643 char *n = prenick(pre);644645 if(victim != nil)646 if(cistrcmp(victim, "privmsg") == 0)647 return 0;648649 if(victim && par[1] && cistrcmp(par[1], victim))650 return 0;651652 if (n != nil)653 r = fprint(scr, "%s (%s)\t", cmd, n);654 else655 r = fprint(scr, "%s (%s)\t", cmd, par[i++]);656657 for (; par[i] != nil; ++i)658 r += fprint(scr, " %s", par[i]);659660 r += fprint(scr, "\n");661662 return r;663}664665int666usrparse(char *ln, char *cmd, char *par[], int npar)667{668 enum state st = Cmd;669 int i;670671 for(i = 0; i < npar; i++)672 par[i] = nil;673674 if (ln[0] == '/' && npar >= 2) {675 *cmd = ln[1];676 for (i = 1; ln[i] != '\0'; ++i) {677 switch(st) {678 case Cmd:679 if (ln[i] == ' ') {680 ln[i] = '\0';681 par[0] = ln+i+1;682 st = Middle;683 } else if(ln[i] == '\n') {684 /* enable commands with no arguments */685 ln[i] = '\0';686 par[0] = nil;687 par[1] = nil;688 st = Ok;689 }690 break;691 case Middle:692 if (ln[i] == '\r' || ln[i] == '\n') {693 ln[i] = '\0';694 st = Ok;695 }696 if (ln[i] == ' ') {697 ln[i] = '\0';698 par[1] = ln+i+1;699 st = Trail;700 }701 break;702 case Trail:703 if (ln[i] == '\r' || ln[i] == '\n') {704 ln[i] = '\0';705 st = Ok;706 }707 break;708 case Ok:709 if (ln[i] == '\r' || ln[i] == '\n')710 ln[i] = '\0';711 break;712 }713 }714 } else { /* send line to victim by default */715 st = Ok;716 *cmd = 'm';717 for (i = 0; ln[i] != '\0'; ++i)718 if (ln[i] == '\r' || ln[i] == '\n')719 ln[i] = '\0';720 par[0] = victim;721 par[1] = ln;722 }723 return st == Ok ? 0 : 1;724}725726int727srvparse(char *line, char **pre, char **cmd, char *par[], int npar)728{729 int i;730 char *p;731 enum state st = Cmd;732733 *pre = *cmd = nil;734735 for (*cmd = p = line, i = 0; *p != '\0'; ++p) {736 switch (st) {737 case Cmd:738 if (*p == ':') {739 *p = '\0';740 *pre = p + 1;741 st = Prefix;742 } else if (*p == ' ') {743 *p = '\0';744 par[i] = p + 1;745 st = Middle;746 }747 break;748 case Prefix:749 if (*p == ' ') {750 *p = '\0';751 *cmd = p + 1;752 st = Cmd;753 }754 break;755 case Middle:756 if (*p == '\r' || *p == '\n') {757 *p = '\0';758 st = Ok;759 } else if (*p == ':') {760 *p = '\0';761 par[i] = p + 1;762 st = Trail;763 } else if (*p == ' ') {764 *p = '\0';765 i = (i + 1) % npar;766 par[i] = p + 1;767 st = Middle;768 }769 break;770 case Trail:771 if (*p == '\r' || *p == '\n') {772 *p = '\0';773 st = Ok;774 }775 break;776 case Ok:777 *p = '\0';778 break;779 }780 }781 par[(i + 1) % npar] = nil;782 return st == Ok ? 0 : 1;783}784785void // this garbage is bullshit786getwidth(void)787{788 Font *font;789 int n, fd, mintab;790 char buf[128], *f[10], *p;791792 if(inacme){793 if((fd = open("/dev/acme/ctl", OREAD)) < 0)794 return;795 n = read(fd, buf, sizeof buf-1);796 close(fd);797 if(n <= 0)798 return;799 buf[n] = 0;800 n = tokenize(buf, f, nelem(f));801 if(n < 7)802 return;803 if((font = openfont(nil, f[6])) == nil)804 return;805 mintab = stringwidth(font, "0");806 linewidth = atoi(f[5]);807 linewidth = linewidth/mintab;808 return;809 }810811 if((p = getenv("font")) == nil)812 return;813 if((font = openfont(nil, p)) == nil)814 return;815 if((fd = open("/dev/window", OREAD)) < 0)816 return;817818 n = read(fd, buf, 5*12);819 close(fd);820821 if(n < 5*12)822 return;823824 buf[n] = 0;825826 /* window stucture:827 4 bit left edge828 1 bit gap829 12 bit scrollbar830 4 bit gap831 text832 4 bit right edge833 */834// linewidth = atoi(buf+3*12) - atoi(buf+1*12) - (4+1+12+4+4);835 linewidth = 20000; // update this when screens are more than 20,000 pixels wide836 mintab = stringwidth(font, "0");837 linewidth = linewidth/mintab;838}