1 /* irc.c: handle the IRC interface */
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"
16 #include <sys/types.h>
17 #include <sys/socket.h>
25 #include <netinet/in.h>
29 #define READBUFSIZE 32768
30 #define MAX_SERVERARGS 20
31 #define MIN_NUMERIC 100
32 #define MAX_NUMERIC 999
34 void irc_connect(void *arg
);
35 void ircstats(int hooknum
, void *arg
);
37 CommandTree
*servercommands
;
38 Command
*numericcommands
[MAX_NUMERIC
-MIN_NUMERIC
];
40 char inbuf
[READBUFSIZE
];
42 char *args
[MAX_SERVERARGS
];
56 servercommands
=newcommandtree();
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
);
66 mylongnum
=numerictolong(mynumeric
->content
,2);
68 /* Schedule a connection to the IRC server */
69 scheduleoneshot(time(NULL
),&irc_connect
,NULL
);
71 registerserverhandler("G",&handleping
,1);
72 registerserverhandler("SE",&handlesettime
,1);
73 registerserverhandler("Z",&handlepingreply
,1);
74 registerserverhandler("SERVER",&irc_handleserver
,8);
76 registerhook(HOOK_CORE_STATSREQUEST
,&ircstats
);
81 irc_send("%s SQ %s 0 :Shutting down",mynumeric
->content
,myserver
->content
);
85 deregisterserverhandler("G",&handleping
);
86 deregisterserverhandler("SE",&handlesettime
);
87 deregisterserverhandler("Z",&handlepingreply
);
88 deregisterserverhandler("SERVER",&irc_handleserver
);
90 deregisterhook(HOOK_CORE_STATSREQUEST
,&ircstats
);
92 deleteschedule(NULL
,&sendping
,NULL
);
93 deleteschedule(NULL
,&irc_connect
,NULL
);
95 freesstring(mynumeric
);
96 freesstring(myserver
);
98 destroycommandtree(servercommands
);
101 void irc_connect(void *arg
) {
102 struct sockaddr_in sockaddress
;
103 struct hostent
*host
;
104 sstring
*conto
,*conport
,*conpass
;
105 sstring
*mydesc
, *pingfreq
;
114 conto
=getcopyconfigitem("irc","hubhost","127.0.0.1",HOSTLEN
);
115 conport
=getcopyconfigitem("irc","hubport","4400",7);
116 conpass
=getcopyconfigitem("irc","hubpass","erik",20);
118 mydesc
=getcopyconfigitem("irc","serverdescription","newserv 0.01",100);
120 pingfreq
=getcopyconfigitem("irc","pingfreq","90",10);
122 portnum
=strtol(conport
->content
,NULL
,10);
124 serverfd
= socket(PF_INET
, SOCK_STREAM
, 0);
125 if (serverfd
== -1) {
126 Error("irc",ERR_FATAL
,"Couldn't create socket.");
130 sockaddress
.sin_family
= AF_INET
;
131 sockaddress
.sin_port
= htons(portnum
);
132 host
= gethostbyname(conto
->content
);
134 Error("irc",ERR_FATAL
,"Couldn't resolve host %s.",conto
->content
);
137 memcpy(&sockaddress
.sin_addr
, host
->h_addr
, sizeof(struct in_addr
));
139 if (setsockopt(serverfd
, SOL_SOCKET
, SO_RCVBUF
, &opt
, sizeof(opt
))) {
140 Error("irc",ERR_WARNING
,"Error setting socket buffer.");
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
);
150 Error("irc",ERR_INFO
,"Connecting to %s:%s",conto
->content
,conport
->content
);
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
);
155 registerhandler(serverfd
, POLLIN
|POLLPRI
|POLLERR
|POLLHUP
|POLLNVAL
, &handledata
);
157 /* Schedule our ping requests. Note that this will also server
158 * to time out a failed connection.. */
160 schedulerecurring(time(NULL
)+strtol(pingfreq
->content
,NULL
,10),0,
161 strtol(pingfreq
->content
,NULL
,10),&sendping
,NULL
);
164 freesstring(conport
);
165 freesstring(conpass
);
167 freesstring(pingfreq
);
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]);
176 /* Fix our timestamps before we do anything else */
177 setnettime(strtol(cargv
[3],NULL
,10));
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
);
185 triggerhook(HOOK_IRC_CONNECTED
,NULL
);
187 Error("irc",ERR_INFO
,"Unexpected SERVER message");
193 void irc_disconnected() {
195 deregisterhandler(serverfd
,1);
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
);
209 void irc_send(char *format
, ... ) {
214 va_start(val
,format
);
215 len
=vsnprintf(buf
,509,format
,val
);
218 if (len
>509 || len
<0) {
225 write(serverfd
,buf
,len
);
228 void handledata(int fd
, short events
) {
232 if (events
& (POLLPRI
| POLLERR
| POLLHUP
| POLLNVAL
)) {
233 /* Oh shit, we got dropped */
234 Error("irc",ERR_ERROR
,"Got socket error, dropping connection.");
242 res
=read(serverfd
, inbuf
+bytesleft
, READBUFSIZE
-bytesleft
);
244 Error("irc",ERR_ERROR
,"Disconnected by remote server.");
249 again
=((bytesleft
+=res
)==READBUFSIZE
);
253 memmove(inbuf
, nextline
, bytesleft
);
259 * Parse and dispatch a line from the IRC server
262 * 0: found at least one complete line in buffer
263 * 1: no complete line found in buffer
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
271 char *currentline
=nextline
;
274 char *cargv
[MAX_SERVERARGS
];
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
) {
284 currentline
=nextline
;
296 bytesleft
=(nextline
-currentline
);
297 nextline
=currentline
;
301 /* OK, currentline points at a valid NULL-terminated line */
302 /* and nextline points at where we are going next */
307 cargc
=splitline(currentline
,cargv
,MAX_SERVERARGS
,1);
310 /* Less than two arguments? Not a valid command, sir */
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. */
322 for (;c
!=NULL
;c
=c
->next
) {
323 if (((c
->handler
)("INIT",cargc
-1,&cargv
[1]))==CMD_LAST
)
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
)
337 if ((c
=findcommandintree(servercommands
,cargv
[1],1))==NULL
) {
338 /* We don't have a handler for this command */
341 for (;c
!=NULL
;c
=c
->next
) {
342 if (((c
->handler
)(cargv
[0],cargc
-2,cargv
+2))==CMD_LAST
)
350 int registerserverhandler(const char *command
, CommandHandler handler
, int maxparams
) {
351 if ((addcommandtotree(servercommands
,command
,0,maxparams
,handler
))==NULL
)
357 int deregisterserverhandler(const char *command
, CommandHandler handler
) {
358 return deletecommandfromtree(servercommands
, command
, handler
);
361 int registernumerichandler(const int numeric
, CommandHandler handler
, int maxparams
) {
363 if((numeric
< MIN_NUMERIC
) || (numeric
> MAX_NUMERIC
))
366 /* doesn't happen that often */
367 np
= (Command
*)malloc(sizeof(Command
));
368 np
->handler
= handler
;
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
;
376 for(;cp
->next
;cp
=cp
->next
);
385 int deregisternumerichandler(const int numeric
, CommandHandler handler
) {
387 if((numeric
< MIN_NUMERIC
) || (numeric
> MAX_NUMERIC
))
390 for(cp
=numericcommands
[numeric
],lp
=NULL
;cp
;lp
=cp
,cp
=cp
->next
) {
391 if(cp
->handler
== handler
) {
395 numericcommands
[numeric
] = cp
->next
;
405 char *getmynumeric() {
406 return mynumeric
->content
;
409 time_t getnettime() {
410 return (time(NULL
)+timeoffset
);
413 void setnettime(time_t newtime
) {
414 timeoffset
=newtime
-time(NULL
);
415 Error("irc",ERR_INFO
,"setnettime: Time offset is now %d",timeoffset
);
418 int handleping(void *sender
, int cargc
, char **cargv
) {
420 irc_send("%s Z %s",mynumeric
->content
,cargv
[cargc
-1]);
425 int handlesettime(void *sender
, int cargc
, char **cargv
) {
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());
436 void sendping(void *arg
) {
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
);
444 irc_send("%s G :%s",mynumeric
->content
,myserver
->content
);
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 */
454 int handlepingreply(void *sender
, int cargc
, char **cargv
) {
460 void ircstats(int hooknum
, void *arg
) {
461 long level
=(long)arg
;
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
);