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}