9fans - fans of the OS Plan 9 from Bell Labs
 help / color / mirror / Atom feed
From: "Russ Cox" <rsc@plan9.bell-labs.com>
To: 9fans@cse.psu.edu
Subject: [9fans] irc tools
Date: Thu,  3 Oct 2002 22:17:23 -0400	[thread overview]
Message-ID: <f89957c1940f5101e1212ce079785898@plan9.bell-labs.com> (raw)

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 '\x01DCC CHAT chat ip-decimal port\x01'
-# PRIVMSG :from to '\x01DCC SEND file-name ip-decimal port size\x01'
-
-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 <u.h>
-#include <libc.h>
-#include <bio.h>
-
-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; nargs<maxargs; nargs++){
-		while(*s==' ')
-			*s++ = '\0';
-		if(*s == '\0')
-			break;
-		args[nargs] = s;
-		if(s[0] == ':' && s != os){
-			args[nargs++]++;
-			break;
-		}
-		while(*s != ' ' && *s != '\0')
-			s++;
-	}
-
-	return nargs;
-}
-
-void
-main(int argc, char **argv)
-{
-	char *f[64];
-	char *s;
-	Biobuf *b;
-	Biobuf bout;
-	int nf;
-	int i, cmd;
-	char *src;
-
-	doquote = lsquote;
-	quotefmtinstall();
-	ARGBEGIN{
-	default:
-		usage();
-	}ARGEND
-
-	file = "/fd/0";
-	switch(argc){
-	default:
-		usage();
-	case 0:
-		break;
-	case 1:
-		file = argv[0];
-		break;
-	}
-
-	b = Bopen(file, OREAD);
-	if(b == nil)
-		sysfatal("open: %r");
-
-	Binit(&bout, 1, OWRITE);
-	for(; (s = Brdstr(b, '\n', 1)) != nil; free(s)){
-		if(strlen(s) == 0)
-			continue;
-		if(s[strlen(s)-1] == '\r')
-			s[strlen(s)-1] = '\0';
-		nf = irctokenize(s, f, nelem(f));
-		if(nf == 0)
-			continue;
-		cmd = 0;
-		src = "";
-		if(f[0][0] == ':'){
-			src = f[0];
-			cmd++;
-		}
-		if(nf <= cmd)
-			continue;
-		Bprint(&bout, "%q %q", f[cmd], src);
-		for(i=cmd+1; i<nf; i++)
-			Bprint(&bout, " %q", f[i]);
-		Bprint(&bout, "\n");
-		Bflush(&bout);
-	}
-	exits(nil);
-}
-
//GO.SYSIN DD irccat.c
echo ircctcp.c
sed 's/.//' >ircctcp.c <<'//GO.SYSIN DD ircctcp.c'
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-
-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 <u.h>
-#include <libc.h>
-
-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 <u.h>
-#include <libc.h>
-#include <fcall.h>
-#include <thread.h>
-#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



             reply	other threads:[~2002-10-04  2:17 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2002-10-04  2:17 Russ Cox [this message]
2002-10-04  4:48 ` Jonathan Sergent
2002-10-04 16:40 ` Jack Johnson
2002-10-04 16:43   ` Scott Schwartz
2002-10-04 17:14     ` Jack Johnson
2002-10-04 17:24   ` andrey mirtchovski
2002-10-05  0:41 Russ Cox
2002-10-05 16:34 ` Jack Johnson
2002-10-05  2:09 rob pike, esq.

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=f89957c1940f5101e1212ce079785898@plan9.bell-labs.com \
    --to=rsc@plan9.bell-labs.com \
    --cc=9fans@cse.psu.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).