From mboxrd@z Thu Jan 1 00:00:00 1970 Message-ID: To: 9fans@cse.psu.edu From: "Russ Cox" MIME-Version: 1.0 Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: 7bit Subject: [9fans] irc tools Date: Thu, 3 Oct 2002 22:17:23 -0400 Topicbox-Message-UUID: fc662536-eaca-11e9-9e20-41e7f4b1d025 this doesn't quite qualify as an irc client. the interface is terrible, but all the functionality is there. it implements dcc chat and file sending too. i'm more interested in scripting things than actually chatting, hence the rough interactive interface. there are a few helper programs and a main script. the helpers are: netmux - consolefs, but for a network connection irccat - rewrite irc traffic from :from cmd to args... to cmd :from to args... quoting as necessary to make it safe shell input ircctcp - format a ctcp command into a PRIVMSG command. used by the ctcp shell function. ircget - perform a dcc file receive the main script logs into the server and then runs a background proc that watches the irc traffic for PING and PRIVMSG commands and acts accordingly. PING gets PONGed, and PRIVMSG looks for dcc chat or file transfers and starts them. (this isn't quite that safe, no.) my expectation is you'd want to change the behavior of PRIVMSG, though the PING behavior is safe. as an example of how everything works, the background proc runs by doing irccat | grep '^(PRIVMSG|PING) ' | rc in a shell with appropriate PRIVMSG and PING functions. irc under plan 9 seems to be a regular though not particularly frequent question. this is my contribution to the cause. enjoy. russ # To unbundle, run this file echo irc sed 's/.//' >irc <<'//GO.SYSIN DD irc' -#!/bin/rc - -rfork en - -fn cmd { - echo $* >/dev/irc -} -fn nick { - switch($#*){ - case 1 - cmd NICK $1 - case * - echo 'usage: nick name' >[1=2] - } -} - -fn user { - cmd USER $* -} - -fn msg { - switch($#*){ - case 0 1 - echo 'usage: msg name message...' >[1=2] - case * - addr = $1 - shift - cmd PRIVMSG $addr $"* - } -} - -fn ctcp { - switch($#*){ - case 0 1 - echo 'usage: msg name message...' >[1=2] - case * - addr = $1 - shift - # can't use normal processing because we need - # to send an 001 byte, and rc cuts them out of variables. - # (they are word separators.) - x=`{echo $* | tr a-z A-Z} - ircctcp $addr $"x >/dev/irc - } -} - -fn _join { - rfork e - fn sigexit { - cmd PART $channel - } - irccat /dev/irc | grep -i '^(PRIVMSG|NOTICE) '''$channel''' ' & - prompt=($channel^'% ' ' ') - rc -i -} -fn join { - switch($#*){ - case 1 - cmd JOIN $1 - channel=$1 - window -m rc -c _join $1 - case * - echo 'usage: join channel' >[1=2] - } -} - -fn decimalip { - echo $1 | awk '{a=$1; printf("%d.%d.%d.%d\n", a/256/256/256, (a/256/256)%256, (a/256)%256, a%256);}' -} - -fn PING { # ping handler - echo Ping-pong. - cmd PONG $* -} - -# PRIVMSG :from to 'DCC CHAT chat ip-decimal port' -# PRIVMSG :from to 'DCC SEND file-name ip-decimal port size' - -fn _chat { - rfork e - con -Cl $1 -} - -fn PRIVMSG { # privmsg handler - *=(`{echo $3}) - if(~ $1 DCC && ~ $2 CHAT && ~ $3 chat){ - ip=`{decimalip $4} - port=$5 - window -m rc -c '_chat 'tcp!$ip!$port - } - if not if(~ $1 DCC && ~ $2 SEND){ - file=$3 - ip=`{decimalip $4} - port=$5 - size=$6 - out=/tmp/irc/in.$pid.^`{date -n} - whatis file ip port size >$out.info - ircget tcp!$ip!$port >$out & - } -} - -fn sigexit { - cmd QUIT -} - -email=(irc irc.irc) -fullname='IRC User' - -switch($#*){ -case 2 - ; -case 3 - email=`{echo $3 | tr @ ' '} -case 4 - email=`{echo $3 | tr @ ' '} - fullname=$4 -case * - echo 'usage: irc server nick [email [fullname]]' >[1=2] - exit usage -} - -server=$1 -nick=$2 - -if(~ $#email 0 1) - email=(irc irc.irc) -if(! ~ $#email 2) - email=($email(1) $email(2)) - -netmux -m /dev -n irc $1 - -# handlers -irccat /dev/irc | grep '^(PING|PRIVMSG) ' | rc & -irccat /dev/irc & -prompt=('IRC% ' ' ') -nick $2 -user $email $server ':'^$"fullname -rc -i //GO.SYSIN DD irc echo irccat.c sed 's/.//' >irccat.c <<'//GO.SYSIN DD irccat.c' -#include -#include -#include - -char *file; - -int -lsquote(int c) -{ - if(c <= ' ') - return 1; - if(strchr("`^#*[]=|\?${}()';", c)) - return 1; - return 0; -} - -void -usage(void) -{ - fprint(2, "usage: irccat [/dev/irc]\n"); - exits("usage"); -} - -int -irctokenize(char *s, char **args, int maxargs) -{ - int nargs; - char *os; - - os = s; - for(nargs=0; nargsircctcp.c <<'//GO.SYSIN DD ircctcp.c' -#include -#include -#include - -void -usage(void) -{ - fprint(2, "usage: ircctcp dest msg>/dev/irc\n"); - exits("usage"); -} - -char* -q(char *m) -{ - Fmt fmt; - char *p, s[2]; - - fmtstrinit(&fmt); - for(p=m; *p; p++) - if(*p == 1) - fmtstrcpy(&fmt, "\\a"); - else if(*p == '\\') - fmtstrcpy(&fmt, "\\\\"); - else{ - s[0] = *p; - s[1] = '\0'; - fmtstrcpy(&fmt, s); - } - return fmtstrflush(&fmt); -} - -void -main(int argc, char **argv) -{ - char *dst, *msg, *s; - - argv0 = argv[0]; - if(argc != 3) - usage(); - - dst = argv[1]; - msg = argv[2]; - - s = smprint("PRIVMSG %s :%c%s%c\n", dst, 1, q(msg), 1); - write(1, s, strlen(s)); - exits(nil); -} - //GO.SYSIN DD ircctcp.c echo ircget.c sed 's/.//' >ircget.c <<'//GO.SYSIN DD ircget.c' -#include -#include - -int alarmed; - -void -usage(void) -{ - fprint(2, "usage: ircget address\n"); - exits("usage"); -} - -int -bell(void*, char *msg) -{ - if(strcmp(msg, "alarm") == 0){ - alarmed = 1; - return 1; - } - return 0; -} - -void -send(int fd, int tot) -{ - uchar buf[4]; - -if(fd == 0) fd = 1; - buf[0] = tot>>24; - buf[1] = tot>>16; - buf[2] = tot>>8; - buf[3] = tot; - write(fd, buf, 4); -} - -void -main(int argc, char **argv) -{ - char *addr; - int fd, tot; - char buf[8192]; - int n, first, cur; - - argv0 = argv[0]; - if(argc > 2) - usage(); - - if(argc == 2){ - addr = argv[1]; - fd = dial(addr, nil, nil, nil); - if(fd < 0) - sysfatal("dial: %r"); - }else - fd = 0; - - atnotify(bell, 1); - - first = 0; - tot = 0; - cur = 0; - for(;;){ - alarm(1000); - alarmed = 0; - if((n = read(fd, buf, sizeof buf)) <= 0){ - alarm(0); - if(alarmed){ - if(first == 0) - first = tot; - cur = tot; - send(fd, tot); - continue; - } - break; - } - tot += n; - if(first && cur+first < tot){ - send(fd, tot); - cur = tot; - } - alarm(0); - if(write(1, buf, n) != n) - sysfatal("write: %r"); - } - send(fd, tot); - exits(nil); -} //GO.SYSIN DD ircget.c echo netmux.c sed 's/.//' >netmux.c <<'//GO.SYSIN DD netmux.c' -/* - * Mux a given dialed network address among many clients. - */ - -#include -#include -#include -#include -#include <9p.h> - -uint time0; - -enum -{ - Qroot = 0, - Qkid, - - Bufsize = 32*1024, -}; - -typedef struct Buffer Buffer; -struct Buffer -{ - Buffer *next; - char buf[Bufsize]; - uint nbuf; - Req *wait; - Req *endwait; -}; - -int dialnet(void); -int nfd = -1; -char *addr; -char *emailname; -char *emailserver; -char *server; -char *nick; -char *email; -char *fullname; -int didnet; -int redial; -Buffer *allbuf; -QLock buflock; -int debug; -char *kidname = "mux"; - -void -servebuf(Buffer *b) -{ - int n; - Req *r; - - while(b->wait && b->nbuf){ - n = b->nbuf; - r = b->wait; - b->wait = r->aux; - r->aux = nil; - if(n > r->ifcall.count) - n = r->ifcall.count; - r->ofcall.data = b->buf; - r->ofcall.count = n; - respond(r, nil); - b->nbuf -= n; - memmove(b->buf, b->buf+n, b->nbuf); - } -} - -void -addbuf(char *a, uint n) -{ - Buffer *b; - - if(n > Bufsize){ - a += n - Bufsize; - n = Bufsize; - } - - qlock(&buflock); - for(b=allbuf; b; b=b->next){ - if(b->nbuf + n > Bufsize) - b->nbuf = 0; - memmove(b->buf+b->nbuf, a, n); - b->nbuf += n; - servebuf(b); - } - qunlock(&buflock); -} - -void -cancelbuf(void) -{ - Buffer *b; - Req *r, *next; - - qlock(&buflock); - for(b=allbuf; b; b=b->next){ - for(r=b->wait; r; r=next){ - next = r->aux; - r->aux = nil; - respond(r, "hangup"); - } - b->wait = nil; - } - qunlock(&buflock); -} - -void -readbuffer(Req *r, Fid *fid) -{ - Buffer *b; - - qlock(&buflock); - if(time0 == 0){ - respond(r, "hangup"); - qunlock(&buflock); - return; - } - - b = fid->aux; - r->aux = nil; - if(b->wait == nil) - b->wait = r; - else - b->endwait->aux = r; - b->endwait = r; - servebuf(b); - qunlock(&buflock); -} - -void -flushbuf(Buffer *b, Req *r) -{ - Req **l; - - qlock(&buflock); - for(l=&b->wait; *l; l=&(*l)->aux){ - if(*l == r){ - *l = r->aux; - qunlock(&buflock); - respond(r, nil); - return; - } - } - qunlock(&buflock); -} - -Buffer* -mkbuffer(void) -{ - Buffer *b; - - qlock(&buflock); - b = emalloc9p(sizeof *b); - memset(b, 0, sizeof *b); - b->next = allbuf; - allbuf = b; - qunlock(&buflock); - return b; -} - -void -closebuffer(Buffer *b) -{ - Buffer **l; - - if(b == nil) - return; - - qlock(&buflock); - for(l=&allbuf; *l; l=&(*l)->next) - if(*l == b) - break; - if(*l == nil) - sysfatal("buffer not found"); - *l = b->next; - qunlock(&buflock); - - if(b->wait != nil) - sysfatal("buffer has waiting requests"); - - free(b); -} - -void -netreader(void*) -{ - char buf[512]; - int n; - - for(;;){ - while((n = read(nfd, buf, sizeof buf)) > 0){ - if(debug) - write(2, buf, n); - addbuf(buf, n); - } - if(debug) - fprint(2, "hangup\n"); - if(!redial) - sysfatal("hangup on network connection"); - close(nfd); - nfd = -1; - time0 = 0; - cancelbuf(); - - for(;;){ - if(dialnet() < 0) - sleep(1000); - } - time0 = time(0); - } -} - -void -fsattach(Req *r) -{ - char *spec; - - spec = r->ifcall.aname; - if(spec && spec[0]){ - respond(r, "invalid attach specifier"); - return; - } - r->ofcall.qid = (Qid){Qroot, 0, QTDIR}; - r->fid->qid = r->ofcall.qid; - - if(!didnet){ - didnet = 1; - proccreate(netreader, nil, 8192); - } - respond(r, nil); -} - -char* -fswalk1(Fid *fid, char *name, Qid *qid) -{ - switch((int)fid->qid.path){ - default: - return "path not found"; - case Qroot: - if(strcmp(name, "..") == 0) - break; - if(strcmp(name, kidname) == 0){ - fid->qid = (Qid){Qkid, 0, 0}; - break; - } - return "path not found"; - case Qkid: - if(strcmp(name, "..") == 0){ - fid->qid = (Qid){Qroot, 0, QTDIR}; - break; - } - return "path not found"; - } - *qid = fid->qid; - return nil; -} - -void -fsstat(Req *r) -{ - int q; - Dir *d; - - d = &r->d; - memset(d, 0, sizeof *d); - q = r->fid->qid.path; - d->qid = r->fid->qid; - switch(q){ - case Qroot: - d->name = estrdup9p("/"); - d->mode = DMDIR|0777; - break; - - case Qkid: - d->name = estrdup9p(kidname); - d->mode = 0666; - break; - } - - d->atime = d->mtime = time0; - d->uid = estrdup9p("netmux"); - d->gid = estrdup9p("netmux"); - d->muid = estrdup9p(""); - respond(r, nil); -} - -int -dirgen(int off, Dir *d, void*) -{ - if(off != 0) - return -1; - - memset(d, 0, sizeof *d); - d->atime = d->mtime = time0; - d->name = estrdup9p(kidname); - d->mode = 0666; - d->qid = (Qid){Qkid, 0, 0}; - d->qid.type = d->mode>>24; - d->uid = estrdup9p("stub"); - d->gid = estrdup9p("stub"); - d->muid = estrdup9p(""); - return 0; -} - -void -fsread(Req *r) -{ - int q; - - q = r->fid->qid.path; - switch(q){ - default: - respond(r, "bug"); - return; - - case Qkid: - if(r->fid->qid.vers != time0 || time0 == 0){ - respond(r, "hangup"); - return; - } - readbuffer(r, r->fid); - return; - - case Qroot: - dirread9p(r, dirgen, nil); - respond(r, nil); - return; - } -} - -void -fswrite(Req *r) -{ - int q; - - q = r->fid->qid.path; - switch(q){ - default: - respond(r, "no writing"); - return; - - case Qkid: - if(r->fid->qid.vers != time0 || time0 == 0){ - respond(r, "hangup"); - return; - } - if(debug) - write(2, r->ifcall.data, r->ifcall.count); - if(write(nfd, r->ifcall.data, r->ifcall.count) != r->ifcall.count){ - respond(r, "hangup"); - return; - } - r->ofcall.count = r->ifcall.count; - respond(r, nil); - return; - } -} - -void -fsopen(Req *r) -{ - if(r->fid->qid.path == Qroot){ - if(r->ifcall.mode != OREAD) - respond(r, "permission denied"); - else - respond(r, nil); - return; - } - if(r->fid->qid.path == Qkid){ - r->fid->aux = mkbuffer(); - r->fid->qid.vers = time0; - r->ofcall.qid.vers = time0; - respond(r, nil); - return; - } - respond(r, "permission denied"); -} - -void -fsflush(Req *r) -{ - flushbuf(r->oldreq->fid->aux, r->oldreq); - respond(r, nil); -} - -int -dialnet(void) -{ - if(nfd >= 0){ - close(nfd); - nfd = -1; - USED(nfd); - } - if((nfd = dial(addr, nil, nil, nil)) < 0) - return -1; - - time0 = time(0); - return 0; -} - -void -fsclose(Fid *fid) -{ - closebuffer(fid->aux); -} - -void -fsend(Srv*) -{ - threadexitsall(nil); -} - -Srv fs = { - .attach= fsattach, - .open= fsopen, - .read= fsread, - .write= fswrite, - .stat= fsstat, - .walk1= fswalk1, - .destroyfid= fsclose, - .end= fsend, - .flush= fsflush, -}; - -void -usage(void) -{ - fprint(2, "usage: netmux [-m mtpt] [-n name] addr\n"); - exits("usage"); -} - -void -threadmain(int argc, char **argv) -{ - char *mtpt; - - quotefmtinstall(); - - mtpt = "/mnt"; - time0 = time(0); - ARGBEGIN{ - case 'D': - chatty9p++; - break; - case 'd': - debug = 1; - break; - case 'm': - mtpt = EARGF(usage()); - break; - case 'n': - kidname = EARGF(usage()); - break; - case 'r': - redial = 1; - break; - default: - usage(); - }ARGEND - - if(argc != 1) - usage(); - - addr = argv[0]; - - if(dialnet() < 0) - sysfatal("dial: %r"); - - threadpostmountsrv(&fs, nil, mtpt, MBEFORE); -} - //GO.SYSIN DD netmux.c