1 /*
2 * atheme-services: A collection of minimalist IRC services
3 * services.c: Routines commonly used by various services.
4 *
5 * Copyright (c) 2005-2007 Atheme Project (http://www.atheme.org)
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
12 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
13 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
14 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
15 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
16 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
17 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
19 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
20 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21 * POSSIBILITY OF SUCH DAMAGE.
22 */
23
24 #include "atheme.h"
25 #include "pmodule.h"
26
27 int authservice_loaded = 0;
28 int use_myuser_access = 0;
29 int use_svsignore = 0;
30 int use_privmsg = 0;
31 int use_account_private = 0;
32 int use_channel_private = 0;
33 int use_limitflags = 0;
34
35 #define MAX_BUF 256
36
37 /* ban wrapper for cmode, returns number of bans added (0 or 1) */
ban(user_t * sender,channel_t * c,user_t * user)38 int ban(user_t *sender, channel_t *c, user_t *user)
39 {
40 char mask[MAX_BUF];
41 chanban_t *cb;
42
43 if (!c)
44 return 0;
45
46 snprintf(mask, MAX_BUF, "*!*@%s", user->vhost);
47 mask[MAX_BUF - 1] = '\0';
48
49 cb = chanban_find(c, mask, 'b');
50
51 if (cb != NULL)
52 return 0;
53
54 chanban_add(c, mask, 'b');
55
56 modestack_mode_param(sender->nick, c, MTYPE_ADD, 'b', mask);
57 modestack_flush_now();
58
59 return 1;
60 }
61
62 /* returns number of modes removed -- jilles */
remove_banlike(user_t * source,channel_t * chan,int type,user_t * target)63 int remove_banlike(user_t *source, channel_t *chan, int type, user_t *target)
64 {
65 int count = 0;
66 mowgli_node_t *n, *tn;
67 chanban_t *cb;
68
69 if (type == 0)
70 return 0;
71 if (source == NULL || chan == NULL || target == NULL)
72 return 0;
73
74 for (n = next_matching_ban(chan, target, type, chan->bans.head); n != NULL; n = next_matching_ban(chan, target, type, tn))
75 {
76 tn = n->next;
77 cb = n->data;
78
79 modestack_mode_param(source->nick, chan, MTYPE_DEL, cb->type, cb->mask);
80 chanban_delete(cb);
81 count++;
82 }
83
84 modestack_flush_now();
85
86 return count;
87 }
88
89 /* returns number of exceptions removed -- jilles */
remove_ban_exceptions(user_t * source,channel_t * chan,user_t * target)90 int remove_ban_exceptions(user_t *source, channel_t *chan, user_t *target)
91 {
92 return remove_banlike(source, chan, ircd->except_mchar, target);
93 }
94
try_kick_real(user_t * source,channel_t * chan,user_t * target,const char * reason)95 void try_kick_real(user_t *source, channel_t *chan, user_t *target, const char *reason)
96 {
97 chanuser_t *cu;
98
99 return_if_fail(source != NULL);
100 return_if_fail(chan != NULL);
101 return_if_fail(target != NULL);
102 return_if_fail(reason != NULL);
103
104 cu = chanuser_find(chan, target);
105 if (cu == NULL)
106 return;
107
108 if ((chan->modes & ircd->oimmune_mode || cu->modes & CSTATUS_IMMUNE) && is_ircop(target))
109 {
110 wallops("Not kicking oper %s!%s@%s from protected %s (%s: %s)",
111 target->nick, target->user, target->vhost,
112 chan->name, source ? source->nick : me.name,
113 reason);
114 notice(source->nick, chan->name,
115 "Not kicking oper %s (%s)",
116 target->nick, reason);
117 return;
118 }
119 if (target->flags & config_options.immune_level)
120 {
121 wallops("Not kicking immune user %s!%s@%s from %s (%s: %s)",
122 target->nick, target->user, target->vhost,
123 chan->name, source ? source->nick : me.name,
124 reason);
125 notice(source->nick, chan->name,
126 "Not kicking immune user %s (%s)",
127 target->nick, reason);
128 return;
129 }
130 kick(source, chan, target, reason);
131 }
132 void (*try_kick)(user_t *source, channel_t *chan, user_t *target, const char *reason) = try_kick_real;
133
134 /* sends a KILL message for a user and removes the user from the userlist
135 * source should be a service user or NULL for a server kill
136 */
kill_user(user_t * source,user_t * victim,const char * fmt,...)137 void kill_user(user_t *source, user_t *victim, const char *fmt, ...)
138 {
139 va_list ap;
140 char buf[BUFSIZE];
141 char qreason[512];
142
143 return_if_fail(victim != NULL);
144
145 va_start(ap, fmt);
146 vsnprintf(buf, BUFSIZE, fmt, ap);
147 va_end(ap);
148
149 if (victim->flags & UF_ENFORCER)
150 quit_sts(victim, buf);
151 else if (victim->server == me.me)
152 {
153 slog(LG_INFO, "kill_user(): not killing service %s (source %s, comment %s)",
154 victim->nick,
155 source != NULL ? source->nick : me.name,
156 buf);
157 return;
158 }
159 else
160 kill_id_sts(source, CLIENT_NAME(victim), buf);
161 snprintf(qreason, sizeof qreason, "Killed (%s (%s))",
162 source != NULL ? source->nick : me.name, buf);
163 user_delete(victim, qreason);
164 }
165
introduce_enforcer(const char * nick)166 void introduce_enforcer(const char *nick)
167 {
168 user_t *u;
169
170 /* TS 1 to win nick collisions */
171 u = user_add(nick, "enforcer", me.name, NULL, NULL,
172 ircd->uses_uid ? uid_get() : NULL,
173 "Held for nickname owner", me.me, 1);
174 return_if_fail(u != NULL);
175 u->flags |= UF_INVIS | UF_ENFORCER;
176 introduce_nick(u);
177 }
178
179 /* join a channel, creating it if necessary */
join(const char * chan,const char * nick)180 void join(const char *chan, const char *nick)
181 {
182 channel_t *c;
183 user_t *u;
184 chanuser_t *cu;
185 bool isnew = false;
186 mychan_t *mc;
187 metadata_t *md;
188 time_t ts;
189
190 u = user_find_named(nick);
191 if (!u)
192 return;
193 c = channel_find(chan);
194 if (c == NULL)
195 {
196 mc = mychan_find(chan);
197 if (chansvs.changets && mc != NULL)
198 {
199 /* Use the previous TS if known, registration
200 * time otherwise, but never ever create a channel
201 * with TS 0 -- jilles */
202 ts = mc->registered;
203 md = metadata_find(mc, "private:channelts");
204 if (md != NULL)
205 ts = atol(md->value);
206 if (ts == 0)
207 ts = CURRTIME;
208 }
209 else
210 ts = CURRTIME;
211 c = channel_add(chan, ts, me.me);
212 c->modes |= CMODE_NOEXT | CMODE_TOPIC;
213 if (mc != NULL)
214 check_modes(mc, false);
215 isnew = true;
216 }
217 else if (chanuser_find(c, u))
218 {
219 slog(LG_DEBUG, "join(): i'm already in `%s'", c->name);
220 return;
221 }
222 join_sts(c, u, isnew, channel_modes(c, true));
223 cu = chanuser_add(c, CLIENT_NAME(u));
224 cu->modes |= CSTATUS_OP;
225 if (isnew)
226 {
227 hook_call_channel_add(c);
228 }
229 }
230
231 /* part a channel */
part(const char * chan,const char * nick)232 void part(const char *chan, const char *nick)
233 {
234 channel_t *c = channel_find(chan);
235 user_t *u = user_find_named(nick);
236
237 if (!u || !c)
238 return;
239 if (!chanuser_find(c, u))
240 return;
241 if (me.connected)
242 part_sts(c, u);
243 chanuser_delete(c, u);
244 }
245
services_init(void)246 void services_init(void)
247 {
248 service_t *svs;
249 mowgli_patricia_iteration_state_t state;
250
251 MOWGLI_PATRICIA_FOREACH(svs, &state, services_name)
252 {
253 if (ircd->uses_uid && svs->me->uid == NULL)
254 user_changeuid(svs->me, uid_get());
255 else if (!ircd->uses_uid && svs->me->uid != NULL)
256 user_changeuid(svs->me, NULL);
257 if (!ircd->uses_uid)
258 kill_id_sts(NULL, svs->nick, "Attempt to use service nick");
259 introduce_nick(svs->me);
260 }
261
262 hook_add_event("user_can_login");
263 }
264
joinall(const char * name)265 void joinall(const char *name)
266 {
267 service_t *svs;
268 mowgli_patricia_iteration_state_t state;
269
270 if (name == NULL)
271 return;
272
273 MOWGLI_PATRICIA_FOREACH(svs, &state, services_name)
274 {
275 /* service must be online and not a botserv bot */
276 if (svs->me == NULL || svs->botonly)
277 continue;
278 join(name, svs->me->nick);
279 }
280 }
281
partall(const char * name)282 void partall(const char *name)
283 {
284 mowgli_patricia_iteration_state_t state;
285 service_t *svs;
286 mychan_t *mc;
287
288 if (name == NULL)
289 return;
290 mc = mychan_find(name);
291 MOWGLI_PATRICIA_FOREACH(svs, &state, services_name)
292 {
293 if (svs == chansvs.me && mc != NULL && mc->flags & MC_GUARD)
294 continue;
295 if (svs->me == NULL)
296 continue;
297 /* Do not cache this channel_find(), the
298 * channel may disappear under our feet
299 * -- jilles */
300 if (chanuser_find(channel_find(name), svs->me))
301 part(name, svs->me->nick);
302 }
303 }
304
305 /* reintroduce a service e.g. after it's been killed -- jilles */
reintroduce_user(user_t * u)306 void reintroduce_user(user_t *u)
307 {
308 mowgli_node_t *n;
309 channel_t *c;
310 service_t *svs;
311
312 svs = service_find_nick(u->nick);
313 if (svs == NULL)
314 {
315 slog(LG_DEBUG, "tried to reintroduce_user non-service %s", u->nick);
316 return;
317 }
318 /* Reintroduce with a new UID. This avoids problems distinguishing
319 * commands targeted at the old and new user.
320 */
321 if (u->uid != NULL)
322 {
323 user_changeuid(u, uid_get());
324 }
325 else
326 {
327 /* Ensure it is really gone before introducing the new one.
328 * This also helps with nick collisions.
329 * With UID this is not necessary as we will
330 * reintroduce with a new UID, and nick collisions
331 * are unambiguous.
332 */
333 if (!ircd->uses_uid)
334 kill_id_sts(NULL, u->nick, "Service nick");
335 }
336 introduce_nick(u);
337 MOWGLI_ITER_FOREACH(n, u->channels.head)
338 {
339 c = ((chanuser_t *)n->data)->chan;
340 if (MOWGLI_LIST_LENGTH(&c->members) > 1 || c->modes & ircd->perm_mode)
341 join_sts(c, u, 0, channel_modes(c, true));
342 else
343 {
344 /* channel will have been destroyed... */
345 /* XXX resend the bans instead of destroying them? */
346 chanban_clear(c);
347 join_sts(c, u, 1, channel_modes(c, true));
348 if (c->topic != NULL)
349 topic_sts(c, chansvs.me->me, c->topic_setter, c->topicts, 0, c->topic);
350 }
351 }
352 }
353
verbose(mychan_t * mychan,const char * fmt,...)354 void verbose(mychan_t *mychan, const char *fmt, ...)
355 {
356 va_list ap;
357 char buf[BUFSIZE];
358
359 if (mychan->chan == NULL)
360 return;
361
362 va_start(ap, fmt);
363 vsnprintf(buf, BUFSIZE, fmt, ap);
364 va_end(ap);
365
366 if ((MC_VERBOSE | MC_FORCEVERBOSE) & mychan->flags)
367 notice(chansvs.nick, mychan->name, "%s", buf);
368 else if (MC_VERBOSE_OPS & mychan->flags)
369 wallchops(chansvs.me->me, mychan->chan, buf);
370 }
371
372 /* protocol wrapper for nickchange/nick burst */
handle_nickchange(user_t * u)373 void handle_nickchange(user_t *u)
374 {
375 service_t *svs;
376
377 return_if_fail(u != NULL);
378 return_if_fail(!is_internal_client(u));
379
380 svs = service_find("global");
381
382 if (runflags & RF_LIVE && log_debug_enabled())
383 notice(svs != NULL ? svs->me->nick : me.name, u->nick, "Services are presently running in debug mode, attached to a console. You should take extra caution when utilizing your services passwords.");
384
385 if (readonly)
386 notice(svs != NULL ? svs->me->nick : me.name, u->nick, "Services are presently running in readonly mode. Any changes you make will not be saved.");
387
388 hook_call_nick_check(u);
389 }
390
391 /* User u is bursted as being logged in to login (if not NULL) or as
392 * being identified to their current nick (if login is NULL)
393 * The timestamp is the time of registration of the account, if the ircd
394 * stores this, or 0 if not known.
395 * Update the administration or log them out on ircd
396 * How to use this in protocol modules:
397 * 1. if login info is bursted in a command that always occurs, call
398 * this if the user is logged in, before handle_nickchange()
399 * 2. if it is bursted in a command that doesn't always occur, use
400 * netwide EOB as in the ratbox module; call this if the user is logged
401 * in; for all users, postpone handle_nickchange() until the user's
402 * server confirms EOB
403 * -- jilles
404 */
handle_burstlogin(user_t * u,const char * login,time_t ts)405 void handle_burstlogin(user_t *u, const char *login, time_t ts)
406 {
407 mynick_t *mn;
408 myuser_t *mu;
409 mowgli_node_t *n;
410
411 if (login != NULL)
412 /* don't allow alias nicks here -- jilles */
413 mu = myuser_find(login);
414 else
415 {
416 mn = mynick_find(u->nick);
417 mu = mn != NULL ? mn->owner : NULL;
418 login = mu != NULL ? entity(mu)->name : u->nick;
419 }
420 if (mu == NULL)
421 {
422 /* account dropped during split...
423 * if we have an authentication service, log them out */
424 if (authservice_loaded || backend_loaded)
425 {
426 slog(LG_DEBUG, "handle_burstlogin(): got nonexistent login %s for user %s", login, u->nick);
427 if (authservice_loaded)
428 {
429 notice(nicksvs.nick ? nicksvs.nick : me.name, u->nick, _("Account %s dropped, forcing logout"), login);
430 ircd_on_logout(u, login);
431 }
432 return;
433 }
434 /* we're running without a persistent db, create it */
435 mu = myuser_add(login, "*", "noemail", MU_CRYPTPASS);
436 if (ts != 0)
437 mu->registered = ts;
438 metadata_add(mu, "fake", "1");
439 }
440 if (u->myuser != NULL) /* already logged in, hmm */
441 return;
442 if (ts != 0 && ts != mu->registered)
443 {
444 /* wrong account creation time
445 * if we have an authentication service, log them out */
446 slog(LG_INFO, "handle_burstlogin(): got stale login %s for user %s", login, u->nick);
447 if (authservice_loaded)
448 {
449 notice(nicksvs.nick ? nicksvs.nick : me.name, u->nick, _("Login to account %s is stale, forcing logout"), login);
450 ircd_on_logout(u, login);
451 }
452 return;
453 }
454 if (mu->flags & MU_NOBURSTLOGIN && authservice_loaded)
455 {
456 /* no splits for this account, this bursted login cannot
457 * be legit...
458 * if we have an authentication service, log them out */
459 slog(LG_INFO, "handle_burstlogin(): got illegit login %s for user %s", login, u->nick);
460 notice(nicksvs.nick ? nicksvs.nick : me.name, u->nick, _("Login to account %s seems invalid, forcing logout"), login);
461 ircd_on_logout(u, login);
462 return;
463 }
464 u->myuser = mu;
465 u->flags &= ~UF_SOPER_PASS;
466 n = mowgli_node_create();
467 mowgli_node_add(u, n, &mu->logins);
468 slog(LG_DEBUG, "handle_burstlogin(): automatically identified %s as %s", u->nick, login);
469
470 /* XXX: ugh, this is a lame hack but I can't think of anything better... --nenolod */
471 if (mu->flags & MU_PENDINGLOGIN && authservice_loaded)
472 {
473 slog(LG_DEBUG, "handle_burstlogin(): handling pending login hooks for %s", u->nick);
474 mu->flags &= ~MU_PENDINGLOGIN;
475 hook_call_user_identify(u);
476 }
477 }
478
handle_setlogin(sourceinfo_t * si,user_t * u,const char * login,time_t ts)479 void handle_setlogin(sourceinfo_t *si, user_t *u, const char *login, time_t ts)
480 {
481 mynick_t *mn;
482 myuser_t *mu;
483 mowgli_node_t *n;
484
485 if (login != NULL)
486 /* don't allow alias nicks here -- jilles */
487 mu = myuser_find(login);
488 else
489 {
490 mn = mynick_find(u->nick);
491 mu = mn != NULL ? mn->owner : NULL;
492 login = mu != NULL ? entity(mu)->name : u->nick;
493 }
494
495 if (authservice_loaded)
496 {
497 wallops("Ignoring attempt from %s to set login name for %s to %s",
498 get_oper_name(si), u->nick, login);
499 return;
500 }
501
502 if (u->myuser != NULL)
503 {
504 n = mowgli_node_find(u, &u->myuser->logins);
505 if (n != NULL)
506 {
507 mowgli_node_delete(n, &u->myuser->logins);
508 mowgli_node_free(n);
509 }
510 u->myuser = NULL;
511 }
512 if (mu == NULL)
513 {
514 if (backend_loaded)
515 {
516 slog(LG_DEBUG, "handle_setlogin(): got nonexistent login %s for user %s", login, u->nick);
517 return;
518 }
519 /* we're running without a persistent db, create it */
520 mu = myuser_add(login, "*", "noemail", MU_CRYPTPASS);
521 if (ts != 0)
522 mu->registered = ts;
523 metadata_add(mu, "fake", "1");
524 }
525 else if (ts != 0 && ts != mu->registered)
526 {
527 if (backend_loaded)
528 {
529 slog(LG_DEBUG, "handle_setlogin(): got unexpected registration time for login %s for user %s (%lu != %lu)",
530 login, u->nick,
531 (unsigned long)ts,
532 (unsigned long)mu->registered);
533 return;
534 }
535 if (MOWGLI_LIST_LENGTH(&mu->logins))
536 slog(LG_INFO, "handle_setlogin(): account %s with changing registration time has logins", login);
537 slog(LG_DEBUG, "handle_setlogin(): changing registration time for %s from %lu to %lu",
538 entity(mu)->name, (unsigned long)mu->registered,
539 (unsigned long)ts);
540 mu->registered = ts;
541 }
542 u->myuser = mu;
543 u->flags &= ~UF_SOPER_PASS;
544 n = mowgli_node_create();
545 mowgli_node_add(u, n, &mu->logins);
546 slog(LG_DEBUG, "handle_setlogin(): %s set %s logged in as %s",
547 get_oper_name(si), u->nick, login);
548 }
549
handle_clearlogin(sourceinfo_t * si,user_t * u)550 void handle_clearlogin(sourceinfo_t *si, user_t *u)
551 {
552 mowgli_node_t *n;
553
554 if (authservice_loaded)
555 {
556 wallops("Ignoring attempt from %s to clear login name for %s",
557 get_oper_name(si), u->nick);
558 return;
559 }
560
561 if (u->myuser == NULL)
562 return;
563
564 slog(LG_DEBUG, "handle_clearlogin(): %s cleared login for %s (%s)",
565 get_oper_name(si), u->nick, entity(u->myuser)->name);
566 n = mowgli_node_find(u, &u->myuser->logins);
567 if (n != NULL)
568 {
569 mowgli_node_delete(n, &u->myuser->logins);
570 mowgli_node_free(n);
571 }
572 u->myuser = NULL;
573 }
574
handle_certfp(sourceinfo_t * si,user_t * u,const char * certfp)575 void handle_certfp(sourceinfo_t *si, user_t *u, const char *certfp)
576 {
577 myuser_t *mu;
578 mycertfp_t *mcfp;
579 service_t *svs;
580 hook_user_login_check_t req;
581
582 free(u->certfp);
583 u->certfp = sstrdup(certfp);
584
585 if (u->myuser != NULL)
586 return;
587
588 if ((mcfp = mycertfp_find(certfp)) == NULL)
589 return;
590
591 mu = mcfp->mu;
592 svs = service_find("nickserv");
593 if (svs == NULL)
594 return;
595
596 if (metadata_find(mu, "private:freeze:freezer"))
597 {
598 notice(svs->me->nick, u->nick, nicksvs.no_nick_ownership ? "You cannot login as \2%s\2 because the account has been frozen." : "You cannot identify to \2%s\2 because the nickname has been frozen.", entity(mu)->name);
599 logcommand_user(svs, u, CMDLOG_LOGIN, "failed LOGIN to %s (frozen) via CERTFP (%s)", entity(mu)->name, certfp);
600 return;
601 }
602
603 if (MOWGLI_LIST_LENGTH(&mu->logins) >= me.maxlogins)
604 {
605 notice(svs->me->nick, u->nick, _("There are already \2%zu\2 sessions logged in to \2%s\2 (maximum allowed: %u)."), MOWGLI_LIST_LENGTH(&mu->logins), entity(mu)->name, me.maxlogins);
606 return;
607 }
608
609 req.si = si;
610 req.mu = mu;
611 req.allowed = true;
612 hook_call_user_can_login(&req);
613 if (!req.allowed)
614 {
615 return;
616 }
617
618 notice(svs->me->nick, u->nick, nicksvs.no_nick_ownership ? _("You are now logged in as \2%s\2.") : _("You are now identified for \2%s\2."), entity(mu)->name);
619
620 myuser_login(svs, u, mu, true);
621 logcommand_user(svs, u, CMDLOG_LOGIN, "LOGIN via CERTFP (%s)", certfp);
622 }
623
myuser_login(service_t * svs,user_t * u,myuser_t * mu,bool sendaccount)624 void myuser_login(service_t *svs, user_t *u, myuser_t *mu, bool sendaccount)
625 {
626 char lau[BUFSIZE], lao[BUFSIZE];
627 char strfbuf[BUFSIZE];
628 metadata_t *md_failnum;
629 struct tm tm;
630 mynick_t *mn;
631
632 return_if_fail(svs != NULL && svs->me != NULL);
633 return_if_fail(u->myuser == NULL);
634
635 if (is_soper(mu))
636 slog(LG_INFO, "SOPER: \2%s\2 as \2%s\2", u->nick, entity(mu)->name);
637
638 myuser_notice(svs->me->nick, mu, "%s!%s@%s has just authenticated as you (%s)", u->nick, u->user, u->vhost, entity(mu)->name);
639
640 u->myuser = mu;
641 mowgli_node_add(u, mowgli_node_create(), &mu->logins);
642 u->flags &= ~UF_SOPER_PASS;
643
644 /* keep track of login address for users */
645 mowgli_strlcpy(lau, u->user, BUFSIZE);
646 mowgli_strlcat(lau, "@", BUFSIZE);
647 mowgli_strlcat(lau, u->vhost, BUFSIZE);
648 metadata_add(mu, "private:host:vhost", lau);
649
650 /* and for opers */
651 mowgli_strlcpy(lao, u->user, BUFSIZE);
652 mowgli_strlcat(lao, "@", BUFSIZE);
653 mowgli_strlcat(lao, u->host, BUFSIZE);
654 metadata_add(mu, "private:host:actual", lao);
655
656 /* check for failed attempts and let them know */
657 if ((md_failnum = metadata_find(mu, "private:loginfail:failnum")) && (atoi(md_failnum->value) > 0))
658 {
659 metadata_t *md_failtime, *md_failaddr;
660 time_t ts = CURRTIME;
661
662 notice(svs->me->nick, u->nick, "\2%d\2 failed %s since last login.",
663 atoi(md_failnum->value), (atoi(md_failnum->value) == 1) ? "login" : "logins");
664
665 md_failtime = metadata_find(mu, "private:loginfail:lastfailtime");
666 if (md_failtime != NULL)
667 ts = atol(md_failtime->value);
668
669 md_failaddr = metadata_find(mu, "private:loginfail:lastfailaddr");
670 if (md_failaddr != NULL)
671 {
672 tm = *localtime(&ts);
673 strftime(strfbuf, sizeof strfbuf, TIME_FORMAT, &tm);
674
675 notice(svs->me->nick, u->nick, "Last failed attempt from: \2%s\2 on %s.",
676 md_failaddr->value, strfbuf);
677 }
678
679 metadata_delete(mu, "private:loginfail:failnum"); /* md_failnum now invalid */
680 metadata_delete(mu, "private:loginfail:lastfailtime");
681 metadata_delete(mu, "private:loginfail:lastfailaddr");
682 }
683
684 mu->lastlogin = CURRTIME;
685 mn = mynick_find(u->nick);
686 if (mn != NULL && mn->owner == mu)
687 mn->lastseen = CURRTIME;
688
689 /* XXX: ircd_on_login supports hostmasking, we just dont have it yet. */
690 /* don't allow them to join regonly chans until their
691 * email is verified */
692 if (sendaccount && !(mu->flags & MU_WAITAUTH))
693 ircd_on_login(u, mu, NULL);
694
695 hook_call_user_identify(u);
696 }
697
698 /* this could be done with more finesse, but hey! */
generic_notice(const char * from,const char * to,const char * fmt,...)699 static void generic_notice(const char *from, const char *to, const char *fmt, ...)
700 {
701 va_list args;
702 char buf[BUFSIZE];
703 user_t *u;
704 channel_t *c;
705
706 va_start(args, fmt);
707 vsnprintf(buf, BUFSIZE, fmt, args);
708 va_end(args);
709
710 if (*to == '#')
711 {
712 c = channel_find(to);
713 if (c != NULL)
714 notice_channel_sts(user_find_named(from), c, buf);
715 }
716 else
717 {
718 u = user_find_named(to);
719 if (u != NULL)
720 {
721 if (u->myuser != NULL && u->myuser->flags & MU_USE_PRIVMSG)
722 msg(from, to, "%s", buf);
723 else
724 notice_user_sts(user_find_named(from), u, buf);
725 }
726 }
727 }
728 void (*notice) (const char *from, const char *target, const char *fmt, ...) = generic_notice;
729
730 /*
731 * change_notify()
732 *
733 * Sends a change notification to a user affected by that change, provided
734 * they have not disabled the messages (MU_QUIETCHG is not set).
735 *
736 * Inputs:
737 * - string representing source (for compatibility with notice())
738 * - user_t object to send the notice to
739 * - printf-style string containing the data to send and any args
740 *
741 * Outputs:
742 * - nothing
743 *
744 * Side Effects:
745 * - a notice is sent to a user if MU_QUIETCHG is not set.
746 */
change_notify(const char * from,user_t * to,const char * fmt,...)747 void change_notify(const char *from, user_t *to, const char *fmt, ...)
748 {
749 va_list args;
750 char buf[BUFSIZE];
751
752 va_start(args, fmt);
753 vsnprintf(buf, BUFSIZE, fmt, args);
754 va_end(args);
755
756 if (is_internal_client(to))
757 return;
758 if (to->myuser != NULL && to->myuser->flags & MU_QUIETCHG)
759 return;
760
761 notice_user_sts(user_find_named(from), to, buf);
762 }
763
764 /*
765 * bad_password()
766 *
767 * Registers an attempt to authenticate with an incorrect password.
768 *
769 * Inputs:
770 * - sourceinfo_t representing what sent the bad password
771 * - myuser_t object attempt was against
772 *
773 * Outputs:
774 * - whether the user was killed off the network
775 *
776 * Side Effects:
777 * - flood counter is incremented for user
778 * - attempt is registered in metadata
779 * - opers warned if necessary
780 *
781 * Note:
782 * - kills are currently not done
783 */
bad_password(sourceinfo_t * si,myuser_t * mu)784 bool bad_password(sourceinfo_t *si, myuser_t *mu)
785 {
786 const char *mask;
787 struct tm tm;
788 char numeric[21], strfbuf[BUFSIZE];
789 int count;
790 metadata_t *md_failnum;
791 service_t *svs;
792
793 /* If the user is already logged in, no paranoia is needed,
794 * as they could /ns set password anyway.
795 */
796 if (si->smu == mu)
797 return false;
798
799 command_add_flood(si, FLOOD_MODERATE);
800
801 mask = get_source_mask(si);
802
803 md_failnum = metadata_find(mu, "private:loginfail:failnum");
804 count = md_failnum ? atoi(md_failnum->value) : 0;
805 count++;
806 snprintf(numeric, sizeof numeric, "%d", count);
807 md_failnum = metadata_add(mu, "private:loginfail:failnum", numeric);
808 metadata_add(mu, "private:loginfail:lastfailaddr", mask);
809 snprintf(numeric, sizeof numeric, "%lu", (unsigned long)CURRTIME);
810 metadata_add(mu, "private:loginfail:lastfailtime", numeric);
811
812 svs = si->service;
813 if (svs == NULL)
814 svs = service_find("nickserv");
815 if (svs != NULL)
816 {
817 myuser_notice(svs->me->nick, mu, "\2%s\2 failed to login to \2%s\2. There %s been \2%d\2 failed login %s since your last successful login.", mask, entity(mu)->name, count == 1 ? "has" : "have", count, count == 1 ? "attempt" : "attempts");
818 }
819
820 if (is_soper(mu))
821 slog(LG_INFO, "SOPER:AF: \2%s\2 as \2%s\2", get_source_name(si), entity(mu)->name);
822
823 if (count % 10 == 0)
824 {
825 time_t ts = CURRTIME;
826 tm = *localtime(&ts);
827 strftime(strfbuf, sizeof strfbuf, TIME_FORMAT, &tm);
828 wallops("Warning: \2%d\2 failed login attempts to \2%s\2. Last attempt received from \2%s\2 on %s.", count, entity(mu)->name, mask, strfbuf);
829 }
830
831 return false;
832 }
833
834 mowgli_heap_t *sourceinfo_heap = NULL;
835
sourceinfo_delete(sourceinfo_t * si)836 static void sourceinfo_delete(sourceinfo_t *si)
837 {
838 mowgli_heap_free(sourceinfo_heap, si);
839 }
840
sourceinfo_create(void)841 sourceinfo_t *sourceinfo_create(void)
842 {
843 sourceinfo_t *out;
844
845 if (sourceinfo_heap == NULL)
846 sourceinfo_heap = sharedheap_get(sizeof(sourceinfo_t));
847
848 out = mowgli_heap_alloc(sourceinfo_heap);
849 object_init(object(out), "<sourceinfo>", (destructor_t) sourceinfo_delete);
850
851 return out;
852 }
853
command_fail(sourceinfo_t * si,cmd_faultcode_t code,const char * fmt,...)854 void command_fail(sourceinfo_t *si, cmd_faultcode_t code, const char *fmt, ...)
855 {
856 va_list args;
857 char buf[BUFSIZE];
858
859 va_start(args, fmt);
860 vsnprintf(buf, sizeof buf, fmt, args);
861 va_end(args);
862
863 if (si->v != NULL && si->v->cmd_fail)
864 {
865 si->v->cmd_fail(si, code, buf);
866 return;
867 }
868 if (si->su == NULL)
869 return;
870
871 if (use_privmsg && si->smu != NULL && si->smu->flags & MU_USE_PRIVMSG)
872 msg(si->service->nick, si->su->nick, "%s", buf);
873 else
874 notice_user_sts(si->service->me, si->su, buf);
875 }
876
command_success_nodata(sourceinfo_t * si,const char * fmt,...)877 void command_success_nodata(sourceinfo_t *si, const char *fmt, ...)
878 {
879 va_list args;
880 char buf[BUFSIZE];
881 char *p, *q;
882 char space[] = " ";
883
884 if (si->output_limit && si->output_count > si->output_limit)
885 return;
886 si->output_count++;
887
888 va_start(args, fmt);
889 vsnprintf(buf, BUFSIZE, fmt, args);
890 va_end(args);
891
892 if (si->v != NULL && si->v->cmd_success_nodata)
893 {
894 si->v->cmd_success_nodata(si, buf);
895 return;
896 }
897 if (si->su == NULL)
898 return;
899
900 if (si->output_limit && si->output_count > si->output_limit)
901 {
902 notice(si->service->nick, si->su->nick, _("Output limit (%u) exceeded, halting output"), si->output_limit);
903 return;
904 }
905
906 p = buf;
907 do
908 {
909 q = strchr(p, '\n');
910 if (q != NULL)
911 {
912 *q++ = '\0';
913 if (*q == '\0')
914 return; /* ending with \n */
915 }
916 if (*p == '\0')
917 p = space; /* replace empty lines with a space */
918 if (use_privmsg && si->smu != NULL && si->smu->flags & MU_USE_PRIVMSG)
919 msg(si->service->nick, si->su->nick, "%s", p);
920 else
921 notice_user_sts(si->service->me, si->su, p);
922 p = q;
923 } while (p != NULL);
924 }
925
command_success_string(sourceinfo_t * si,const char * result,const char * fmt,...)926 void command_success_string(sourceinfo_t *si, const char *result, const char *fmt, ...)
927 {
928 va_list args;
929 char buf[BUFSIZE];
930
931 va_start(args, fmt);
932 vsnprintf(buf, BUFSIZE, fmt, args);
933 va_end(args);
934
935 if (si->v != NULL && si->v->cmd_success_string)
936 {
937 si->v->cmd_success_string(si, result, buf);
938 return;
939 }
940 if (si->su == NULL)
941 return;
942
943 if (use_privmsg && si->smu != NULL && si->smu->flags & MU_USE_PRIVMSG)
944 msg(si->service->nick, si->su->nick, "%s", buf);
945 else
946 notice_user_sts(si->service->me, si->su, buf);
947 }
948
command_table_cb(const char * line,void * data)949 static void command_table_cb(const char *line, void *data)
950 {
951 command_success_nodata(data, "%s", line);
952 }
953
command_success_table(sourceinfo_t * si,table_t * table)954 void command_success_table(sourceinfo_t *si, table_t *table)
955 {
956 if (si->v != NULL && si->v->cmd_success_table)
957 {
958 si->v->cmd_success_table(si, table);
959 return;
960 }
961
962 table_render(table, command_table_cb, si);
963 }
964
get_source_name(sourceinfo_t * si)965 const char *get_source_name(sourceinfo_t *si)
966 {
967 static char result[NICKLEN+NICKLEN+10];
968
969 if (si->v != NULL && si->v->get_source_name != NULL)
970 return si->v->get_source_name(si);
971
972 if (si->su != NULL)
973 {
974 if (si->smu && !irccasecmp(si->su->nick, entity(si->smu)->name))
975 snprintf(result, sizeof result, "%s", si->su->nick);
976 else
977 snprintf(result, sizeof result, "%s (%s)", si->su->nick,
978 si->smu ? entity(si->smu)->name : "");
979 }
980 else if (si->s != NULL)
981 snprintf(result, sizeof result, "%s", si->s->name);
982 else
983 {
984 snprintf(result, sizeof result, "<%s>%s", si->v->description,
985 si->smu ? entity(si->smu)->name : "");
986 }
987 return result;
988 }
989
get_source_mask(sourceinfo_t * si)990 const char *get_source_mask(sourceinfo_t *si)
991 {
992 static char result[NICKLEN+USERLEN+HOSTLEN+10];
993
994 if (si->v != NULL && si->v->get_source_mask != NULL)
995 return si->v->get_source_mask(si);
996
997 if (si->su != NULL)
998 {
999 snprintf(result, sizeof result, "%s!%s@%s", si->su->nick,
1000 si->su->user, si->su->vhost);
1001 }
1002 else if (si->s != NULL)
1003 snprintf(result, sizeof result, "%s", si->s->name);
1004 else
1005 {
1006 snprintf(result, sizeof result, "<%s>%s", si->v->description,
1007 si->smu ? entity(si->smu)->name : "");
1008 }
1009 return result;
1010 }
1011
get_oper_name(sourceinfo_t * si)1012 const char *get_oper_name(sourceinfo_t *si)
1013 {
1014 static char result[NICKLEN+USERLEN+HOSTLEN+NICKLEN+10];
1015
1016 if (si->v != NULL && si->v->get_oper_name != NULL)
1017 return si->v->get_oper_name(si);
1018
1019 if (si->su != NULL)
1020 {
1021 if (si->smu == NULL)
1022 snprintf(result, sizeof result, "%s!%s@%s{%s}", si->su->nick,
1023 si->su->user, si->su->vhost,
1024 si->su->server->name);
1025 else if (!irccasecmp(si->su->nick, entity(si->smu)->name))
1026 snprintf(result, sizeof result, "%s", si->su->nick);
1027 else
1028 snprintf(result, sizeof result, "%s (%s)", si->su->nick,
1029 si->smu ? entity(si->smu)->name : "");
1030 }
1031 else if (si->s != NULL)
1032 snprintf(result, sizeof result, "%s", si->s->name);
1033 else
1034 {
1035 snprintf(result, sizeof result, "<%s>%s", si->v->description,
1036 si->smu ? entity(si->smu)->name : "");
1037 }
1038 return result;
1039 }
1040
get_storage_oper_name(sourceinfo_t * si)1041 const char *get_storage_oper_name(sourceinfo_t *si)
1042 {
1043 static char result[NICKLEN+USERLEN+HOSTLEN+NICKLEN+10];
1044
1045 if (si->v != NULL && si->v->get_storage_oper_name != NULL)
1046 return si->v->get_storage_oper_name(si);
1047
1048 if (si->smu != NULL)
1049 snprintf(result, sizeof result, "%s", entity(si->smu)->name);
1050 else if (si->su != NULL)
1051 {
1052 snprintf(result, sizeof result, "%s!%s@%s{%s}", si->su->nick,
1053 si->su->user, si->su->vhost,
1054 si->su->server->name);
1055 }
1056 else if (si->s != NULL)
1057 snprintf(result, sizeof result, "%s", si->s->name);
1058 else
1059 snprintf(result, sizeof result, "<%s>", si->v->description);
1060 return result;
1061 }
1062
get_source_security_label(sourceinfo_t * si)1063 const char *get_source_security_label(sourceinfo_t *si)
1064 {
1065 static char result[NICKLEN+USERLEN+HOSTLEN+NICKLEN+HOSTLEN+10];
1066 const soper_t *soper;
1067 const operclass_t *operclass = NULL;
1068
1069 mowgli_strlcpy(result, get_storage_oper_name(si), sizeof result);
1070
1071 soper = get_sourceinfo_soper(si);
1072 if (soper != NULL)
1073 {
1074 mowgli_strlcat(result, "/", sizeof result);
1075 /* If the soper is attached to an entity, then soper->name
1076 * is null. Use soper->myuser->ent.name instead. --mr_flea
1077 */
1078 mowgli_strlcat(result, soper->name ? soper->name : soper->myuser->ent.name,
1079 sizeof result);
1080
1081 operclass = soper->operclass;
1082 }
1083
1084 if (operclass == NULL)
1085 operclass = get_sourceinfo_operclass(si);
1086
1087 mowgli_strlcat(result, "/", sizeof result);
1088 mowgli_strlcat(result, operclass != NULL ? operclass->name : "unauthenticated", sizeof result);
1089
1090 return result;
1091 }
1092
wallops(const char * fmt,...)1093 void wallops(const char *fmt, ...)
1094 {
1095 va_list args;
1096 char buf[BUFSIZE];
1097
1098 if (config_options.silent)
1099 return;
1100
1101 va_start(args, fmt);
1102 vsnprintf(buf, BUFSIZE, fmt, args);
1103 va_end(args);
1104
1105 if (me.me != NULL && me.connected)
1106 wallops_sts(buf);
1107 else
1108 slog(LG_ERROR, "wallops(): unable to send: %s", buf);
1109 }
1110
verbose_wallops(const char * fmt,...)1111 void verbose_wallops(const char *fmt, ...)
1112 {
1113 va_list args;
1114 char buf[BUFSIZE];
1115
1116 if (config_options.silent || !config_options.verbose_wallops)
1117 return;
1118
1119 va_start(args, fmt);
1120 vsnprintf(buf, BUFSIZE, fmt, args);
1121 va_end(args);
1122
1123 if (me.me != NULL && me.connected)
1124 wallops_sts(buf);
1125 else
1126 slog(LG_ERROR, "verbose_wallops(): unable to send: %s", buf);
1127 }
1128
1129 /* Check validity of a vhost against both general and protocol-specific rules.
1130 * Returns true if it is ok, false if not; command_fail() will have been called
1131 * as well.
1132 */
check_vhost_validity(sourceinfo_t * si,const char * host)1133 bool check_vhost_validity(sourceinfo_t *si, const char *host)
1134 {
1135 const char *p;
1136
1137 /* Never ever allow @!?* as they have special meaning in all ircds */
1138 /* Empty, space anywhere and colon at the start break the protocol */
1139 /* Also disallow ASCII 1-31 and "' as no sane IRCd allows them in hosts */
1140 if (strchr(host, '@') || strchr(host, '!') || strchr(host, '?') ||
1141 strchr(host, '*') || strchr(host, ' ') || strchr(host, '\'') ||
1142 strchr(host, '"') || *host == ':' || *host == '\0' ||
1143 has_ctrl_chars(host))
1144 {
1145 command_fail(si, fault_badparams, _("The vhost provided contains invalid characters."));
1146 return false;
1147 }
1148 if (strlen(host) >= HOSTLEN)
1149 {
1150 command_fail(si, fault_badparams, _("The vhost provided is too long."));
1151 return false;
1152 }
1153 p = strrchr(host, '/');
1154 if (p != NULL && isdigit((unsigned char)p[1]))
1155 {
1156 command_fail(si, fault_badparams, _("The vhost provided looks like a CIDR mask."));
1157 return false;
1158 }
1159 if (!is_valid_host(host))
1160 {
1161 /* This can be stuff like missing dots too. */
1162 command_fail(si, fault_badparams, _("The vhost provided is invalid."));
1163 return false;
1164 }
1165 return true;
1166 }
1167
1168 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
1169 * vim:ts=8
1170 * vim:sw=8
1171 * vim:noexpandtab
1172 */
1173