]> jfr.im git - irc/quakenet/newserv.git/blob - irc/irc.c
72c8c8b621ad835177b9666eb317df3e6a3dce05
[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 /* remember when changing modes to change server/server.c too */
154 irc_send("SERVER %s 1 %ld %ld J10 %s%s +sh6n :%s",myserver->content,starttime,time(NULL),mynumeric->content,longtonumeric(MAXLOCALUSER,3),mydesc->content);
155
156 registerhandler(serverfd, POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL, &handledata);
157
158 /* Schedule our ping requests. Note that this will also server
159 * to time out a failed connection.. */
160
161 schedulerecurring(time(NULL)+strtol(pingfreq->content,NULL,10),0,
162 strtol(pingfreq->content,NULL,10),&sendping,NULL);
163
164 freesstring(conto);
165 freesstring(conport);
166 freesstring(conpass);
167 freesstring(mydesc);
168 freesstring(pingfreq);
169 }
170
171 int irc_handleserver(void *source, int cargc, char **cargv) {
172 if (!strncmp((char *)source,"INIT",4) && !connected) {
173 /* OK, this is the SERVER response back from the remote server */
174 /* This completes the connection process. */
175 Error("irc",ERR_INFO,"Connection accepted by %s",cargv[0]);
176
177 /* Fix our timestamps before we do anything else */
178 setnettime(strtol(cargv[3],NULL,10));
179
180 connected=1;
181 } else {
182 Error("irc",ERR_INFO,"Unexpected SERVER message");
183 }
184
185 return CMD_OK;
186 }
187
188 void irc_disconnected() {
189 if (serverfd>=0) {
190 deregisterhandler(serverfd,1);
191 }
192 serverfd=-1;
193 if (connected) {
194 connected=0;
195 triggerhook(HOOK_IRC_PRE_DISCON,NULL);
196 triggerhook(HOOK_IRC_DISCON,NULL);
197 }
198 deleteschedule(NULL,&irc_connect,NULL);
199 deleteschedule(NULL,&sendping,NULL);
200 scheduleoneshot(time(NULL)+2,&irc_connect,NULL);
201 }
202
203 void irc_send(char *format, ... ) {
204 char buf[512];
205 va_list val;
206 int len;
207
208 va_start(val,format);
209 len=vsnprintf(buf,509,format,val);
210 va_end(val);
211
212 if (len>509 || len<0) {
213 len=509;
214 }
215
216 buf[len++]='\r';
217 buf[len++]='\n';
218
219 write(serverfd,buf,len);
220 }
221
222 void handledata(int fd, short events) {
223 int res;
224 int again=1;
225
226 if (events & (POLLPRI | POLLERR | POLLHUP | POLLNVAL)) {
227 /* Oh shit, we got dropped */
228 Error("irc",ERR_ERROR,"Got socket error, dropping connection.");
229 irc_disconnected();
230 return;
231 }
232
233 while(again) {
234 again=0;
235
236 res=read(serverfd, inbuf+bytesleft, READBUFSIZE-bytesleft);
237 if (res<=0) {
238 Error("irc",ERR_ERROR,"Disconnected by remote server.");
239 irc_disconnected();
240 return;
241 }
242
243 again=((bytesleft+=res)==READBUFSIZE);
244 while (!parseline())
245 ; /* empty loop */
246
247 memmove(inbuf, nextline, bytesleft);
248 nextline=inbuf;
249 }
250 }
251
252 /*
253 * Parse and dispatch a line from the IRC server
254 *
255 * Returns:
256 * 0: found at least one complete line in buffer
257 * 1: no complete line found in buffer
258 *
259 * Note that this returns 0 even if it finds a line which wasn't handled.
260 * The return code is just used to determine when there are no more lines
261 * to parse.
262 */
263
264 int parseline() {
265 char *currentline=nextline;
266 int foundcmd=0;
267 int cargc;
268 char *cargv[MAX_SERVERARGS];
269 Command *c;
270
271 while (bytesleft-->0) {
272 if (*nextline=='\r' || *nextline=='\n' || *nextline=='\0') {
273 /* Found a newline character. Replace it with \0 */
274 /* If we found some non-newline characters first, we need to parse this */
275 /* Otherwise, don't bother and just skip ahead */
276 if (currentline==nextline) {
277 *nextline++='\0';
278 currentline=nextline;
279 } else {
280 *nextline++='\0';
281 foundcmd=1;
282 break;
283 }
284 } else {
285 nextline++;
286 }
287 }
288
289 if (foundcmd==0) {
290 bytesleft=(nextline-currentline);
291 nextline=currentline;
292 return 1;
293 }
294
295 /* OK, currentline points at a valid NULL-terminated line */
296 /* and nextline points at where we are going next */
297
298 linesreceived++;
299
300 /* Split it up */
301 cargc=splitline(currentline,cargv,MAX_SERVERARGS,1);
302
303 if (cargc<2) {
304 /* Less than two arguments? Not a valid command, sir */
305 return 0;
306 }
307
308 if (linesreceived<3) {
309 /* Special-case the first two lines
310 * These CANNOT be numeric responses,
311 * and the command is the FIRST thing on the line */
312 if ((c=findcommandintree(servercommands,cargv[0],1))==NULL) {
313 /* No handler, return. */
314 return 0;
315 }
316 for (;c!=NULL;c=c->next) {
317 if (((c->handler)("INIT",cargc-1,&cargv[1]))==CMD_LAST)
318 return 0;
319 }
320 } else {
321 if (cargv[1][0]>='0' && cargv[1][0]<='9') {
322 /* It's a numeric! */
323 long numeric = strtol(cargv[1], NULL, 0);
324 if((numeric >= MIN_NUMERIC) && (numeric <= MAX_NUMERIC)) {
325 for(c=numericcommands[numeric];c;c=c->next) {
326 if (((c->handler)((void *)numeric,cargc,cargv))==CMD_LAST)
327 return 0;
328 }
329 }
330 } else {
331 if ((c=findcommandintree(servercommands,cargv[1],1))==NULL) {
332 /* We don't have a handler for this command */
333 return 0;
334 }
335 for (;c!=NULL;c=c->next) {
336 if (((c->handler)(cargv[0],cargc-2,cargv+2))==CMD_LAST)
337 return 0;
338 }
339 }
340 }
341 return 0;
342 }
343
344 int registerserverhandler(const char *command, CommandHandler handler, int maxparams) {
345 if ((addcommandtotree(servercommands,command,0,maxparams,handler))==NULL)
346 return 1;
347
348 return 0;
349 }
350
351 int deregisterserverhandler(const char *command, CommandHandler handler) {
352 return deletecommandfromtree(servercommands, command, handler);
353 }
354
355 int registernumerichandler(const int numeric, CommandHandler handler, int maxparams) {
356 Command *cp, *np;
357 if((numeric < MIN_NUMERIC) || (numeric > MAX_NUMERIC))
358 return 1;
359
360 /* doesn't happen that often */
361 np = (Command *)malloc(sizeof(Command));
362 np->handler = handler;
363 np->next = NULL;
364
365 /* I know I could just add to the beginning, but I guess since we have that LAST stuff
366 * it should go at the end */
367 if(!(cp = numericcommands[numeric])) {
368 numericcommands[numeric] = np;
369 } else {
370 for(;cp->next;cp=cp->next);
371 /* empty loop */
372
373 cp->next = np;
374 }
375
376 return 0;
377 }
378
379 int deregisternumerichandler(const int numeric, CommandHandler handler) {
380 Command *cp, *lp;
381 if((numeric < MIN_NUMERIC) || (numeric > MAX_NUMERIC))
382 return 1;
383
384 for(cp=numericcommands[numeric],lp=NULL;cp;lp=cp,cp=cp->next) {
385 if(cp->handler == handler) {
386 if(lp) {
387 lp->next = cp->next;
388 } else {
389 numericcommands[numeric] = cp->next;
390 }
391 free(cp);
392 return 0;
393 }
394 }
395
396 return 1;
397 }
398
399 char *getmynumeric() {
400 return mynumeric->content;
401 }
402
403 time_t getnettime() {
404 return (time(NULL)+timeoffset);
405 }
406
407 void setnettime(time_t newtime) {
408 timeoffset=newtime-time(NULL);
409 Error("irc",ERR_INFO,"setnettime: Time offset is now %d",timeoffset);
410 }
411
412 int handleping(void *sender, int cargc, char **cargv) {
413 if (cargc>0) {
414 irc_send("%s Z %s",mynumeric->content,cargv[cargc-1]);
415 }
416 return CMD_OK;
417 }
418
419 int handlesettime(void *sender, int cargc, char **cargv) {
420 time_t newtime;
421
422 if (cargc>0) {
423 newtime=strtol(cargv[0],NULL,10);
424 Error("irc",ERR_INFO,"Received settime: %lu (current time is %lu, offset %ld)",newtime,getnettime(),newtime-getnettime());
425 setnettime(newtime);
426 }
427 return CMD_OK;
428 }
429
430 void sendping(void *arg) {
431 if (connected) {
432 if (awaitingping==1) {
433 /* We didn't get a ping reply, kill the connection */
434 irc_send("%s SQ %s 0 :Ping timeout",mynumeric->content,myserver->content);
435 irc_disconnected();
436 } else {
437 awaitingping=1;
438 irc_send("%s G :%s",mynumeric->content,myserver->content);
439 }
440 } else {
441 /* We tried to send a ping when we weren't connected.. */
442 Error("irc",ERR_INFO,"Connection timed out.");
443 connected=1; /* EVIL HACK */
444 irc_disconnected();
445 }
446 }
447
448 int handlepingreply(void *sender, int cargc, char **cargv) {
449 awaitingping=0;
450 return CMD_OK;
451 }
452
453
454 void ircstats(int hooknum, void *arg) {
455 long level=(long)arg;
456 char buf[100];
457
458 if (level>5) {
459 sprintf(buf,"irc : start time %lu (running %s)", starttime,longtoduration(time(NULL)-starttime,0));
460 triggerhook(HOOK_CORE_STATSREPLY,buf);
461 sprintf(buf,"Time : %lu (current time is %lu, offset %ld)",getnettime(),time(NULL),timeoffset);
462 triggerhook(HOOK_CORE_STATSREPLY,buf);
463 }
464 }