1 |
/* Routines to maintain a list of online users. |
2 |
* |
3 |
* IRC Services is copyright (c) 1996-2009 Andrew Church. |
4 |
* E-mail: <achurch@achurch.org> |
5 |
* Parts written by Andrew Kempe and others. |
6 |
* This program is free but copyrighted software; see the file GPL.txt for |
7 |
* details. |
8 |
*/ |
9 |
|
10 |
#include "services.h" |
11 |
#include "modules.h" |
12 |
|
13 |
/*************************************************************************/ |
14 |
|
15 |
/* Maximum number of tries to randomly select a new guest nick when the |
16 |
* first one chosen is in use before giving up. |
17 |
*/ |
18 |
#define MAKEGUESTNICK_TRIES 1000 |
19 |
|
20 |
/*************************************************************************/ |
21 |
|
22 |
#define add_user static add_user |
23 |
#define del_user static del_user |
24 |
#include "hash.h" |
25 |
DEFINE_HASH(user, User, nick) |
26 |
#undef add_user |
27 |
#undef del_user |
28 |
|
29 |
int32 usercnt = 0, opcnt = 0; |
30 |
|
31 |
static int cb_check = -1; |
32 |
static int cb_create = -1; |
33 |
static int cb_servicestamp_change = -1; |
34 |
static int cb_nickchange1 = -1; |
35 |
static int cb_nickchange2 = -1; |
36 |
static int cb_delete = -1; |
37 |
static int cb_mode = -1; |
38 |
static int cb_chan_part = -1; |
39 |
static int cb_chan_kick = -1; |
40 |
|
41 |
/*************************************************************************/ |
42 |
|
43 |
int user_init(int ac, char **av) |
44 |
{ |
45 |
cb_check = register_callback("user check"); |
46 |
cb_create = register_callback("user create"); |
47 |
cb_servicestamp_change = register_callback("user servicestamp change"); |
48 |
cb_nickchange1 = register_callback("user nickchange (before)"); |
49 |
cb_nickchange2 = register_callback("user nickchange (after)"); |
50 |
cb_delete = register_callback("user delete"); |
51 |
cb_mode = register_callback("user MODE"); |
52 |
cb_chan_part = register_callback("channel PART"); |
53 |
cb_chan_kick = register_callback("channel KICK"); |
54 |
if (cb_check < 0 || cb_create < 0 || cb_servicestamp_change < 0 |
55 |
|| cb_nickchange1 < 0 || cb_nickchange2 < 0 || cb_delete < 0 |
56 |
|| cb_mode < 0 || cb_chan_part < 0 || cb_chan_kick < 0 |
57 |
) { |
58 |
log("user_init: register_callback() failed\n"); |
59 |
return 0; |
60 |
} |
61 |
return 1; |
62 |
} |
63 |
|
64 |
/*************************************************************************/ |
65 |
|
66 |
void user_cleanup(void) |
67 |
{ |
68 |
User *u; |
69 |
|
70 |
for (u = first_user(); u; u = next_user()) |
71 |
del_user(u); |
72 |
unregister_callback(cb_chan_kick); |
73 |
unregister_callback(cb_chan_part); |
74 |
unregister_callback(cb_mode); |
75 |
unregister_callback(cb_delete); |
76 |
unregister_callback(cb_nickchange2); |
77 |
unregister_callback(cb_nickchange1); |
78 |
unregister_callback(cb_servicestamp_change); |
79 |
unregister_callback(cb_create); |
80 |
unregister_callback(cb_check); |
81 |
} |
82 |
|
83 |
/*************************************************************************/ |
84 |
/************************* User list management **************************/ |
85 |
/*************************************************************************/ |
86 |
|
87 |
/* Allocate a new User structure, fill in basic values, link it to the |
88 |
* overall list, and return it. Always successful. |
89 |
*/ |
90 |
|
91 |
static User *new_user(const char *nick) |
92 |
{ |
93 |
User *user; |
94 |
|
95 |
user = scalloc(sizeof(User), 1); |
96 |
if (!nick) |
97 |
nick = ""; |
98 |
strbcpy(user->nick, nick); |
99 |
add_user(user); |
100 |
usercnt++; |
101 |
return user; |
102 |
} |
103 |
|
104 |
/*************************************************************************/ |
105 |
|
106 |
/* Change the nickname of a user, and move pointers as necessary. */ |
107 |
|
108 |
static void change_user_nick(User *user, const char *nick) |
109 |
{ |
110 |
del_user(user); |
111 |
strbcpy(user->nick, nick); |
112 |
add_user(user); |
113 |
} |
114 |
|
115 |
/*************************************************************************/ |
116 |
|
117 |
/* Remove and free a User structure. */ |
118 |
|
119 |
static void delete_user(User *user) |
120 |
{ |
121 |
struct u_chanlist *c, *c2; |
122 |
struct u_chaninfolist *ci, *ci2; |
123 |
|
124 |
usercnt--; |
125 |
if (is_oper(user)) |
126 |
opcnt--; |
127 |
|
128 |
free(user->username); |
129 |
free(user->host); |
130 |
free(user->ipaddr); |
131 |
free(user->realname); |
132 |
free(user->fakehost); |
133 |
free(user->id_nicks); |
134 |
LIST_FOREACH_SAFE (c, user->chans, c2) { |
135 |
chan_deluser(user, c->chan); |
136 |
free(c); |
137 |
} |
138 |
LIST_FOREACH_SAFE (ci, user->id_chans, ci2) |
139 |
free(ci); |
140 |
#define next snext |
141 |
#define prev sprev |
142 |
if (user->server) |
143 |
LIST_REMOVE(user, user->server->userlist); |
144 |
#undef next |
145 |
#undef prev |
146 |
del_user(user); |
147 |
free(user); |
148 |
} |
149 |
|
150 |
/*************************************************************************/ |
151 |
/*************************************************************************/ |
152 |
|
153 |
/* Remove a user on QUIT/KILL. Calls the user delete callback and then |
154 |
* deletes the User structure. |
155 |
*/ |
156 |
|
157 |
void quit_user(User *user, const char *quitmsg, int is_kill) |
158 |
{ |
159 |
call_callback_3(cb_delete, user, quitmsg, is_kill); |
160 |
delete_user(user); |
161 |
} |
162 |
|
163 |
/*************************************************************************/ |
164 |
|
165 |
/* Return statistics. Pointers are assumed to be valid. */ |
166 |
|
167 |
void get_user_stats(long *nusers, long *memuse) |
168 |
{ |
169 |
long count = 0, mem = 0; |
170 |
User *user; |
171 |
struct u_chanlist *uc; |
172 |
struct u_chaninfolist *uci; |
173 |
|
174 |
for (user = first_user(); user; user = next_user()) { |
175 |
count++; |
176 |
mem += sizeof(*user); |
177 |
if (user->username) |
178 |
mem += strlen(user->username)+1; |
179 |
if (user->host) |
180 |
mem += strlen(user->host)+1; |
181 |
if (user->realname) |
182 |
mem += strlen(user->realname)+1; |
183 |
LIST_FOREACH (uc, user->chans) |
184 |
mem += sizeof(*uc); |
185 |
LIST_FOREACH (uci, user->id_chans) |
186 |
mem += sizeof(*uci); |
187 |
} |
188 |
*nusers = count; |
189 |
*memuse = mem; |
190 |
} |
191 |
|
192 |
/*************************************************************************/ |
193 |
/************************* Internal routines *****************************/ |
194 |
/*************************************************************************/ |
195 |
|
196 |
/* Part a user from a channel given the user's u_chanlist entry for the |
197 |
* channel. */ |
198 |
|
199 |
static void part_channel_uc(User *user, struct u_chanlist *uc, int callback, |
200 |
const char *param, const char *source) |
201 |
{ |
202 |
call_callback_4(callback, uc->chan, user, param, source); |
203 |
chan_deluser(user, uc->chan); |
204 |
LIST_REMOVE(uc, user->chans); |
205 |
free(uc); |
206 |
} |
207 |
|
208 |
/*************************************************************************/ |
209 |
/************************* Message handlers ******************************/ |
210 |
/*************************************************************************/ |
211 |
|
212 |
/* Handle a server NICK command. Parameters must be in the following order. |
213 |
* av[0] = nick |
214 |
* If a new user: |
215 |
* av[1] = hop count |
216 |
* av[2] = signon time |
217 |
* av[3] = username |
218 |
* av[4] = hostname |
219 |
* av[5] = server |
220 |
* av[6] = real name |
221 |
* av[7] = services stamp (if ac >= 8; NULL if none) |
222 |
* av[8] = IP address (if ac >= 9; NULL if unknown) |
223 |
* av[9] = user modes (lf ac >= 10; NULL if unknown. |
224 |
* Leading + optional) |
225 |
* av[10..] available for protocol module use |
226 |
* Else: |
227 |
* av[1] = time of change |
228 |
* Return 1 if message was accepted, 0 if rejected (AKILL/session limit). |
229 |
*/ |
230 |
|
231 |
int do_nick(const char *source, int ac, char **av) |
232 |
{ |
233 |
User *user; |
234 |
|
235 |
if (!*source) { |
236 |
/* This is a new user; create a User structure for it. */ |
237 |
|
238 |
int reconnect = 0; /* Is user reconnecting after a split? */ |
239 |
|
240 |
log_debug(1, "new user: %s", av[0]); |
241 |
|
242 |
/* We used to ignore the ~ which a lot of ircd's use to indicate no |
243 |
* identd response. That caused channel bans to break, so now we |
244 |
* just take what the server gives us. People are still encouraged |
245 |
* to read the RFCs and stop doing anything to usernames depending |
246 |
* on the result of an identd lookup. */ |
247 |
|
248 |
/* First check whether the user should be allowed on. */ |
249 |
if (call_callback_2(cb_check, ac, av)) |
250 |
return 0; |
251 |
|
252 |
/* User was accepted; allocate User structure and fill it in. */ |
253 |
user = new_user(av[0]); |
254 |
user->my_signon = time(NULL); |
255 |
user->signon = strtotime(av[2], NULL); |
256 |
user->username = sstrdup(av[3]); |
257 |
user->host = sstrdup(av[4]); |
258 |
user->server = get_server(av[5]); |
259 |
user->realname = sstrdup(av[6]); |
260 |
if (ac >= 8 && av[7]) { |
261 |
user->servicestamp = strtoul(av[7], NULL, 10); |
262 |
reconnect = (user->servicestamp != 0); |
263 |
} else { |
264 |
user->servicestamp = (uint32)user->signon; |
265 |
/* Unfortunately, we have no way to tell whether the user is |
266 |
* new or not */ |
267 |
} |
268 |
if (ac >= 9 && av[8]) |
269 |
user->ipaddr = sstrdup(av[8]); |
270 |
else |
271 |
user->ipaddr = NULL; |
272 |
#define next snext |
273 |
#define prev sprev |
274 |
if (user->server) |
275 |
LIST_INSERT(user, user->server->userlist); |
276 |
#undef next |
277 |
#undef prev |
278 |
ignore_init(user); |
279 |
|
280 |
call_callback_4(cb_create, user, ac, av, reconnect); |
281 |
|
282 |
if (ac >= 8 && av[7] && !user->servicestamp) { |
283 |
/* A servicestamp was provided, but it was zero, so assign one. |
284 |
* Note that we use a random value for the initial Services |
285 |
* stamp instead of the current time for the following reason: |
286 |
* |
287 |
* Suppose you have a network with an average of more than one |
288 |
* new user per second; for the sake of argument, assume there |
289 |
* are an average of 1.3 new users per second. If the initial |
290 |
* Services stamp is T, the current time, then in 100 seconds |
291 |
* (i.e. at T+100) the Services stamp will have gone to T+130. |
292 |
* (In reality, it would jump much higher on the initial net |
293 |
* burst when no users have Services stamps, but that does not |
294 |
* affect this argument.) |
295 |
* |
296 |
* If Services is now restarted, clearing the last used stamp |
297 |
* value, then assuming 5 seconds for restart, Services will |
298 |
* receive a network burst at T+105. However! While most of |
299 |
* the users will already have Services stamps, any new users |
300 |
* (as well as any users which connect after the network burst) |
301 |
* will be assigned new Services stamps starting with the |
302 |
* default value of the current time, in this case T+105. But |
303 |
* other users _already_ have Services stamp values in the |
304 |
* range T+105 to T+130 from the previous run--thus you have |
305 |
* Services stamp collisions, and all the security problems |
306 |
* that go with them. |
307 |
* |
308 |
* Obviously, this possibility does not disappear entirely by |
309 |
* using a random initial value, but it becomes much more |
310 |
* unlikely. |
311 |
* |
312 |
* Note that Unreal 3.1.1 (at least) upper-bounds values at |
313 |
* 2^31-1, so we limit ourselves to 31 bits here, even though |
314 |
* our field is unsigned. |
315 |
*/ |
316 |
|
317 |
static int32 servstamp = 0; |
318 |
|
319 |
if (servstamp == 0) |
320 |
servstamp = (rand() & 0x7FFFFFFF) | 1; |
321 |
user->servicestamp = servstamp++; |
322 |
if (servstamp <= 0) |
323 |
servstamp = 1; |
324 |
call_callback_1(cb_servicestamp_change, user); |
325 |
} |
326 |
|
327 |
if (ac >= 10 && av[9] && *av[9]) { |
328 |
/* Apply modes supplied in av[9]. Current protocol modules all |
329 |
* include a '+' before the mode letters, but allow strings |
330 |
* without the '+' for robustness. */ |
331 |
char buf[BUFSIZE]; |
332 |
char *newav[2]; |
333 |
newav[0] = user->nick; |
334 |
if (*av[9] == '+') { |
335 |
newav[1] = av[9]; |
336 |
} else { |
337 |
snprintf(buf, sizeof(buf), "+%s", av[9]); |
338 |
newav[1] = buf; |
339 |
} |
340 |
do_umode(user->nick, 2, newav); |
341 |
} |
342 |
|
343 |
} else { |
344 |
/* An old user changing nicks. */ |
345 |
char oldnick[NICKMAX]; |
346 |
|
347 |
user = get_user(source); |
348 |
if (!user) { |
349 |
log_debug(1, "user: NICK from nonexistent nick %s: %s", |
350 |
source, merge_args(ac, av)); |
351 |
return 0; |
352 |
} |
353 |
log_debug(1, "%s changes nick to %s", source, av[0]); |
354 |
|
355 |
strbcpy(oldnick, user->nick); |
356 |
call_callback_2(cb_nickchange1, user, av[0]); |
357 |
/* Flush out all mode changes; necessary to avoid desynch (otherwise |
358 |
* we can't find the user when the mode goes out later). The IRC |
359 |
* servers will take care of translating the old nick to the new one */ |
360 |
set_cmode(NULL, NULL); |
361 |
change_user_nick(user, av[0]); |
362 |
call_callback_2(cb_nickchange2, user, oldnick); |
363 |
} |
364 |
|
365 |
return 1; |
366 |
} |
367 |
|
368 |
/*************************************************************************/ |
369 |
|
370 |
/* Handle a JOIN command. |
371 |
* av[0] = channels to join |
372 |
*/ |
373 |
|
374 |
void do_join(const char *source, int ac, char **av) |
375 |
{ |
376 |
User *user; |
377 |
char *s, *t; |
378 |
|
379 |
user = get_user(source); |
380 |
if (!user) { |
381 |
log_debug(1, "user: JOIN from nonexistent user %s: %s", |
382 |
source, merge_args(ac, av)); |
383 |
return; |
384 |
} |
385 |
t = av[0]; |
386 |
while (*(s=t)) { |
387 |
t = s + strcspn(s, ","); |
388 |
if (*t) |
389 |
*t++ = 0; |
390 |
log_debug(1, "%s joins %s", source, s); |
391 |
|
392 |
if (*s == '0') |
393 |
part_all_channels(user); |
394 |
else |
395 |
join_channel(user, s, 0); |
396 |
} |
397 |
} |
398 |
|
399 |
/*************************************************************************/ |
400 |
|
401 |
/* Handle a PART command. |
402 |
* av[0] = channels to leave |
403 |
* av[1] = reason (optional) |
404 |
*/ |
405 |
|
406 |
void do_part(const char *source, int ac, char **av) |
407 |
{ |
408 |
User *user; |
409 |
char *s, *t; |
410 |
|
411 |
user = get_user(source); |
412 |
if (!user) { |
413 |
log_debug(1, "user: PART from nonexistent user %s: %s", |
414 |
source, merge_args(ac, av)); |
415 |
return; |
416 |
} |
417 |
t = av[0]; |
418 |
while (*(s=t)) { |
419 |
t = s + strcspn(s, ","); |
420 |
if (*t) |
421 |
*t++ = 0; |
422 |
log_debug(1, "%s leaves %s", source, s); |
423 |
if (!part_channel(user, s, cb_chan_part, av[1], source)) { |
424 |
log("user: do_part: no channel record for %s on %s (bug?)", |
425 |
user->nick, av[0]); |
426 |
} |
427 |
} |
428 |
} |
429 |
|
430 |
/*************************************************************************/ |
431 |
|
432 |
/* Handle a KICK command. |
433 |
* av[0] = channel |
434 |
* av[1] = nick(s) being kicked |
435 |
* av[2] = reason |
436 |
* When called internally to remove a single user (no "," in av[1]) from a |
437 |
* channel, callers may assume that the contents of the argument strings |
438 |
* will not be modified. |
439 |
*/ |
440 |
|
441 |
void do_kick(const char *source, int ac, char **av) |
442 |
{ |
443 |
User *user; |
444 |
char *s, *t; |
445 |
|
446 |
t = av[1]; |
447 |
while (*(s=t)) { |
448 |
t = s + strcspn(s, ","); |
449 |
if (*t) |
450 |
*t++ = 0; |
451 |
user = get_user(s); |
452 |
if (!user) { |
453 |
log_debug(1, "user: KICK for nonexistent user %s on %s: %s", |
454 |
s, av[0], merge_args(ac-2, av+2)); |
455 |
continue; |
456 |
} |
457 |
log_debug(1, "kicking %s from %s", s, av[0]); |
458 |
if (!part_channel(user, av[0], cb_chan_kick, av[2], source)) { |
459 |
log("user: do_kick: no channel record for %s on %s (bug?)", |
460 |
user->nick, av[0]); |
461 |
} |
462 |
} |
463 |
} |
464 |
|
465 |
/*************************************************************************/ |
466 |
|
467 |
/* Handle a MODE command for a user. |
468 |
* av[0] = nick to change mode for |
469 |
* av[1] = modes |
470 |
*/ |
471 |
|
472 |
void do_umode(const char *source, int ac, char **av) |
473 |
{ |
474 |
User *user; |
475 |
char *modestr, *s; |
476 |
int add = 1; /* 1 if adding modes, 0 if deleting */ |
477 |
|
478 |
user = get_user(av[0]); |
479 |
if (!user) { |
480 |
log_debug(1, "user: MODE %s for nonexistent nick %s from %s: %s", |
481 |
av[1], av[0], source, merge_args(ac, av)); |
482 |
return; |
483 |
} |
484 |
log_debug(1, "Changing mode for %s to %s", av[0], av[1]); |
485 |
modestr = s = av[1]; |
486 |
av += 2; |
487 |
ac -= 2; |
488 |
|
489 |
while (*s) { |
490 |
char modechar = *s++; |
491 |
int32 flag; |
492 |
int params; |
493 |
|
494 |
if (modechar == '+') { |
495 |
add = 1; |
496 |
continue; |
497 |
} else if (modechar == '-') { |
498 |
add = 0; |
499 |
continue; |
500 |
} else if (add < 0) { |
501 |
continue; |
502 |
} |
503 |
|
504 |
flag = mode_char_to_flag(modechar, MODE_USER); |
505 |
if (!flag) |
506 |
continue; |
507 |
if (flag == MODE_INVALID) |
508 |
flag = 0; |
509 |
params = mode_char_to_params(modechar, MODE_USER); |
510 |
params = (params >> (add*8)) & 0xFF; |
511 |
if (ac < params) { |
512 |
log("user: MODE %s %s: missing parameter(s) for %c%c", |
513 |
user->nick, modestr, add ? '+' : '-', modechar); |
514 |
break; |
515 |
} |
516 |
|
517 |
if (call_callback_4(cb_mode, user, modechar, add, av) <= 0) { |
518 |
if (modechar == 'o') { |
519 |
if (add) |
520 |
opcnt++; |
521 |
else |
522 |
opcnt--; |
523 |
} |
524 |
if (add) |
525 |
user->mode |= flag; |
526 |
else |
527 |
user->mode &= ~flag; |
528 |
} |
529 |
av += params; |
530 |
ac -= params; |
531 |
} |
532 |
} |
533 |
|
534 |
/*************************************************************************/ |
535 |
|
536 |
/* Handle a QUIT command. |
537 |
* av[0] = reason |
538 |
* When called internally, callers may assume that the contents of the |
539 |
* argument string will not be modified. |
540 |
*/ |
541 |
|
542 |
void do_quit(const char *source, int ac, char **av) |
543 |
{ |
544 |
User *user; |
545 |
|
546 |
user = get_user(source); |
547 |
if (!user) { |
548 |
log_debug(1, "user: QUIT from nonexistent user %s: %s", |
549 |
source, merge_args(ac, av)); |
550 |
return; |
551 |
} |
552 |
log_debug(1, "%s quits", source); |
553 |
quit_user(user, av[0], 0); |
554 |
} |
555 |
|
556 |
/*************************************************************************/ |
557 |
|
558 |
/* Handle a KILL command. |
559 |
* av[0] = nick being killed |
560 |
* av[1] = reason |
561 |
* When called internally, callers may assume that the contents of the |
562 |
* argument strings will not be modified. |
563 |
*/ |
564 |
|
565 |
void do_kill(const char *source, int ac, char **av) |
566 |
{ |
567 |
User *user; |
568 |
|
569 |
user = get_user(av[0]); |
570 |
if (!user) |
571 |
return; |
572 |
log_debug(1, "%s killed", av[0]); |
573 |
quit_user(user, av[1], 1); |
574 |
} |
575 |
|
576 |
/*************************************************************************/ |
577 |
/*************************************************************************/ |
578 |
|
579 |
/* Join a user to a channel. Return a pointer to the channel record if the |
580 |
* join succeeded, NULL otherwise. |
581 |
*/ |
582 |
|
583 |
Channel *join_channel(User *user, const char *channel, int32 modes) |
584 |
{ |
585 |
Channel *c = chan_adduser(user, channel, modes); |
586 |
struct u_chanlist *uc; |
587 |
|
588 |
if (!c) |
589 |
return NULL; |
590 |
uc = smalloc(sizeof(*uc)); |
591 |
LIST_INSERT(uc, user->chans); |
592 |
uc->chan = c; |
593 |
return c; |
594 |
} |
595 |
|
596 |
/*************************************************************************/ |
597 |
|
598 |
/* Part a user from a channel. */ |
599 |
|
600 |
int part_channel(User *user, const char *channel, int callback, |
601 |
const char *param, const char *source) |
602 |
{ |
603 |
struct u_chanlist *uc; |
604 |
LIST_SEARCH(user->chans, chan->name, channel, irc_stricmp, uc); |
605 |
if (uc) |
606 |
part_channel_uc(user, uc, callback, param, source); |
607 |
return uc != NULL; |
608 |
} |
609 |
|
610 |
/*************************************************************************/ |
611 |
|
612 |
/* Part a user from all channels s/he is in. Assumes cb_chan_part, an |
613 |
* empty `param' string, and the user as source. */ |
614 |
|
615 |
void part_all_channels(User *user) |
616 |
{ |
617 |
struct u_chanlist *uc, *nextuc; |
618 |
LIST_FOREACH_SAFE (uc, user->chans, nextuc) |
619 |
part_channel_uc(user, uc, cb_chan_part, "", user->nick); |
620 |
} |
621 |
|
622 |
/*************************************************************************/ |
623 |
/*************************************************************************/ |
624 |
|
625 |
/* Various check functions. All of these return false/NULL if any |
626 |
* parameter is NULL. */ |
627 |
|
628 |
/*************************************************************************/ |
629 |
|
630 |
/* Is the given user an oper? */ |
631 |
|
632 |
int is_oper(const User *user) |
633 |
{ |
634 |
return user != NULL && (user->mode & UMODE_o); |
635 |
} |
636 |
|
637 |
/*************************************************************************/ |
638 |
|
639 |
/* Is the given user on the given channel? Return the Channel * for the |
640 |
* channel if so, NULL if not. |
641 |
*/ |
642 |
|
643 |
Channel *is_on_chan(const User *user, const char *chan) |
644 |
{ |
645 |
struct u_chanlist *c; |
646 |
|
647 |
if (!user || !chan) |
648 |
return NULL; |
649 |
LIST_SEARCH(user->chans, chan->name, chan, irc_stricmp, c); |
650 |
return c ? c->chan : NULL; |
651 |
} |
652 |
|
653 |
/*************************************************************************/ |
654 |
|
655 |
/* Is the given user a channel operator on the given channel? */ |
656 |
|
657 |
int is_chanop(const User *user, const char *chan) |
658 |
{ |
659 |
Channel *c = chan ? get_channel(chan) : NULL; |
660 |
struct c_userlist *cu; |
661 |
|
662 |
if (!user || !chan || !c) |
663 |
return 0; |
664 |
LIST_SEARCH(c->users, user->nick, user->nick, irc_stricmp, cu); |
665 |
return cu != NULL && (cu->mode & CUMODE_o) != 0; |
666 |
} |
667 |
|
668 |
/*************************************************************************/ |
669 |
|
670 |
/* Is the given user voiced (channel mode +v) on the given channel? */ |
671 |
|
672 |
int is_voiced(const User *user, const char *chan) |
673 |
{ |
674 |
Channel *c = chan ? get_channel(chan) : NULL; |
675 |
struct c_userlist *cu; |
676 |
|
677 |
if (!user || !chan || !c) |
678 |
return 0; |
679 |
LIST_SEARCH(c->users, user->nick, user->nick, irc_stricmp, cu); |
680 |
return cu != NULL && (cu->mode & CUMODE_v) != 0; |
681 |
} |
682 |
|
683 |
/*************************************************************************/ |
684 |
/*************************************************************************/ |
685 |
|
686 |
/* Does the user's usermask match the given mask? The mask may be in |
687 |
* either nick!user@host or just user@host form. When "fakehosts" or IP |
688 |
* addresses are available in the user record, they are also checked |
689 |
* against the "host" part of the mask, and a match by any field (real |
690 |
* host, fakehost, or IP address) is treated as a match for the host part. |
691 |
* Note that CIDR matching on IP addresses is _not_ performed. |
692 |
*/ |
693 |
|
694 |
int match_usermask(const char *mask, const User *user) |
695 |
{ |
696 |
char *mask2; |
697 |
char *nick, *username, *host; |
698 |
int match_user, match_host, result; |
699 |
|
700 |
if (!mask || !user) { |
701 |
log_debug(1, "match_usermask: NULL %s!", !mask ? "mask" : "user"); |
702 |
return 0; |
703 |
} |
704 |
mask2 = sstrdup(mask); |
705 |
if (strchr(mask2, '!')) { |
706 |
nick = strtok(mask2, "!"); |
707 |
username = strtok(NULL, "@"); |
708 |
} else { |
709 |
nick = NULL; |
710 |
username = strtok(mask2, "@"); |
711 |
} |
712 |
host = strtok(NULL, ""); |
713 |
if (!host) { |
714 |
free(mask2); |
715 |
return 0; |
716 |
} |
717 |
match_user = match_wild_nocase(username, user->username); |
718 |
match_host = match_wild_nocase(host, user->host); |
719 |
if (user->fakehost) |
720 |
match_host |= match_wild_nocase(host, user->fakehost); |
721 |
if (user->ipaddr) |
722 |
match_host |= match_wild_nocase(host, user->ipaddr); |
723 |
if (nick) { |
724 |
result = match_wild_nocase(nick, user->nick) && |
725 |
match_user && match_host; |
726 |
} else { |
727 |
result = match_user && match_host; |
728 |
} |
729 |
free(mask2); |
730 |
return result; |
731 |
} |
732 |
|
733 |
/*************************************************************************/ |
734 |
|
735 |
/* Split a usermask up into its constitutent parts. Returned strings are |
736 |
* malloc()'d, and should be free()'d when done with. Returns "*" for |
737 |
* missing parts. Assumes `mask' is a non-empty string. |
738 |
*/ |
739 |
|
740 |
void split_usermask(const char *mask, char **nick, char **user, char **host) |
741 |
{ |
742 |
char *mask2 = sstrdup(mask); |
743 |
char *mynick, *myuser, *myhost; |
744 |
|
745 |
mynick = mask2; |
746 |
myuser = strchr(mask2, '!'); |
747 |
myhost = myuser ? strchr(myuser, '@') : NULL; |
748 |
if (myuser) |
749 |
*myuser++ = 0; |
750 |
if (myhost) |
751 |
*myhost++ = 0; |
752 |
/* Handle special case: mask == user@host */ |
753 |
if (mynick && !myuser && strchr(mynick, '@')) { |
754 |
mynick = NULL; |
755 |
myuser = mask2; |
756 |
myhost = strchr(mask2, '@'); |
757 |
if (myhost) /* Paranoia */ |
758 |
*myhost++ = 0; |
759 |
} |
760 |
if (!mynick || !*mynick) |
761 |
mynick = (char *)"*"; |
762 |
if (!myuser || !*myuser) |
763 |
myuser = (char *)"*"; |
764 |
if (!myhost || !*myhost) |
765 |
myhost = (char *)"*"; |
766 |
*nick = sstrdup(mynick); |
767 |
*user = sstrdup(myuser); |
768 |
*host = sstrdup(myhost); |
769 |
free(mask2); |
770 |
} |
771 |
|
772 |
/*************************************************************************/ |
773 |
|
774 |
/* Given a user, return a mask that will most likely match any address the |
775 |
* user will have from that location. For IP addresses, wildcards the last |
776 |
* octet of the address (e.g. 10.1.1.1 -> 10.1.1.*); for named addresses, |
777 |
* wildcards the leftmost part of the name unless the name only contains |
778 |
* two parts. The returned character string is malloc'd and should be |
779 |
* free'd when done with. |
780 |
* |
781 |
* Where supported, uses the fake host instead of the real one if |
782 |
* use_fakehost is nonzero. |
783 |
*/ |
784 |
|
785 |
char *create_mask(const User *user, int use_fakehost) |
786 |
{ |
787 |
char *mask, *s, *end, *host; |
788 |
|
789 |
host = user->host; |
790 |
if (use_fakehost && user->fakehost) |
791 |
host = user->fakehost; |
792 |
/* Get us a buffer the size of the username plus hostname. The result |
793 |
* will never be longer than this (and will often be shorter), thus we |
794 |
* can use strcpy() and sprintf() safely. |
795 |
*/ |
796 |
end = mask = smalloc(strlen(user->username) + strlen(host) + 2); |
797 |
end += sprintf(end, "%s@", user->username); |
798 |
if (strspn(host, "0123456789.") == strlen(host) |
799 |
&& (s = strchr(host, '.')) |
800 |
&& (s = strchr(s+1, '.')) |
801 |
&& (s = strchr(s+1, '.')) |
802 |
&& ( !strchr(s+1, '.'))) { /* IP addr */ |
803 |
s = sstrdup(host); |
804 |
*strrchr(s, '.') = 0; |
805 |
sprintf(end, "%s.*", s); |
806 |
free(s); |
807 |
} else { |
808 |
if ((s = strchr(host+1, '.')) && strchr(s+1, '.')) { |
809 |
s = sstrdup(s-1); |
810 |
*s = '*'; |
811 |
} else { |
812 |
s = sstrdup(host); |
813 |
} |
814 |
strcpy(end, s); /* safe: see above */ |
815 |
free(s); |
816 |
} |
817 |
return mask; |
818 |
} |
819 |
|
820 |
/*************************************************************************/ |
821 |
/*************************************************************************/ |
822 |
|
823 |
/* Create a new guest nick using GuestNickPrefix and a unique series of |
824 |
* digits, and return it. The returned nick is stored in a static buffer |
825 |
* and will be overwritten at the next call. |
826 |
* |
827 |
* At present, we simply use a rollover counter attached to the nick, |
828 |
* initialized to a random value. This provides for uniqueness as long as |
829 |
* Services is not restarted and a reasonable chance of uniqueness even |
830 |
* when Services is restarted (unless you have a long prefix and a short |
831 |
* maximum nick length; however, this routine will make sure at least 4 |
832 |
* digits are available, possibly by shortening the prefix). |
833 |
* |
834 |
* Note that initializing the counter based on the current time would be a |
835 |
* bad idea, since if more than one user per second (on average) connected |
836 |
* to the network, duplicate nicks would be almost guaranteed if Services |
837 |
* restarted. |
838 |
*/ |
839 |
|
840 |
char *make_guest_nick(void) |
841 |
{ |
842 |
static char nickbuf[NICKMAX+1]; /* +1 to check for overrun */ |
843 |
static uint32 counter = 0; /* Unique suffix counter */ |
844 |
int tries; /* Tries to find an unused nick */ |
845 |
int prefixlen; /* Length of nick prefix */ |
846 |
uint32 suffixmod; /* Modulo for suffix counter */ |
847 |
int i; |
848 |
|
849 |
/* Sanity checks on nick prefix length */ |
850 |
prefixlen = strlen(GuestNickPrefix); |
851 |
if (protocol_nickmax <= 4) { |
852 |
/* This violates RFC1459 as well as common sense, so just blow |
853 |
* ourselves out of the water. */ |
854 |
fatal("make_guest_nick(): protocol_nickmax too small (%d)", |
855 |
protocol_nickmax); |
856 |
} else if (prefixlen+4 > protocol_nickmax) { |
857 |
/* Reserve at least 4 digits for the suffix */ |
858 |
prefixlen = protocol_nickmax-4; |
859 |
log("warning: make_guest_nick(): GuestNickPrefix too long," |
860 |
" shortening to %d characters", prefixlen); |
861 |
GuestNickPrefix[prefixlen] = 0; |
862 |
} |
863 |
|
864 |
/* Calculate number of digits available for suffix -> suffix modulo */ |
865 |
i = protocol_nickmax - prefixlen; |
866 |
if (i < 10) { |
867 |
suffixmod = 1; |
868 |
while (i-- > 0) |
869 |
suffixmod *= 10; |
870 |
} else { |
871 |
suffixmod = 0; /* no modulo */ |
872 |
} |
873 |
|
874 |
/* Actually generate the nick. If the nick already exists, generate a |
875 |
* new one, and repeat until finding an unused nick or trying too many |
876 |
* times. If we try too many times, we just kill the last one we end |
877 |
* up with. */ |
878 |
tries = 0; |
879 |
for (;;) { |
880 |
if (counter == 0) /* initialize to random the first time */ |
881 |
counter = rand(); |
882 |
if (suffixmod) /* strip down to right number of digits */ |
883 |
counter %= suffixmod; |
884 |
if (counter == 0) /* if rand() gave us 0 or N*suffixmod... */ |
885 |
counter = 1; /* ... use 1 instead */ |
886 |
i = snprintf(nickbuf, sizeof(nickbuf), "%s%u", GuestNickPrefix, counter); |
887 |
if (i > protocol_nickmax) { |
888 |
log("BUG: make_guest_nick() generated %s but nickmax == %d!", |
889 |
nickbuf, protocol_nickmax); |
890 |
nickbuf[protocol_nickmax] = 0; |
891 |
} |
892 |
if (!get_user(nickbuf)) /* done if user not found */ |
893 |
break; |
894 |
/* Increment try count, and break out if it's too much */ |
895 |
if (++tries >= MAKEGUESTNICK_TRIES) { |
896 |
kill_user(NULL, nickbuf, "Guest nicks may not be used"); |
897 |
break; |
898 |
} |
899 |
/* Reset counter to 0 to use a new random number */ |
900 |
counter = 0; |
901 |
} |
902 |
|
903 |
/* Increment the unique suffix counter modulo suffixmod, and avoid 0 |
904 |
* (which would cause a reset to a random value) */ |
905 |
counter++; |
906 |
if (counter == suffixmod) /* because suffixmod==0 if no modulo */ |
907 |
counter = 1; |
908 |
|
909 |
/* Return the nick */ |
910 |
return nickbuf; |
911 |
}; |
912 |
|
913 |
/*************************************************************************/ |
914 |
|
915 |
/* Return nonzero if the given nickname can potentially be a guest nickname |
916 |
* (i.e. a nickname that could be generated by make_guest_nick()), zero if |
917 |
* not. Currently this means a nickname consisting of GuestNickPrefix |
918 |
* followed by between 1 and 10 digits inclusive. |
919 |
*/ |
920 |
|
921 |
int is_guest_nick(const char *nick) |
922 |
{ |
923 |
int prefixlen = strlen(GuestNickPrefix); |
924 |
int nicklen = strlen(nick); |
925 |
return nicklen >= prefixlen+1 && nicklen <= prefixlen+10 |
926 |
&& irc_strnicmp(nick, GuestNickPrefix, prefixlen) == 0 |
927 |
&& strspn(nick+prefixlen, "1234567890") == nicklen-prefixlen; |
928 |
} |
929 |
|
930 |
/*************************************************************************/ |
931 |
|
932 |
/* |
933 |
* Local variables: |
934 |
* c-file-style: "stroustrup" |
935 |
* c-file-offsets: ((case-label . *) (statement-case-intro . *)) |
936 |
* indent-tabs-mode: nil |
937 |
* End: |
938 |
* |
939 |
* vim: expandtab shiftwidth=4: |
940 |
*/ |