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