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