]> jfr.im git - irc/quakenet/newserv.git/blob - nick/nickhandlers.c
NICK: Add "away" functionality.
[irc/quakenet/newserv.git] / nick / nickhandlers.c
1 /* nickhandlers.c */
2
3 #include "nick.h"
4 #include "../lib/flags.h"
5 #include "../lib/irc_string.h"
6 #include "../lib/base64.h"
7 #include "../irc/irc.h"
8 #include "../irc/irc_config.h"
9 #include "../core/error.h"
10 #include "../core/hooks.h"
11 #include "../lib/sstring.h"
12 #include "../server/server.h"
13 #include "../parser/parser.h"
14 #include <stdlib.h>
15 #include <string.h>
16 #include <stdint.h>
17
18 /*
19 * handlenickmsg:
20 * Handle new nicks being introduced to the network.
21 * Handle renames.
22 */
23
24 int handlenickmsg(void *source, int cargc, char **cargv) {
25 char *sender=(char *)source;
26 time_t timestamp;
27 nick *np,*np2;
28 nick **nh;
29 char *fakehost;
30 char *accountts;
31 char *accountflags;
32 struct irc_in_addr ipaddress;
33 char *accountid;
34 unsigned long userid;
35
36 if (cargc==2) { /* rename */
37 /* Nyklon 1017697578 */
38 timestamp=strtol(cargv[1],NULL,10);
39 np=getnickbynumericstr(sender);
40 if (np==NULL) {
41 Error("nick",ERR_ERROR,"Rename from non-existent sender %s",sender);
42 return CMD_OK;
43 }
44 np2=getnickbynick(cargv[0]);
45 if (np==np2) {
46 /* The new and old nickname have the same hash, this means a rename to the same name in
47 * different case, e.g. Flash -> flash. In this case the timestamp for the change should
48 * match the existing timestamp, and we can bypass all the collision checking and hash fettling. */
49 if (np->timestamp!=timestamp) {
50 Error("nick",ERR_WARNING,"Rename to same nickname with different timestamp (%s(%jd) -> %s(%jd))",
51 np->nick,(intmax_t)np->timestamp,cargv[0], (intmax_t)timestamp);
52 np->timestamp=timestamp;
53 }
54 strncpy(np->nick,cargv[0],NICKLEN);
55 np->nick[NICKLEN]='\0';
56 triggerhook(HOOK_NICK_RENAME,np);
57 return CMD_OK;
58 }
59 if (np2!=NULL) {
60 /* Nick collision */
61 if (ircd_strcmp(np->ident,np2->ident) || (np->host!=np2->host)) {
62 /* Different user@host */
63 if (np2->timestamp < timestamp) {
64 /* The nick attempting to rename got killed. Guess we don't need to do the rename now :) */
65 deletenick(np);
66 return CMD_OK;
67 } else {
68 /* The other nick got killed */
69 deletenick(np2);
70 }
71 } else {
72 if (np2->timestamp < timestamp) {
73 /* Same user@host: reverse logic. Whose idea was all this anyway? */
74 deletenick(np2);
75 } else {
76 deletenick(np);
77 return CMD_OK;
78 }
79 }
80 }
81 /* OK, we've survived the collision hazard. Change timestamp and rename */
82 np->timestamp=timestamp;
83 removenickfromhash(np);
84 strncpy(np->nick,cargv[0],NICKLEN);
85 np->nick[NICKLEN]='\0';
86 addnicktohash(np);
87 triggerhook(HOOK_NICK_RENAME,np);
88 } else if (cargc>=8) { /* new nick */
89 /* Jupiler 2 1016645147 ~Jupiler www.iglobal.be +ir moo [FUTURE CRAP HERE] DV74O] BNBd7 :Jupiler */
90 timestamp=strtol(cargv[2],NULL,10);
91 np=getnickbynick(cargv[0]);
92 if (np!=NULL) {
93 /* Nick collision */
94 if (ircd_strcmp(np->ident,cargv[3]) || ircd_strcmp(np->host->name->content,cargv[4])) {
95 /* Different user@host */
96 if (timestamp>np->timestamp) {
97 /* New nick is newer. Ignore this nick message */
98 return CMD_OK;
99 } else {
100 /* New nick is older. Kill the imposter, and drop through */
101 deletenick(np);
102 }
103 } else {
104 if (timestamp>np->timestamp) {
105 /* Same user@host, newer timestamp: we're killing off a ghost */
106 deletenick(np);
107 } else {
108 /* This nick is the ghost, so ignore it */
109 return CMD_OK;
110 }
111 }
112 }
113
114 nh=gethandlebynumeric(numerictolong(cargv[cargc-2],5));
115 if (!nh) {
116 /* This isn't a valid numeric */
117 Error("nick",ERR_WARNING,"Received NICK with invalid numeric %s from %s.",cargv[cargc-2],sender);
118 return CMD_ERROR;
119 }
120
121 base64toip(cargv[cargc-3], &ipaddress);
122 if (!irc_in_addr_valid(&ipaddress)) {
123 Error("nick",ERR_ERROR,"Received NICK with invalid ipaddress for %s from %s.",cargv[0],sender);
124 return CMD_ERROR;
125 }
126
127 /* At this stage the nick is cleared to proceed */
128 np=newnick();
129 strncpy(np->nick,cargv[0],NICKLEN);
130 np->nick[NICKLEN]='\0';
131 np->numeric=numerictolong(cargv[cargc-2],5);
132 strncpy(np->ident,cargv[3],USERLEN);
133 np->ident[USERLEN]='\0';
134 np->host=findorcreatehost(cargv[4]);
135 np->realname=findorcreaterealname(cargv[cargc-1]);
136 np->nextbyhost=np->host->nicks;
137 np->host->nicks=np;
138 np->nextbyrealname=np->realname->nicks;
139 np->realname->nicks=np;
140 np->timestamp=timestamp;
141
142 base64toip(cargv[cargc-3], &ipaddress);
143 np->ipnode = refnode(iptree, &ipaddress, PATRICIA_MAXBITS);
144 node_increment_usercount(np->ipnode);
145
146 np->away=NULL;
147 np->shident=NULL;
148 np->sethost=NULL;
149 np->opername=NULL;
150 np->umodes=0;
151 np->marker=0;
152 memset(np->exts, 0, MAXNICKEXTS * sizeof(void *));
153 np->authname=NULLAUTHNAME;
154 np->auth=NULL;
155 np->accountts=0;
156 if(cargc>=9) {
157 int sethostarg = 6, opernamearg = 6, accountarg = 6;
158
159 setflags(&(np->umodes),UMODE_ALL,cargv[5],umodeflags,REJECT_NONE);
160
161 if(IsOper(np) && (serverlist[myhub].flags & SMODE_OPERNAME)) {
162 accountarg++;
163 sethostarg++;
164
165 np->opername=getsstring(cargv[opernamearg],ACCOUNTLEN);
166 }
167
168 if (IsAccount(np)) {
169 sethostarg++;
170
171 if ((accountts=strchr(cargv[accountarg],':'))) {
172 userid=0;
173 *accountts++='\0';
174 np->accountts=strtoul(accountts,&accountid,10);
175 if(accountid) {
176 userid=strtoul(accountid + 1,&accountflags,10);
177 if(userid) {
178 np->auth=findorcreateauthname(userid, cargv[accountarg]);
179 np->authname=np->auth->name;
180 np->auth->usercount++;
181 np->nextbyauthname=np->auth->nicks;
182 np->auth->nicks=np;
183 if(accountflags)
184 np->auth->flags=strtoull(accountflags + 1,NULL,10);
185 }
186 }
187 if(!userid) {
188 np->authname=malloc(strlen(cargv[accountarg]) + 1);
189 strcpy(np->authname,cargv[accountarg]);
190 }
191 }
192 }
193 if (IsSetHost(np) && (fakehost=strchr(cargv[sethostarg],'@'))) {
194 /* valid sethost */
195 *fakehost++='\0';
196 np->shident=getsstring(cargv[sethostarg],USERLEN);
197 np->sethost=getsstring(fakehost,HOSTLEN);
198 }
199 }
200
201 /* Place this nick in the server nick table. Note that nh is valid from the numeric check above */
202 if (*nh) {
203 /* There was a nick there already -- we have a masked numeric collision
204 * This shouldn't happen, but if it does the newer nick takes precedence
205 * (the two nicks are from the same server, and if the server has reissued
206 * the masked numeric it must believe the old user no longer exists).
207 */
208 Error("nick",ERR_ERROR,"Masked numeric collision for %s [%s vs %s]",cargv[0],cargv[cargc-2],longtonumeric((*nh)->numeric,5));
209 deletenick(*nh);
210 }
211 *nh=np;
212
213 /* And the nick hash table */
214 addnicktohash(np);
215
216 /* Trigger the hook */
217 triggerhook(HOOK_NICK_NEWNICK,np);
218 } else {
219 Error("nick",ERR_WARNING,"Nick message with weird number of parameters (%d)",cargc);
220 }
221
222 return CMD_OK;
223 }
224
225 int handlequitmsg(void *source, int cargc, char **cargv) {
226 nick *np;
227 void *harg[2];
228
229 if (cargc>0) {
230 harg[1]=(void *)cargv[0];
231 } else {
232 harg[1]="";
233 }
234
235 np=getnickbynumericstr((char *)source);
236 if (np) {
237 harg[0]=(void *)np;
238 triggerhook(HOOK_NICK_QUIT, harg);
239 deletenick(np);
240 } else {
241 Error("nick",ERR_WARNING,"Quit from non-existant numeric %s",(char *)source);
242 }
243 return CMD_OK;
244 }
245
246 int handlekillmsg(void *source, int cargc, char **cargv) {
247 nick *np;
248 void *harg[2];
249 #warning Fix me to use source
250
251 if (cargc<1) {
252 Error("nick",ERR_WARNING,"Kill message with too few parameters");
253 return CMD_ERROR;
254 }
255
256 if (cargc>1) {
257 harg[1]=(void *)cargv[1];
258 } else {
259 harg[1]="";
260 }
261
262 np=getnickbynumericstr(cargv[0]);
263 if (np) {
264 harg[0]=(void *)np;
265 triggerhook(HOOK_NICK_KILL, harg);
266 deletenick(np);
267 } else {
268 Error("nick",ERR_WARNING,"Kill for non-existant numeric %s",cargv[0]);
269 }
270 return CMD_OK;
271 }
272
273 int handleusermodemsg(void *source, int cargc, char **cargv) {
274 nick *np;
275 flag_t oldflags;
276 char *fakehost;
277
278 if (cargc<2) {
279 Error("nick",ERR_WARNING,"Mode message with too few parameters");
280 return CMD_LAST; /* With <2 params the channels module won't want it either */
281 }
282
283 if (cargv[0][0]=='#') {
284 /* Channel mode change, we don't care.
285 * We don't bother checking for other channel types here, since & channel mode
286 * changes aren't broadcast and + channels don't have mode changes :) */
287 return CMD_OK;
288 }
289
290 np=getnickbynumericstr((char *)source);
291 if (np!=NULL) {
292 if (ircd_strcmp(cargv[0],np->nick)) {
293 Error("nick",ERR_WARNING,"Attempted mode change on user %s by %s",cargv[0],np->nick);
294 return CMD_OK;
295 }
296 oldflags=np->umodes;
297 setflags(&(np->umodes),UMODE_ALL,cargv[1],umodeflags,REJECT_NONE);
298
299 if (strchr(cargv[1],'o')) { /* o always comes on its own when being set */
300 if(serverlist[myhub].flags & SMODE_OPERNAME) {
301 if((np->umodes & UMODE_OPER)) {
302 np->opername = getsstring(cargv[2], ACCOUNTLEN);
303 } else {
304 freesstring(np->opername);
305 np->opername = NULL;
306 }
307 }
308 if((np->umodes ^ oldflags) & UMODE_OPER)
309 triggerhook(HOOK_NICK_MODEOPER,np);
310 }
311 if (strchr(cargv[1],'h')) { /* Have to allow +h twice.. */
312 /* +-h: just the freesstring() calls for the -h case */
313 freesstring(np->shident); /* freesstring(NULL) is OK */
314 freesstring(np->sethost);
315 np->shident=NULL;
316 np->sethost=NULL;
317 if (IsSetHost(np) && cargc>2) { /* +h and mask received */
318 if ((fakehost=strchr(cargv[2],'@'))) {
319 /* user@host change */
320 *fakehost++='\0';
321 np->shident=getsstring(cargv[2],USERLEN);
322 np->sethost=getsstring(fakehost,HOSTLEN);
323 } else {
324 np->sethost=getsstring(cargv[2],HOSTLEN);
325 }
326 }
327 triggerhook(HOOK_NICK_SETHOST, (void *)np);
328 }
329 } else {
330 Error("nick",ERR_WARNING,"Usermode change by unknown user %s",(char *)source);
331 }
332 return CMD_OK;
333 }
334
335 int handlewhoismsg(void *source, int cargc, char **cargv) {
336 nick *sender,*target;
337 nick *nicks[2];
338
339 if (cargc<2)
340 return CMD_OK;
341
342 if (strncmp(cargv[0],mynumeric->content,2)) {
343 return CMD_OK;
344 }
345
346 /* Find the sender... */
347 if ((sender=getnickbynumericstr((char *)source))==NULL) {
348 Error("localuser",ERR_WARNING,"WHOIS message from non existent numeric %s",(char *)source);
349 return CMD_OK;
350 }
351
352 /* :hub.splidge.netsplit.net 311 moo splidge splidge ground.stbarnab.as * :splidge
353 :hub.splidge.netsplit.net 312 moo splidge splidge.netsplit.net :splidge's netsplit leaf
354 :hub.splidge.netsplit.net 313 moo splidge :is an IRC Operator
355 :hub.splidge.netsplit.net 318 moo splidge :End of /WHOIS list.
356 */
357
358 /* And the target... */
359 if ((target=getnickbynick(cargv[1]))==NULL) {
360 irc_send(":%s 401 %s %s :No such nick",myserver->content,sender->nick,cargv[1]);
361 } else {
362 irc_send(":%s 311 %s %s %s %s * :%s",myserver->content,sender->nick,target->nick,target->ident,
363 target->host->name->content, target->realname->name->content);
364 nicks[0]=sender; nicks[1]=target;
365 triggerhook(HOOK_NICK_WHOISCHANNELS,nicks);
366 if (IsOper(sender) || !HIS_SERVER ) {
367 irc_send(":%s 312 %s %s %s :%s",myserver->content,sender->nick,target->nick,
368 serverlist[homeserver(target->numeric)].name->content,
369 serverlist[homeserver(target->numeric)].description->content);
370 } else {
371 irc_send(":%s 312 %s %s " HIS_SERVERNAME " :" HIS_SERVERDESC,myserver->content,sender->nick,target->nick);
372 }
373 if (IsOper(target)) {
374 irc_send(":%s 313 %s %s :is an IRC Operator",myserver->content,sender->nick,target->nick);
375 }
376 if (IsAccount(target)) {
377 irc_send(":%s 330 %s %s %s :is authed as",myserver->content,sender->nick,target->nick,target->authname);
378 }
379 if (homeserver(target->numeric)==mylongnum && !IsService(target) && !IsHideIdle(target)) {
380 irc_send(":%s 317 %s %s %ld %ld :seconds idle, signon time",myserver->content,sender->nick,target->nick,
381 (getnettime() - target->timestamp) % (((target->numeric + target->timestamp) % 983) + 7),target->timestamp);
382 }
383 }
384
385 irc_send(":%s 318 %s %s :End of /WHOIS list.",myserver->content,sender->nick,cargv[1]);
386 return CMD_OK;
387 }
388
389 int handleaccountmsg(void *source, int cargc, char **cargv) {
390 nick *target;
391 unsigned long userid;
392 time_t accountts;
393 u_int64_t accountflags=0, oldflags;
394
395 if (cargc<4) {
396 return CMD_OK;
397 }
398
399 if ((target=getnickbynumericstr(cargv[0]))==NULL) {
400 return CMD_OK;
401 }
402
403 accountts=strtoul(cargv[2],NULL,10);
404 userid=strtoul(cargv[3],NULL,10);
405 if(cargc>=5)
406 accountflags=strtoull(cargv[4],NULL,10);
407
408 /* allow user flags to change if all fields match */
409 if (IsAccount(target)) {
410 void *arg[2];
411
412 if (!target->auth || strcmp(target->auth->name,cargv[1]) || (target->auth->userid != userid) || (target->accountts != accountts)) {
413 return CMD_OK;
414 }
415
416 oldflags = target->auth->flags;
417 arg[0] = target->auth;
418 arg[1] = &oldflags;
419
420 if (cargc>=5)
421 target->auth->flags=accountflags;
422
423 triggerhook(HOOK_AUTH_FLAGSUPDATED, (void *)arg);
424
425 return CMD_OK;
426 }
427
428 SetAccount(target);
429 target->accountts=accountts;
430
431 if(!userid) {
432 target->auth=NULL;
433 target->authname=malloc(strlen(cargv[1]) + 1);
434 strcpy(target->authname,cargv[1]);
435 } else {
436 target->auth=findorcreateauthname(userid, cargv[1]);
437 target->auth->usercount++;
438 target->authname=target->auth->name;
439 target->nextbyauthname = target->auth->nicks;
440 target->auth->nicks = target;
441 if (cargc>=5)
442 target->auth->flags=accountflags;
443 }
444
445 triggerhook(HOOK_NICK_ACCOUNT, (void *)target);
446
447 return CMD_OK;
448 }
449
450 int handlestatsmsg(void *source, int cargc, char **cargv) {
451 int sourceserver;
452 char *sender=(char *)source;
453 char *replytarget;
454 char *fromstring;
455 nick *np;
456
457 if (cargc<2) {
458 Error("nick",ERR_WARNING,"STATS request without enough parameters!");
459 return CMD_OK;
460 }
461
462 if (strlen(sender)==5) {
463 /* client */
464 np=getnickbynumericstr(sender);
465 if (!np) {
466 Error("nick",ERR_WARNING,"STATS request from unknown client %s",sender);
467 return CMD_OK;
468 }
469 replytarget=np->nick;
470 } else {
471 Error("nick",ERR_WARNING,"STATS request from odd source %s",sender);
472 return CMD_OK;
473 }
474
475 /* Reply to stats for ANY server.. including any we are juping */
476 sourceserver=numerictolong(cargv[1],2);
477 if (serverlist[sourceserver].maxusernum==0) {
478 Error("nick",ERR_WARNING,"Stats request for bad server %s",cargv[1]);
479 return CMD_OK;
480 }
481 fromstring=serverlist[sourceserver].name->content;
482
483 switch(cargv[0][0]) {
484 case 'u':
485 irc_send(":%s 242 %s :Server Up %s",fromstring,replytarget,
486 longtoduration(time(NULL)-starttime, 0));
487 irc_send(":%s 250 %s :Highest connection count: 10 (9 clients)",fromstring,replytarget);
488 break;
489
490 case 'P':
491 irc_send(":%s 217 %s P none 0 :0x2000",fromstring,replytarget);
492 break;
493
494 case 'm':
495 stats_m(fromstring, replytarget);
496 break;
497 }
498
499 irc_send(":%s 219 %s %c :End of /STATS report",fromstring,replytarget,cargv[0][0]);
500
501 return CMD_OK;
502 }
503
504 int handleprivmsg(void *source, int cargc, char **cargv) {
505 nick *sender;
506 char *message;
507 void *args[3];
508
509 if (cargc<2)
510 return CMD_OK;
511
512 if (cargv[0][0]!='$')
513 return CMD_OK;
514
515 sender=getnickbynumericstr((char *)source);
516
517 if (!match2strings(cargv[0] + 1,myserver->content))
518 return CMD_OK;
519
520 message=cargv[0];
521
522 args[0]=sender;
523 args[1]=cargv[0];
524 args[2]=cargv[1];
525
526 triggerhook(HOOK_NICK_MASKPRIVMSG, (void *)args);
527
528 return CMD_OK;
529 }
530
531 int handleawaymsg(void *source, int cargc, char **cargv) {
532 nick *sender;
533
534 /* Check source is a valid user */
535 if (!(sender=getnickbynumericstr(source))) {
536 return CMD_OK;
537 }
538
539 /* Done with the old away message either way */
540 freesstring(sender->away);
541 sender->away=NULL;
542
543 /* If we have an arg and it isn't an empty string, this sets a new message */
544 if (cargc > 0 && *(cargv[0])) {
545 sender->away=getsstring(cargv[0], AWAYLEN);
546 }
547
548 return CMD_OK;
549 }