irc7

Fork of https://bitbucket.org/mveety/irc7 with improved TLS support

git clone https://git.8pit.net/irc7.git

  1#include <u.h>
  2#include <libc.h>
  3#include <auth.h>
  4#include <libsec.h>
  5
  6char *post;
  7char *file;
  8int ircfd = -1; // the irc server
  9int logfd;
 10int enctls = 0; // ssl/tls
 11int port = 6667;
 12QLock lck;
 13char *channels[256];
 14int tchans;
 15Thumbprint *thumb;
 16
 17char *ccert;
 18char *server;
 19char *passwd;
 20char *nickname;
 21char *realname;
 22char *username;
 23char *mode = "foo";
 24char *unused = "bar";
 25
 26void ircsrv(void);
 27void logger(void);
 28void die(void*, char*);
 29void reconnect(void);
 30void joinhandler(char*);
 31
 32void
 33usage(void)
 34{
 35	fprint(2, "usage: %s [-e] [-t thumb] [-c cert] [-p port] [-s service] [-f file] [-P pass] [-n nickname] [-r realname ] server\n", argv0);
 36	exits("usage");
 37}
 38
 39void
 40killall(void)
 41{
 42	postnote(PNGROUP, getpid(), "quit");
 43	while(waitpid() != -1)
 44		;
 45	remove(post);
 46	exits(nil);
 47}
 48
 49void
 50die(void *, char *)
 51{
 52	killall();
 53}
 54
 55void
 56main(int argc, char *argv[])
 57{
 58	char *tmp, *tfp = nil;
 59	int p[2], fd;
 60
 61	ARGBEGIN{
 62	case 'c':
 63		ccert = EARGF(usage());
 64		break;
 65	case 't':
 66		tfp = EARGF(usage());
 67		break;
 68	case 'p':
 69		port = atoi(EARGF(usage()));
 70		break;
 71	case 'f':
 72		file = EARGF(usage());
 73		break;
 74	case 's':
 75		post = EARGF(usage());
 76		break;
 77	case 'r':
 78		realname = EARGF(usage());
 79		break;
 80	case 'e':
 81		enctls = 1;
 82		break;
 83	case 'n':
 84		nickname = strdup(EARGF(usage()));
 85		break;
 86	case 'P':
 87		passwd = EARGF(usage());	
 88		/* try to obfuscate the password so ps -a won't see it */
 89		tmp = passwd;
 90		passwd = smprint("%s", tmp);
 91		if(passwd) 
 92			memset(tmp, '\0', strlen(tmp));
 93		else
 94			passwd = tmp;
 95		break;
 96	default:
 97		usage();
 98	}ARGEND;
 99
100	if(argc < 1)
101		usage();
102
103	server = argv[0];
104	username = getuser();
105	if(nickname == nil)
106		nickname = strdup(username);
107
108	if(tfp != nil) {
109		thumb = initThumbprints(tfp, nil);
110		if(thumb == nil)
111			sysfatal("initThumbprints: %r");
112	}
113
114	if(post == nil)
115		post = smprint("/srv/%sirc", username);
116	else
117		post = smprint("/srv/%s", post);
118
119	if(file == nil)
120		file = smprint("/tmp/%sirc", username);
121
122	if((logfd = create(file, OWRITE, 0600 | DMAPPEND)) < 0)
123		sysfatal("create(%s): %r", file);
124
125	if((fd = create(post, OWRITE, 0600)) < 0)
126		sysfatal("create(%s): %r", post);
127	if(pipe(p) == -1)
128		sysfatal("pipe: %r");
129	fprint(fd, "%d", p[1]);
130	close(fd);
131	close(p[1]);
132	close(0);
133	close(1);
134	close(2);
135	dup(p[0], 0);
136
137	if(rfork(RFMEM|RFFDG|RFREND|RFPROC|RFNOTEG|RFCENVG|RFNOWAIT) == 0) {
138		notify(die);
139		reconnect();
140		switch(rfork(RFPROC|RFMEM)){
141		case -1:
142			sysfatal("rfork: %r");
143		case 0:
144			notify(die);
145			logger();
146			break;
147		default:
148			ircsrv();
149			break;
150		}
151	}
152	exits(nil);
153}
154
155long
156readln(int fd, void *vp, long len)
157{
158	char *b = vp;
159	while(len > 0 && read(fd, b, 1) > 0){
160		if(*b++ == '\n')
161			break;
162		len--;
163	}
164	return b - (char*)vp;
165}
166
167void
168reregister(void)
169{
170	int n;
171	char nbuf[32];
172
173	strncpy(nbuf, nickname, sizeof(nbuf) - 2);
174	switch(nbuf[strlen(nbuf) - 1]) {
175	case '0':
176	case '1':
177	case '2':
178	case '3':
179	case '4':
180	case '5':
181	case '6':
182	case '7':
183	case '8':
184		nbuf[strlen(nbuf) - 1]++;
185		break;
186	case '9':
187		qlock(&lck);
188		fprint(logfd, "can not register nick, bailing out\n");
189		qunlock(&lck);
190		die(nil, nil);
191	default:
192		n = strlen(nbuf);
193		nbuf[n] = '0';
194		nbuf[n+1] = '\0';
195		break;
196	}
197	qlock(&lck);
198	fprint(ircfd, "NICK %s\r\n", nbuf);
199	fprint(logfd, "NICK %s\r\n", nickname);
200	qunlock(&lck);
201}
202
203void
204reconnect(void)
205{
206	char addr[128];
207	uchar hash[SHA1dlen];
208	TLSconn conn;
209	int i;
210
211	snprint(addr, sizeof(addr), "tcp!%s!%d", server, port);
212
213	if(ircfd >= 0)
214		close(ircfd);
215	if((ircfd = dial(addr, nil, nil, nil)) < 0)
216		sysfatal("dial: %r");
217	if(enctls > 0) {
218		memset(&conn, 0, sizeof(conn));
219		conn.serverName = server;
220		if(ccert != nil) {
221			conn.cert = readcert(ccert, &conn.certlen);
222			if(conn.cert == nil)
223				sysfatal("readcert: %r");
224		}
225
226		ircfd = tlsClient(ircfd, &conn);
227		if(ircfd < 0)
228			sysfatal("tls: %r");
229
230		if(thumb) {
231			if(conn.cert == nil || conn.certlen <= 0)
232				sysfatal("server did not provide TLS certificate");
233			sha1(conn.cert, conn.certlen, hash, nil);
234			if(!okThumbprint(hash, thumb))
235				sysfatal("server certificate not recognized");
236		}
237
238		free(conn.cert);
239		free(conn.sessionID);
240	}
241	qlock(&lck);
242	if(passwd && strcmp(passwd, ""))
243		fprint(ircfd, "PASS %s\r\n", passwd);
244	fprint(ircfd, "USER %s %s %s :%s\r\n",
245		username, mode, unused, realname);
246	fprint(ircfd, "NICK %s\r\n", nickname);
247	for(i = 0; i < tchans; i++)
248		fprint(ircfd, "JOIN %s\r\n", channels[i]);
249	qunlock(&lck);
250}
251
252
253void
254logger(void)
255{
256	char buf[513];
257	char *f[3];
258	long n;
259
260	for(;;){
261		while((n = readln(ircfd, buf, sizeof(buf)-1)) > 0){
262			write(logfd, buf, n);
263			buf[n] = 0;
264			n = tokenize(buf, f, nelem(f));
265			if(n == 3 && *f[0] == ':' && !cistrcmp(f[1], "PING")){
266				qlock(&lck);
267				fprint(ircfd, "PONG %s\r\n", f[2]);
268				fprint(logfd, "PONG %s\r\n", f[2]);
269				qunlock(&lck);
270			} else if(n == 2 && !cistrcmp(f[0], "PING")){
271				qlock(&lck);
272				fprint(ircfd, "PONG %s\r\n", f[1]);
273				fprint(logfd, "PONG %s\r\n", f[1]);
274				qunlock(&lck);
275			} else if(n == 3 && atoi(f[1]) == 433) {
276				reregister();
277			}
278		}
279		reconnect();
280	}
281}
282
283void
284ircsrv(void)
285{
286	char buf[512];
287	long n;
288
289	while((n = readln(0, buf, sizeof(buf)-1)) > 0){
290		joinhandler(buf);
291		qlock(&lck);
292		if(write(logfd, buf, n) != n)
293			fprint(2, "write to irclog: %r\n");
294		if(write(ircfd, buf, n) != n)
295			fprint(2, "write to ircserver: %r\n");
296		qunlock(&lck);
297	}
298	killall();
299}
300
301void
302joinhandler(char *buf)
303{
304	char *toks[4];
305	char *crap;
306
307	if(strlen(buf) > 4){
308		crap = strdup(buf);
309		crap[strlen(crap)-2] = '\0';
310		if(tokenize(crap, toks, 4) >= 2)
311			if(strncmp(crap, "JOIN", 4) == 0)
312				if(tchans < 256){
313					channels[tchans] = strdup(toks[1]);
314					tchans++;
315				}
316		free(crap);
317	}
318}