]> jfr.im git - irc/quakenet/newserv.git/blame - irc/irc.c
fixes for clang
[irc/quakenet/newserv.git] / irc / irc.c
CommitLineData
c86edd1d
Q
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"
87698d77 13#include "../lib/version.h"
0850681b 14#include "../lib/irc_string.h"
9821c06c 15#include "../lib/strlfunc.h"
c86edd1d
Q
16#include <sys/poll.h>
17#include <sys/types.h>
18#include <sys/socket.h>
19#include <netdb.h>
20#include <stdarg.h>
21#include <time.h>
22#include <string.h>
23#include <stdio.h>
24#include <unistd.h>
25#include <stdlib.h>
26#include <netinet/in.h>
27
70b0a4e5 28MODULE_VERSION("");
87698d77 29
c86edd1d
Q
30#define READBUFSIZE 32768
31#define MAX_SERVERARGS 20
32#define MIN_NUMERIC 100
33#define MAX_NUMERIC 999
34
35void irc_connect(void *arg);
0850681b 36void ircstats(int hooknum, void *arg);
971bb4b4
CP
37void checkhubconfig(void);
38void ircrehash(int hooknum, void *arg);
c86edd1d
Q
39
40CommandTree *servercommands;
41Command *numericcommands[MAX_NUMERIC-MIN_NUMERIC];
42int serverfd;
43char inbuf[READBUFSIZE];
44char *nextline;
45char *args[MAX_SERVERARGS];
46int bytesleft;
47int linesreceived;
48
49sstring *mynumeric;
50sstring *myserver;
51long mylongnum;
52
53time_t starttime;
54time_t timeoffset;
55int awaitingping;
56int connected;
57
971bb4b4
CP
58static int hubnum, hubcount, previouslyconnected = 0;
59static sstring **hublist;
60
c86edd1d
Q
61void _init() {
62 servercommands=newcommandtree();
63 starttime=time(NULL);
64
65 connected=0;
66 timeoffset=0;
67
68 /* These values cannot be changed whilst the IRC module is running */
69 mynumeric=getcopyconfigitem("irc","servernumeric","A]",2);
70 myserver=getcopyconfigitem("irc","servername","services.lame.net",HOSTLEN);
71
72 mylongnum=numerictolong(mynumeric->content,2);
971bb4b4
CP
73
74 checkhubconfig();
75
c86edd1d
Q
76 /* Schedule a connection to the IRC server */
77 scheduleoneshot(time(NULL),&irc_connect,NULL);
78
79 registerserverhandler("G",&handleping,1);
80 registerserverhandler("SE",&handlesettime,1);
81 registerserverhandler("Z",&handlepingreply,1);
82 registerserverhandler("SERVER",&irc_handleserver,8);
0850681b
P
83
84 registerhook(HOOK_CORE_STATSREQUEST,&ircstats);
971bb4b4 85 registerhook(HOOK_CORE_REHASH,&ircrehash);
c86edd1d
Q
86}
87
88void _fini() {
89 if (connected) {
90 irc_send("%s SQ %s 0 :Shutting down",mynumeric->content,myserver->content);
91 irc_disconnected();
92 }
93
94 deregisterserverhandler("G",&handleping);
95 deregisterserverhandler("SE",&handlesettime);
96 deregisterserverhandler("Z",&handlepingreply);
97 deregisterserverhandler("SERVER",&irc_handleserver);
0850681b
P
98
99 deregisterhook(HOOK_CORE_STATSREQUEST,&ircstats);
971bb4b4 100 deregisterhook(HOOK_CORE_REHASH,&ircrehash);
c86edd1d
Q
101
102 deleteschedule(NULL,&sendping,NULL);
103 deleteschedule(NULL,&irc_connect,NULL);
104
105 freesstring(mynumeric);
106 freesstring(myserver);
107
108 destroycommandtree(servercommands);
109}
110
971bb4b4
CP
111void resethubnum(void) {
112 hubnum=0;
113}
114
115void nexthub(void) {
116 /*
117 * this is set if the connection before this (failed one)
118 * was successful, so we attempted reconnecting to the
119 * same server, and now we've failed, so reset to the first
120 * which should be the preferred one.
121 */
122
123 if (previouslyconnected) {
124 previouslyconnected=0;
125 if((hubcount<2) || (hubnum!=0)) {
126 hubnum=0;
127 } else {
128 hubnum=1;
129 }
130 return;
131 }
132
133 /* 1 % 0 is fun */
134 if (hubcount>0)
135 hubnum=(hubnum+1)%hubcount;
136}
137
138int gethub(int servernum, char **conto, long *portnum, char **conpass, long *pingfreq) {
139 sstring *conto_s, *portnum_s, *conpass_s, *pingfreq_s;
140 static char conto_b[512], conpass_b[512];
141 int ret, s;
142 char *section;
143
144 if (hubcount) {
145 static char realsection[512];
146 snprintf(realsection,sizeof(realsection),"hub-%s",hublist[servernum]->content);
147 section=realsection;
148 s=1;
149 } else {
150 section="irc";
151 s=0;
152 }
153
154 conto_s=getconfigitem(section,s?"host":"hubhost");
155 if (!conto_s || !conto_s->content || !conto_s->content[0]) {
156 ret=0;
157 } else {
158 ret=1;
159 }
160
161 if (conto) {
162 conto_s=getcopyconfigitem(section,s?"host":"hubhost","127.0.0.1",HOSTLEN);
163 strlcpy(conto_b, conto_s->content, sizeof(conto_b));
164 *conto=conto_b;
165
166 freesstring(conto_s);
167 }
168
169 if (portnum) {
170 portnum_s=getcopyconfigitem(section,s?"port":"hubport","4400",7);
171 *portnum=strtol(portnum_s->content,NULL,10);
172
173 freesstring(portnum_s);
174 }
175
176 if (conpass) {
177 conpass_s=getcopyconfigitem(section,s?"pass":"hubpass","erik",20);
178 strlcpy(conpass_b, conpass_s->content, sizeof(conpass_b));
179 *conpass = conpass_b;
180
181 freesstring(conpass_s);
182 }
183
184 if (pingfreq) {
185 pingfreq_s=getcopyconfigitem(section,"pingfreq","90",10);
186 *pingfreq=strtol(pingfreq_s->content,NULL,10);
187
188 freesstring(pingfreq_s);
189 }
190
191 return ret;
192}
193
194/* we check at startup that (most things) resolve */
195void checkhubconfig(void) {
196 array *servers_ar;
197 int i;
198
199 resethubnum();
200 hubcount=0;
201
202 servers_ar=getconfigitems("irc", "hub");
203
204 /*
205 * we don't bother doing the gethostbyname check for
206 * the old style of configuration, as it'll
207 * be checked by irc_connect anyway.
208 */
209 if (!servers_ar || !servers_ar->cursi) {
210 Error("irc",ERR_WARNING,"Using legacy hub configuration format.");
211 return;
212 }
213
214 hublist=(sstring **)servers_ar->content;
215 hubcount=servers_ar->cursi;
216
217 for (i=0;i<hubcount;i++) {
218 char *conto;
219
220 if (!gethub(i, &conto, NULL, NULL, NULL)) {
221 Error("irc",ERR_FATAL,"No server configuration specified for '%s'.",hublist[i]->content);
222 exit(1);
223 }
224
225 if (!gethostbyname(conto)) {
226 Error("irc",ERR_FATAL,"Couldn't resolve host %s.",conto);
227 exit(1);
228 }
229 }
230}
231
232void ircrehash(int hookhum, void *arg) {
233 checkhubconfig();
234}
235
c86edd1d
Q
236void irc_connect(void *arg) {
237 struct sockaddr_in sockaddress;
238 struct hostent *host;
971bb4b4
CP
239 sstring *mydesc;
240 char *conto,*conpass;
241 long portnum,pingfreq;
0555113a 242/* socklen_t opt=1460;*/
c86edd1d
Q
243
244 nextline=inbuf;
245 bytesleft=0;
246 linesreceived=0;
247 awaitingping=0;
c86edd1d 248
971bb4b4 249 gethub(hubnum, &conto, &portnum, &conpass, &pingfreq);
c86edd1d 250
971bb4b4 251 mydesc=getcopyconfigitem("irc","serverdescription","newserv 0.01",100);
c86edd1d
Q
252
253 serverfd = socket(PF_INET, SOCK_STREAM, 0);
254 if (serverfd == -1) {
255 Error("irc",ERR_FATAL,"Couldn't create socket.");
256 exit(1);
257 }
258
259 sockaddress.sin_family = AF_INET;
260 sockaddress.sin_port = htons(portnum);
971bb4b4 261 host = gethostbyname(conto);
c86edd1d 262 if (!host) {
971bb4b4 263 Error("irc",ERR_FATAL,"Couldn't resolve host %s.",conto);
c86edd1d
Q
264 exit(1);
265 }
8b8e6c68
CP
266
267#if !defined(h_addr) /* h_addr is deprecated */
268 memcpy(&sockaddress.sin_addr, host->h_addr_list[0], sizeof(struct in_addr));
269#else
c86edd1d 270 memcpy(&sockaddress.sin_addr, host->h_addr, sizeof(struct in_addr));
8b8e6c68 271#endif
c86edd1d 272
af076c80 273/* if (setsockopt(serverfd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt))) {
c86edd1d
Q
274 Error("irc",ERR_WARNING,"Error setting socket buffer.");
275 }
d731c7d8
CP
276 */
277
16739dbe 278 Error("irc",ERR_INFO,"Connecting to %s:%lu",conto,portnum);
971bb4b4 279
c86edd1d 280 if (connect(serverfd, (struct sockaddr *) &sockaddress, sizeof(struct sockaddr_in)) == -1) {
971bb4b4 281 nexthub();
16739dbe 282 Error("irc",ERR_ERROR,"Couldn't connect to %s:%lu, will try next server in one minute",conto,portnum);
c86edd1d
Q
283 scheduleoneshot(time(NULL)+60,&irc_connect,NULL);
284 close(serverfd);
80ff7065 285 freesstring(mydesc);
c86edd1d
Q
286 return;
287 }
288
971bb4b4 289 irc_send("PASS :%s",conpass);
4f2ceb49 290 /* remember when changing modes to change server/server.c too */
843184e3 291 irc_send("SERVER %s 1 %ld %ld J10 %s%s +sh6n :%s",myserver->content,starttime,time(NULL),mynumeric->content,longtonumeric(MAXLOCALUSER,3),mydesc->content);
c86edd1d
Q
292
293 registerhandler(serverfd, POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL, &handledata);
294
295 /* Schedule our ping requests. Note that this will also server
296 * to time out a failed connection.. */
297
971bb4b4 298 schedulerecurring(time(NULL)+pingfreq,0,pingfreq,&sendping,NULL);
c86edd1d 299
c86edd1d 300 freesstring(mydesc);
c86edd1d
Q
301}
302
303int irc_handleserver(void *source, int cargc, char **cargv) {
304 if (!strncmp((char *)source,"INIT",4) && !connected) {
305 /* OK, this is the SERVER response back from the remote server */
306 /* This completes the connection process. */
307 Error("irc",ERR_INFO,"Connection accepted by %s",cargv[0]);
308
309 /* Fix our timestamps before we do anything else */
310 setnettime(strtol(cargv[3],NULL,10));
311
971bb4b4 312 previouslyconnected=1;
c86edd1d 313 connected=1;
c86edd1d
Q
314 } else {
315 Error("irc",ERR_INFO,"Unexpected SERVER message");
316 }
317
318 return CMD_OK;
319}
320
321void irc_disconnected() {
322 if (serverfd>=0) {
323 deregisterhandler(serverfd,1);
324 }
325 serverfd=-1;
326 if (connected) {
327 connected=0;
e609eb1f 328 triggerhook(HOOK_IRC_PRE_DISCON,NULL);
c86edd1d 329 triggerhook(HOOK_IRC_DISCON,NULL);
971bb4b4
CP
330 } else {
331 nexthub();
c86edd1d 332 }
e26df23c
C
333 deleteschedule(NULL,&irc_connect,NULL);
334 deleteschedule(NULL,&sendping,NULL);
335 scheduleoneshot(time(NULL)+2,&irc_connect,NULL);
c86edd1d
Q
336}
337
338void irc_send(char *format, ... ) {
339 char buf[512];
340 va_list val;
341 int len;
342
343 va_start(val,format);
344 len=vsnprintf(buf,509,format,val);
345 va_end(val);
346
347 if (len>509 || len<0) {
348 len=509;
349 }
350
351 buf[len++]='\r';
352 buf[len++]='\n';
353
354 write(serverfd,buf,len);
355}
356
357void handledata(int fd, short events) {
358 int res;
359 int again=1;
360
361 if (events & (POLLPRI | POLLERR | POLLHUP | POLLNVAL)) {
362 /* Oh shit, we got dropped */
363 Error("irc",ERR_ERROR,"Got socket error, dropping connection.");
364 irc_disconnected();
365 return;
366 }
367
368 while(again) {
369 again=0;
370
371 res=read(serverfd, inbuf+bytesleft, READBUFSIZE-bytesleft);
372 if (res<=0) {
373 Error("irc",ERR_ERROR,"Disconnected by remote server.");
374 irc_disconnected();
375 return;
376 }
377
378 again=((bytesleft+=res)==READBUFSIZE);
379 while (!parseline())
380 ; /* empty loop */
381
382 memmove(inbuf, nextline, bytesleft);
383 nextline=inbuf;
384 }
385}
386
387/*
388 * Parse and dispatch a line from the IRC server
389 *
390 * Returns:
391 * 0: found at least one complete line in buffer
392 * 1: no complete line found in buffer
393 *
394 * Note that this returns 0 even if it finds a line which wasn't handled.
395 * The return code is just used to determine when there are no more lines
396 * to parse.
397 */
398
399int parseline() {
400 char *currentline=nextline;
401 int foundcmd=0;
402 int cargc;
403 char *cargv[MAX_SERVERARGS];
404 Command *c;
405
406 while (bytesleft-->0) {
407 if (*nextline=='\r' || *nextline=='\n' || *nextline=='\0') {
408 /* Found a newline character. Replace it with \0 */
409 /* If we found some non-newline characters first, we need to parse this */
410 /* Otherwise, don't bother and just skip ahead */
411 if (currentline==nextline) {
412 *nextline++='\0';
413 currentline=nextline;
414 } else {
415 *nextline++='\0';
416 foundcmd=1;
417 break;
418 }
419 } else {
420 nextline++;
421 }
422 }
423
424 if (foundcmd==0) {
425 bytesleft=(nextline-currentline);
426 nextline=currentline;
427 return 1;
428 }
429
430 /* OK, currentline points at a valid NULL-terminated line */
431 /* and nextline points at where we are going next */
432
433 linesreceived++;
434
435 /* Split it up */
436 cargc=splitline(currentline,cargv,MAX_SERVERARGS,1);
437
438 if (cargc<2) {
439 /* Less than two arguments? Not a valid command, sir */
440 return 0;
441 }
442
443 if (linesreceived<3) {
444 /* Special-case the first two lines
445 * These CANNOT be numeric responses,
446 * and the command is the FIRST thing on the line */
447 if ((c=findcommandintree(servercommands,cargv[0],1))==NULL) {
448 /* No handler, return. */
449 return 0;
450 }
451 for (;c!=NULL;c=c->next) {
09285ac1 452 c->calls++;
c86edd1d
Q
453 if (((c->handler)("INIT",cargc-1,&cargv[1]))==CMD_LAST)
454 return 0;
455 }
456 } else {
457 if (cargv[1][0]>='0' && cargv[1][0]<='9') {
458 /* It's a numeric! */
c3db6f7e 459 long numeric = strtol(cargv[1], NULL, 0);
16d29ce2
CP
460 if((numeric >= MIN_NUMERIC) && (numeric <= MAX_NUMERIC)) {
461 for(c=numericcommands[numeric];c;c=c->next) {
09285ac1 462 c->calls++;
16d29ce2
CP
463 if (((c->handler)((void *)numeric,cargc,cargv))==CMD_LAST)
464 return 0;
465 }
466 }
c86edd1d
Q
467 } else {
468 if ((c=findcommandintree(servercommands,cargv[1],1))==NULL) {
469 /* We don't have a handler for this command */
470 return 0;
471 }
472 for (;c!=NULL;c=c->next) {
09285ac1 473 c->calls++;
c86edd1d
Q
474 if (((c->handler)(cargv[0],cargc-2,cargv+2))==CMD_LAST)
475 return 0;
476 }
477 }
478 }
479 return 0;
480}
481
482int registerserverhandler(const char *command, CommandHandler handler, int maxparams) {
483 if ((addcommandtotree(servercommands,command,0,maxparams,handler))==NULL)
484 return 1;
485
486 return 0;
487}
488
489int deregisterserverhandler(const char *command, CommandHandler handler) {
490 return deletecommandfromtree(servercommands, command, handler);
491}
492
16d29ce2
CP
493int registernumerichandler(const int numeric, CommandHandler handler, int maxparams) {
494 Command *cp, *np;
495 if((numeric < MIN_NUMERIC) || (numeric > MAX_NUMERIC))
496 return 1;
497
498 /* doesn't happen that often */
499 np = (Command *)malloc(sizeof(Command));
500 np->handler = handler;
501 np->next = NULL;
502
503 /* I know I could just add to the beginning, but I guess since we have that LAST stuff
504 * it should go at the end */
505 if(!(cp = numericcommands[numeric])) {
506 numericcommands[numeric] = np;
507 } else {
508 for(;cp->next;cp=cp->next);
509 /* empty loop */
510
511 cp->next = np;
512 }
513
514 return 0;
515}
516
517int deregisternumerichandler(const int numeric, CommandHandler handler) {
518 Command *cp, *lp;
519 if((numeric < MIN_NUMERIC) || (numeric > MAX_NUMERIC))
520 return 1;
521
522 for(cp=numericcommands[numeric],lp=NULL;cp;lp=cp,cp=cp->next) {
523 if(cp->handler == handler) {
524 if(lp) {
525 lp->next = cp->next;
526 } else {
527 numericcommands[numeric] = cp->next;
528 }
529 free(cp);
530 return 0;
531 }
532 }
533
534 return 1;
535}
536
c86edd1d
Q
537char *getmynumeric() {
538 return mynumeric->content;
539}
540
541time_t getnettime() {
542 return (time(NULL)+timeoffset);
543}
544
545void setnettime(time_t newtime) {
546 timeoffset=newtime-time(NULL);
70e589ed 547 Error("irc",ERR_INFO,"setnettime: Time offset is now %ld",timeoffset);
c86edd1d
Q
548}
549
550int handleping(void *sender, int cargc, char **cargv) {
551 if (cargc>0) {
552 irc_send("%s Z %s",mynumeric->content,cargv[cargc-1]);
553 }
554 return CMD_OK;
555}
556
557int handlesettime(void *sender, int cargc, char **cargv) {
558 time_t newtime;
559
560 if (cargc>0) {
561 newtime=strtol(cargv[0],NULL,10);
562 Error("irc",ERR_INFO,"Received settime: %lu (current time is %lu, offset %ld)",newtime,getnettime(),newtime-getnettime());
563 setnettime(newtime);
564 }
565 return CMD_OK;
566}
567
568void sendping(void *arg) {
569 if (connected) {
570 if (awaitingping==1) {
571 /* We didn't get a ping reply, kill the connection */
eb167f60
CP
572 Error("irc",ERR_INFO,"Connection closed due to ping timeout.");
573
c86edd1d
Q
574 irc_send("%s SQ %s 0 :Ping timeout",mynumeric->content,myserver->content);
575 irc_disconnected();
576 } else {
577 awaitingping=1;
578 irc_send("%s G :%s",mynumeric->content,myserver->content);
579 }
580 } else {
581 /* We tried to send a ping when we weren't connected.. */
582 Error("irc",ERR_INFO,"Connection timed out.");
971bb4b4
CP
583
584 /* have to have this here because of the EVIL HACK below */
585 nexthub();
586
c86edd1d
Q
587 connected=1; /* EVIL HACK */
588 irc_disconnected();
589 }
590}
591
592int handlepingreply(void *sender, int cargc, char **cargv) {
593 awaitingping=0;
594 return CMD_OK;
595}
0850681b
P
596
597
598void ircstats(int hooknum, void *arg) {
599 long level=(long)arg;
600 char buf[100];
601
602 if (level>5) {
603 sprintf(buf,"irc : start time %lu (running %s)", starttime,longtoduration(time(NULL)-starttime,0));
604 triggerhook(HOOK_CORE_STATSREPLY,buf);
605 sprintf(buf,"Time : %lu (current time is %lu, offset %ld)",getnettime(),time(NULL),timeoffset);
606 triggerhook(HOOK_CORE_STATSREPLY,buf);
607 }
608}
7bec4aeb 609
610
611/* list stats commands / m to a user
612 *
7bec4aeb 613 * sourcenum numeric of the user requesting the listing
614 */
a7697869 615void stats_commands(char *sourcenum) {
09285ac1 616 Command *cmds[500];
617 unsigned int c,i;
618
619 c=getcommandlist(servercommands,cmds,500);
620
621 for (i=0;i<c;i++) {
7bec4aeb 622 /*
623 * 212 RPL_STATSCOMMANDS "source 212 target command used_count bytes_count"
624 * "irc.netsplit.net 212 foobar ACCOUNT 41 462"
625 */
a7697869 626 irc_send("%s 212 %s %s %u 0", getmynumeric(), sourcenum, cmds[i]->command->content, cmds[i]->calls);
09285ac1 627 }
628}