]> jfr.im git - irc/quakenet/newserv.git/blob - irc/irc.c
f4b19735db3b2c1f300985190dc920b52b056e1d
[irc/quakenet/newserv.git] / irc / irc.c
1 /* irc.c: handle the IRC interface */
2
3 #include "irc.h"
4 #include "irc_config.h"
5 #include "../parser/parser.h"
6 #include "../core/events.h"
7 #include "../core/schedule.h"
8 #include "../core/error.h"
9 #include "../core/config.h"
10 #include "../core/hooks.h"
11 #include "../lib/base64.h"
12 #include "../lib/splitline.h"
13 #include "../lib/version.h"
14 #include "../lib/irc_string.h"
15 #include <sys/poll.h>
16 #include <sys/types.h>
17 #include <sys/socket.h>
18 #include <netdb.h>
19 #include <stdarg.h>
20 #include <time.h>
21 #include <string.h>
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <netinet/in.h>
26
27 MODULE_VERSION("");
28
29 #define READBUFSIZE 32768
30 #define MAX_SERVERARGS 20
31 #define MIN_NUMERIC 100
32 #define MAX_NUMERIC 999
33
34 void irc_connect(void *arg);
35 void ircstats(int hooknum, void *arg);
36
37 CommandTree *servercommands;
38 Command *numericcommands[MAX_NUMERIC-MIN_NUMERIC];
39 int serverfd;
40 char inbuf[READBUFSIZE];
41 char *nextline;
42 char *args[MAX_SERVERARGS];
43 int bytesleft;
44 int linesreceived;
45
46 sstring *mynumeric;
47 sstring *myserver;
48 long mylongnum;
49
50 time_t starttime;
51 time_t timeoffset;
52 int awaitingping;
53 int connected;
54
55 void _init() {
56 servercommands=newcommandtree();
57 starttime=time(NULL);
58
59 connected=0;
60 timeoffset=0;
61
62 /* These values cannot be changed whilst the IRC module is running */
63 mynumeric=getcopyconfigitem("irc","servernumeric","A]",2);
64 myserver=getcopyconfigitem("irc","servername","services.lame.net",HOSTLEN);
65
66 mylongnum=numerictolong(mynumeric->content,2);
67
68 /* Schedule a connection to the IRC server */
69 scheduleoneshot(time(NULL),&irc_connect,NULL);
70
71 registerserverhandler("G",&handleping,1);
72 registerserverhandler("SE",&handlesettime,1);
73 registerserverhandler("Z",&handlepingreply,1);
74 registerserverhandler("SERVER",&irc_handleserver,8);
75
76 registerhook(HOOK_CORE_STATSREQUEST,&ircstats);
77 }
78
79 void _fini() {
80 if (connected) {
81 irc_send("%s SQ %s 0 :Shutting down",mynumeric->content,myserver->content);
82 irc_disconnected();
83 }
84
85 deregisterserverhandler("G",&handleping);
86 deregisterserverhandler("SE",&handlesettime);
87 deregisterserverhandler("Z",&handlepingreply);
88 deregisterserverhandler("SERVER",&irc_handleserver);
89
90 deregisterhook(HOOK_CORE_STATSREQUEST,&ircstats);
91
92 deleteschedule(NULL,&sendping,NULL);
93 deleteschedule(NULL,&irc_connect,NULL);
94
95 freesstring(mynumeric);
96 freesstring(myserver);
97
98 destroycommandtree(servercommands);
99 }
100
101 void irc_connect(void *arg) {
102 struct sockaddr_in sockaddress;
103 struct hostent *host;
104 sstring *conto,*conport,*conpass;
105 sstring *mydesc, *pingfreq;
106 long portnum;
107 socklen_t opt=1460;
108
109 nextline=inbuf;
110 bytesleft=0;
111 linesreceived=0;
112 awaitingping=0;
113
114 conto=getcopyconfigitem("irc","hubhost","127.0.0.1",HOSTLEN);
115 conport=getcopyconfigitem("irc","hubport","4400",7);
116 conpass=getcopyconfigitem("irc","hubpass","erik",20);
117
118 mydesc=getcopyconfigitem("irc","serverdescription","newserv 0.01",100);
119
120 pingfreq=getcopyconfigitem("irc","pingfreq","90",10);
121
122 portnum=strtol(conport->content,NULL,10);
123
124 serverfd = socket(PF_INET, SOCK_STREAM, 0);
125 if (serverfd == -1) {
126 Error("irc",ERR_FATAL,"Couldn't create socket.");
127 exit(1);
128 }
129
130 sockaddress.sin_family = AF_INET;
131 sockaddress.sin_port = htons(portnum);
132 host = gethostbyname(conto->content);
133 if (!host) {
134 Error("irc",ERR_FATAL,"Couldn't resolve host %s.",conto->content);
135 exit(1);
136 }
137 memcpy(&sockaddress.sin_addr, host->h_addr, sizeof(struct in_addr));
138
139 if (setsockopt(serverfd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt))) {
140 Error("irc",ERR_WARNING,"Error setting socket buffer.");
141 }
142
143 if (connect(serverfd, (struct sockaddr *) &sockaddress, sizeof(struct sockaddr_in)) == -1) {
144 Error("irc",ERR_ERROR,"Couldn't connect to %s:%s, will retry in one minute",conto->content,conport->content);
145 scheduleoneshot(time(NULL)+60,&irc_connect,NULL);
146 close(serverfd);
147 return;
148 }
149
150 Error("irc",ERR_INFO,"Connecting to %s:%s",conto->content,conport->content);
151
152 irc_send("PASS :%s",conpass->content);
153 irc_send("SERVER %s 1 %ld %ld J10 %s%s +sh6 :%s",myserver->content,starttime,time(NULL),mynumeric->content,longtonumeric(MAXLOCALUSER,3),mydesc->content);
154
155 registerhandler(serverfd, POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL, &handledata);
156
157 /* Schedule our ping requests. Note that this will also server
158 * to time out a failed connection.. */
159
160 schedulerecurring(time(NULL)+strtol(pingfreq->content,NULL,10),0,
161 strtol(pingfreq->content,NULL,10),&sendping,NULL);
162
163 freesstring(conto);
164 freesstring(conport);
165 freesstring(conpass);
166 freesstring(mydesc);
167 freesstring(pingfreq);
168 }
169
170 int irc_handleserver(void *source, int cargc, char **cargv) {
171 if (!strncmp((char *)source,"INIT",4) && !connected) {
172 /* OK, this is the SERVER response back from the remote server */
173 /* This completes the connection process. */
174 Error("irc",ERR_INFO,"Connection accepted by %s",cargv[0]);
175
176 /* Fix our timestamps before we do anything else */
177 setnettime(strtol(cargv[3],NULL,10));
178
179 connected=1;
180 triggerhook(HOOK_IRC_SENDBURSTSERVERS,NULL);
181 triggerhook(HOOK_IRC_SENDBURSTNICKS,NULL);
182 triggerhook(HOOK_IRC_SENDBURSTBURSTS,NULL);
183 irc_send("%s EB",mynumeric->content);
184
185 triggerhook(HOOK_IRC_CONNECTED,NULL);
186 } else {
187 Error("irc",ERR_INFO,"Unexpected SERVER message");
188 }
189
190 return CMD_OK;
191 }
192
193 void irc_disconnected() {
194 if (serverfd>=0) {
195 deregisterhandler(serverfd,1);
196 }
197 serverfd=-1;
198 if (connected) {
199 connected=0;
200
201 deleteschedule(NULL,&irc_connect,NULL);
202 deleteschedule(NULL,&sendping,NULL);
203 scheduleoneshot(time(NULL)+2,&irc_connect,NULL);
204 triggerhook(HOOK_IRC_PRE_DISCON,NULL);
205 triggerhook(HOOK_IRC_DISCON,NULL);
206 }
207 }
208
209 void irc_send(char *format, ... ) {
210 char buf[512];
211 va_list val;
212 int len;
213
214 va_start(val,format);
215 len=vsnprintf(buf,509,format,val);
216 va_end(val);
217
218 if (len>509 || len<0) {
219 len=509;
220 }
221
222 buf[len++]='\r';
223 buf[len++]='\n';
224
225 write(serverfd,buf,len);
226 }
227
228 void handledata(int fd, short events) {
229 int res;
230 int again=1;
231
232 if (events & (POLLPRI | POLLERR | POLLHUP | POLLNVAL)) {
233 /* Oh shit, we got dropped */
234 Error("irc",ERR_ERROR,"Got socket error, dropping connection.");
235 irc_disconnected();
236 return;
237 }
238
239 while(again) {
240 again=0;
241
242 res=read(serverfd, inbuf+bytesleft, READBUFSIZE-bytesleft);
243 if (res<=0) {
244 Error("irc",ERR_ERROR,"Disconnected by remote server.");
245 irc_disconnected();
246 return;
247 }
248
249 again=((bytesleft+=res)==READBUFSIZE);
250 while (!parseline())
251 ; /* empty loop */
252
253 memmove(inbuf, nextline, bytesleft);
254 nextline=inbuf;
255 }
256 }
257
258 /*
259 * Parse and dispatch a line from the IRC server
260 *
261 * Returns:
262 * 0: found at least one complete line in buffer
263 * 1: no complete line found in buffer
264 *
265 * Note that this returns 0 even if it finds a line which wasn't handled.
266 * The return code is just used to determine when there are no more lines
267 * to parse.
268 */
269
270 int parseline() {
271 char *currentline=nextline;
272 int foundcmd=0;
273 int cargc;
274 char *cargv[MAX_SERVERARGS];
275 Command *c;
276
277 while (bytesleft-->0) {
278 if (*nextline=='\r' || *nextline=='\n' || *nextline=='\0') {
279 /* Found a newline character. Replace it with \0 */
280 /* If we found some non-newline characters first, we need to parse this */
281 /* Otherwise, don't bother and just skip ahead */
282 if (currentline==nextline) {
283 *nextline++='\0';
284 currentline=nextline;
285 } else {
286 *nextline++='\0';
287 foundcmd=1;
288 break;
289 }
290 } else {
291 nextline++;
292 }
293 }
294
295 if (foundcmd==0) {
296 bytesleft=(nextline-currentline);
297 nextline=currentline;
298 return 1;
299 }
300
301 /* OK, currentline points at a valid NULL-terminated line */
302 /* and nextline points at where we are going next */
303
304 linesreceived++;
305
306 /* Split it up */
307 cargc=splitline(currentline,cargv,MAX_SERVERARGS,1);
308
309 if (cargc<2) {
310 /* Less than two arguments? Not a valid command, sir */
311 return 0;
312 }
313
314 if (linesreceived<3) {
315 /* Special-case the first two lines
316 * These CANNOT be numeric responses,
317 * and the command is the FIRST thing on the line */
318 if ((c=findcommandintree(servercommands,cargv[0],1))==NULL) {
319 /* No handler, return. */
320 return 0;
321 }
322 for (;c!=NULL;c=c->next) {
323 if (((c->handler)("INIT",cargc-1,&cargv[1]))==CMD_LAST)
324 return 0;
325 }
326 } else {
327 if (cargv[1][0]>='0' && cargv[1][0]<='9') {
328 /* It's a numeric! */
329 long numeric = strtol(cargv[1], NULL, 0);
330 if((numeric >= MIN_NUMERIC) && (numeric <= MAX_NUMERIC)) {
331 for(c=numericcommands[numeric];c;c=c->next) {
332 if (((c->handler)((void *)numeric,cargc,cargv))==CMD_LAST)
333 return 0;
334 }
335 }
336 } else {
337 if ((c=findcommandintree(servercommands,cargv[1],1))==NULL) {
338 /* We don't have a handler for this command */
339 return 0;
340 }
341 for (;c!=NULL;c=c->next) {
342 if (((c->handler)(cargv[0],cargc-2,cargv+2))==CMD_LAST)
343 return 0;
344 }
345 }
346 }
347 return 0;
348 }
349
350 int registerserverhandler(const char *command, CommandHandler handler, int maxparams) {
351 if ((addcommandtotree(servercommands,command,0,maxparams,handler))==NULL)
352 return 1;
353
354 return 0;
355 }
356
357 int deregisterserverhandler(const char *command, CommandHandler handler) {
358 return deletecommandfromtree(servercommands, command, handler);
359 }
360
361 int registernumerichandler(const int numeric, CommandHandler handler, int maxparams) {
362 Command *cp, *np;
363 if((numeric < MIN_NUMERIC) || (numeric > MAX_NUMERIC))
364 return 1;
365
366 /* doesn't happen that often */
367 np = (Command *)malloc(sizeof(Command));
368 np->handler = handler;
369 np->next = NULL;
370
371 /* I know I could just add to the beginning, but I guess since we have that LAST stuff
372 * it should go at the end */
373 if(!(cp = numericcommands[numeric])) {
374 numericcommands[numeric] = np;
375 } else {
376 for(;cp->next;cp=cp->next);
377 /* empty loop */
378
379 cp->next = np;
380 }
381
382 return 0;
383 }
384
385 int deregisternumerichandler(const int numeric, CommandHandler handler) {
386 Command *cp, *lp;
387 if((numeric < MIN_NUMERIC) || (numeric > MAX_NUMERIC))
388 return 1;
389
390 for(cp=numericcommands[numeric],lp=NULL;cp;lp=cp,cp=cp->next) {
391 if(cp->handler == handler) {
392 if(lp) {
393 lp->next = cp->next;
394 } else {
395 numericcommands[numeric] = cp->next;
396 }
397 free(cp);
398 return 0;
399 }
400 }
401
402 return 1;
403 }
404
405 char *getmynumeric() {
406 return mynumeric->content;
407 }
408
409 time_t getnettime() {
410 return (time(NULL)+timeoffset);
411 }
412
413 void setnettime(time_t newtime) {
414 timeoffset=newtime-time(NULL);
415 Error("irc",ERR_INFO,"setnettime: Time offset is now %d",timeoffset);
416 }
417
418 int handleping(void *sender, int cargc, char **cargv) {
419 if (cargc>0) {
420 irc_send("%s Z %s",mynumeric->content,cargv[cargc-1]);
421 }
422 return CMD_OK;
423 }
424
425 int handlesettime(void *sender, int cargc, char **cargv) {
426 time_t newtime;
427
428 if (cargc>0) {
429 newtime=strtol(cargv[0],NULL,10);
430 Error("irc",ERR_INFO,"Received settime: %lu (current time is %lu, offset %ld)",newtime,getnettime(),newtime-getnettime());
431 setnettime(newtime);
432 }
433 return CMD_OK;
434 }
435
436 void sendping(void *arg) {
437 if (connected) {
438 if (awaitingping==1) {
439 /* We didn't get a ping reply, kill the connection */
440 irc_send("%s SQ %s 0 :Ping timeout",mynumeric->content,myserver->content);
441 irc_disconnected();
442 } else {
443 awaitingping=1;
444 irc_send("%s G :%s",mynumeric->content,myserver->content);
445 }
446 } else {
447 /* We tried to send a ping when we weren't connected.. */
448 Error("irc",ERR_INFO,"Connection timed out.");
449 connected=1; /* EVIL HACK */
450 irc_disconnected();
451 }
452 }
453
454 int handlepingreply(void *sender, int cargc, char **cargv) {
455 awaitingping=0;
456 return CMD_OK;
457 }
458
459
460 void ircstats(int hooknum, void *arg) {
461 long level=(long)arg;
462 char buf[100];
463
464 if (level>5) {
465 sprintf(buf,"irc : start time %lu (running %s)", starttime,longtoduration(time(NULL)-starttime,0));
466 triggerhook(HOOK_CORE_STATSREPLY,buf);
467 sprintf(buf,"Time : %lu (current time is %lu, offset %ld)",getnettime(),time(NULL),timeoffset);
468 triggerhook(HOOK_CORE_STATSREPLY,buf);
469 }
470 }