]> jfr.im git - irc/quakenet/newserv.git/blame - irc/irc.c
Compiler warning fixes
[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"
c86edd1d
Q
14#include <sys/poll.h>
15#include <sys/types.h>
16#include <sys/socket.h>
17#include <netdb.h>
18#include <stdarg.h>
19#include <time.h>
20#include <string.h>
21#include <stdio.h>
22#include <unistd.h>
23#include <stdlib.h>
24#include <netinet/in.h>
25
70b0a4e5 26MODULE_VERSION("");
87698d77 27
c86edd1d
Q
28#define READBUFSIZE 32768
29#define MAX_SERVERARGS 20
30#define MIN_NUMERIC 100
31#define MAX_NUMERIC 999
32
33void irc_connect(void *arg);
34
35CommandTree *servercommands;
36Command *numericcommands[MAX_NUMERIC-MIN_NUMERIC];
37int serverfd;
38char inbuf[READBUFSIZE];
39char *nextline;
40char *args[MAX_SERVERARGS];
41int bytesleft;
42int linesreceived;
43
44sstring *mynumeric;
45sstring *myserver;
46long mylongnum;
47
48time_t starttime;
49time_t timeoffset;
50int awaitingping;
51int connected;
52
53void _init() {
54 servercommands=newcommandtree();
55 starttime=time(NULL);
56
57 connected=0;
58 timeoffset=0;
59
60 /* These values cannot be changed whilst the IRC module is running */
61 mynumeric=getcopyconfigitem("irc","servernumeric","A]",2);
62 myserver=getcopyconfigitem("irc","servername","services.lame.net",HOSTLEN);
63
64 mylongnum=numerictolong(mynumeric->content,2);
65
66 /* Schedule a connection to the IRC server */
67 scheduleoneshot(time(NULL),&irc_connect,NULL);
68
69 registerserverhandler("G",&handleping,1);
70 registerserverhandler("SE",&handlesettime,1);
71 registerserverhandler("Z",&handlepingreply,1);
72 registerserverhandler("SERVER",&irc_handleserver,8);
73}
74
75void _fini() {
76 if (connected) {
77 irc_send("%s SQ %s 0 :Shutting down",mynumeric->content,myserver->content);
78 irc_disconnected();
79 }
80
81 deregisterserverhandler("G",&handleping);
82 deregisterserverhandler("SE",&handlesettime);
83 deregisterserverhandler("Z",&handlepingreply);
84 deregisterserverhandler("SERVER",&irc_handleserver);
85
86 deleteschedule(NULL,&sendping,NULL);
87 deleteschedule(NULL,&irc_connect,NULL);
88
89 freesstring(mynumeric);
90 freesstring(myserver);
91
92 destroycommandtree(servercommands);
93}
94
95void irc_connect(void *arg) {
96 struct sockaddr_in sockaddress;
97 struct hostent *host;
98 sstring *conto,*conport,*conpass;
99 sstring *mydesc, *pingfreq;
100 long portnum;
101 socklen_t opt=1460;
102
103 nextline=inbuf;
104 bytesleft=0;
105 linesreceived=0;
106 awaitingping=0;
107
108 conto=getcopyconfigitem("irc","hubhost","127.0.0.1",HOSTLEN);
109 conport=getcopyconfigitem("irc","hubport","4400",7);
110 conpass=getcopyconfigitem("irc","hubpass","erik",20);
111
112 mydesc=getcopyconfigitem("irc","serverdescription","newserv 0.01",100);
113
114 pingfreq=getcopyconfigitem("irc","pingfreq","90",10);
115
116 portnum=strtol(conport->content,NULL,10);
117
118 serverfd = socket(PF_INET, SOCK_STREAM, 0);
119 if (serverfd == -1) {
120 Error("irc",ERR_FATAL,"Couldn't create socket.");
121 exit(1);
122 }
123
124 sockaddress.sin_family = AF_INET;
125 sockaddress.sin_port = htons(portnum);
126 host = gethostbyname(conto->content);
127 if (!host) {
128 Error("irc",ERR_FATAL,"Couldn't resolve host %s.",conto->content);
129 exit(1);
130 }
131 memcpy(&sockaddress.sin_addr, host->h_addr, sizeof(struct in_addr));
132
133 if (setsockopt(serverfd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt))) {
134 Error("irc",ERR_WARNING,"Error setting socket buffer.");
135 }
136
137 if (connect(serverfd, (struct sockaddr *) &sockaddress, sizeof(struct sockaddr_in)) == -1) {
138 Error("irc",ERR_ERROR,"Couldn't connect to %s:%s, will retry in one minute",conto->content,conport->content);
139 scheduleoneshot(time(NULL)+60,&irc_connect,NULL);
140 close(serverfd);
141 return;
142 }
143
144 Error("irc",ERR_INFO,"Connecting to %s:%s",conto->content,conport->content);
145
146 irc_send("PASS :%s",conpass->content);
147 irc_send("SERVER %s 1 %ld %ld J10 %s%s +sh :%s",myserver->content,starttime,time(NULL),mynumeric->content,longtonumeric(MAXLOCALUSER,3),mydesc->content);
148
149 registerhandler(serverfd, POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL, &handledata);
150
151 /* Schedule our ping requests. Note that this will also server
152 * to time out a failed connection.. */
153
154 schedulerecurring(time(NULL)+strtol(pingfreq->content,NULL,10),0,
155 strtol(pingfreq->content,NULL,10),&sendping,NULL);
156
157 freesstring(conto);
158 freesstring(conport);
159 freesstring(conpass);
160 freesstring(mydesc);
161 freesstring(pingfreq);
162}
163
164int irc_handleserver(void *source, int cargc, char **cargv) {
165 if (!strncmp((char *)source,"INIT",4) && !connected) {
166 /* OK, this is the SERVER response back from the remote server */
167 /* This completes the connection process. */
168 Error("irc",ERR_INFO,"Connection accepted by %s",cargv[0]);
169
170 /* Fix our timestamps before we do anything else */
171 setnettime(strtol(cargv[3],NULL,10));
172
173 connected=1;
174 triggerhook(HOOK_IRC_SENDBURSTSERVERS,NULL);
175 triggerhook(HOOK_IRC_SENDBURSTNICKS,NULL);
176 triggerhook(HOOK_IRC_SENDBURSTBURSTS,NULL);
177 irc_send("%s EB",mynumeric->content);
178
179 triggerhook(HOOK_IRC_CONNECTED,NULL);
180 } else {
181 Error("irc",ERR_INFO,"Unexpected SERVER message");
182 }
183
184 return CMD_OK;
185}
186
187void irc_disconnected() {
188 if (serverfd>=0) {
189 deregisterhandler(serverfd,1);
190 }
191 serverfd=-1;
192 if (connected) {
193 connected=0;
194
195 deleteschedule(NULL,&irc_connect,NULL);
196 deleteschedule(NULL,&sendping,NULL);
197 scheduleoneshot(time(NULL)+2,&irc_connect,NULL);
e609eb1f 198 triggerhook(HOOK_IRC_PRE_DISCON,NULL);
c86edd1d
Q
199 triggerhook(HOOK_IRC_DISCON,NULL);
200 }
201}
202
203void 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
222void 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
264int 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! */
c3db6f7e 323 long numeric = strtol(cargv[1], NULL, 0);
16d29ce2
CP
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 }
c86edd1d
Q
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
344int 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
351int deregisterserverhandler(const char *command, CommandHandler handler) {
352 return deletecommandfromtree(servercommands, command, handler);
353}
354
16d29ce2
CP
355int 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
379int 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
c86edd1d
Q
399char *getmynumeric() {
400 return mynumeric->content;
401}
402
403time_t getnettime() {
404 return (time(NULL)+timeoffset);
405}
406
407void setnettime(time_t newtime) {
408 timeoffset=newtime-time(NULL);
409 Error("irc",ERR_INFO,"setnettime: Time offset is now %d",timeoffset);
410}
411
412int 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
419int 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
430void 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
448int handlepingreply(void *sender, int cargc, char **cargv) {
449 awaitingping=0;
450 return CMD_OK;
451}