1 /*
2  * Copyright (c) 2005-2006 Atheme Development Group
3  * servtree.c: Services binary tree manipulation. (add_service,
4  *    del_service, et al.)
5  *
6  * Permission to use, copy, modify, and/or distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
11  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
12  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
13  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
14  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
15  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
16  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
17  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
18  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
19  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
20  * POSSIBILITY OF SUCH DAMAGE.
21  */
22 
23 #include "atheme.h"
24 #include "pmodule.h"
25 
26 mowgli_patricia_t *services_name;
27 mowgli_patricia_t *services_nick;
28 mowgli_heap_t *service_heap;
29 
30 void servtree_update(void *dummy);
31 
dummy_handler(sourceinfo_t * si,int parc,char ** parv)32 static void dummy_handler(sourceinfo_t *si, int parc, char **parv)
33 {
34 }
35 
service_default_handler(sourceinfo_t * si,int parc,char * parv[])36 static void service_default_handler(sourceinfo_t *si, int parc, char *parv[])
37 {
38 	char *cmd;
39         char *text;
40 	char orig[BUFSIZE];
41 
42 	/* this should never happen */
43 	if (parv[0][0] == '&')
44 	{
45 		slog(LG_ERROR, "services(): got parv with local channel: %s", parv[0]);
46 		return;
47 	}
48 
49 	/* make a copy of the original for debugging */
50 	mowgli_strlcpy(orig, parv[parc - 1], BUFSIZE);
51 
52 	/* lets go through this to get the command */
53 	cmd = strtok(parv[parc - 1], " ");
54 	text = strtok(NULL, "");
55 
56 	if (!cmd)
57 		return;
58 	if (*orig == '\001')
59 	{
60 		handle_ctcp_common(si, cmd, text);
61 		return;
62 	}
63 
64 	/* take the command through the hash table */
65 	command_exec_split(si->service, si, cmd, text, si->service->commands);
66 }
67 
servtree_init(void)68 void servtree_init(void)
69 {
70 	service_heap = sharedheap_get(sizeof(service_t));
71 	services_name = mowgli_patricia_create(strcasecanon);
72 	services_nick = mowgli_patricia_create(strcasecanon);
73 
74 	if (!service_heap)
75 	{
76 		slog(LG_INFO, "servtree_init(): Block allocator failed.");
77 		exit(EXIT_FAILURE);
78 	}
79 
80         hook_add_event("config_ready");
81 	hook_add_config_ready(servtree_update);
82 }
83 
me_me_init(void)84 static void me_me_init(void)
85 {
86 	if (me.numeric)
87 		init_uid();
88 	me.me = server_add(me.name, 0, NULL, me.numeric ? me.numeric : NULL, me.desc);
89 }
90 
free_alias_string(const char * key,void * data,void * privdata)91 static void free_alias_string(const char *key, void *data, void *privdata)
92 {
93 	free(data);
94 }
95 
free_access_string(const char * key,void * data,void * privdata)96 static void free_access_string(const char *key, void *data, void *privdata)
97 {
98 	free(data);
99 }
100 
create_unique_service_nick(char * dest,size_t len)101 static void create_unique_service_nick(char *dest, size_t len)
102 {
103 	unsigned int i = arc4random();
104 
105 	do
106 		snprintf(dest, len, "ath%06x", (i++) & 0xFFFFFF);
107 	while (service_find_nick(dest) || user_find_named(dest));
108 	return;
109 }
110 
conf_service_nick(mowgli_config_file_entry_t * ce)111 static int conf_service_nick(mowgli_config_file_entry_t *ce)
112 {
113 	service_t *sptr;
114 	char newnick[NICKLEN + 1];
115 
116 	if (!ce->vardata)
117 		return -1;
118 
119 	sptr = service_find(ce->prevlevel->varname);
120 	if (!sptr)
121 		return -1;
122 
123 	mowgli_patricia_delete(services_nick, sptr->nick);
124 	free(sptr->nick);
125 
126 	if (service_find_nick(ce->vardata))
127 	{
128 		create_unique_service_nick(newnick, sizeof newnick);
129 		slog(LG_INFO, "conf_service_nick(): using nick %s for service %s due to duplication",
130 				newnick, sptr->internal_name);
131 		sptr->nick = sstrdup(newnick);
132 	}
133 	else
134 		sptr->nick = sstrdup(ce->vardata);
135 	mowgli_patricia_add(services_nick, sptr->nick, sptr);
136 
137 	return 0;
138 }
139 
conf_service_user(mowgli_config_file_entry_t * ce)140 static int conf_service_user(mowgli_config_file_entry_t *ce)
141 {
142 	service_t *sptr;
143 
144 	if (!ce->vardata)
145 		return -1;
146 
147 	sptr = service_find(ce->prevlevel->varname);
148 	if (!sptr)
149 		return -1;
150 
151 	free(sptr->user);
152 	sptr->user = sstrndup(ce->vardata, 10);
153 
154 	return 0;
155 }
156 
conf_service_host(mowgli_config_file_entry_t * ce)157 static int conf_service_host(mowgli_config_file_entry_t *ce)
158 {
159 	service_t *sptr;
160 
161 	if (!ce->vardata)
162 		return -1;
163 
164 	if (!is_valid_host(ce->vardata))
165 	{
166 		conf_report_warning(ce, "invalid hostname: %s", ce->vardata);
167 		return -1;
168 	}
169 
170 	sptr = service_find(ce->prevlevel->varname);
171 	if (!sptr)
172 		return -1;
173 
174 	free(sptr->host);
175 	sptr->host = sstrdup(ce->vardata);
176 
177 	return 0;
178 }
179 
conf_service_real(mowgli_config_file_entry_t * ce)180 static int conf_service_real(mowgli_config_file_entry_t *ce)
181 {
182 	service_t *sptr;
183 
184 	if (!ce->vardata)
185 		return -1;
186 
187 	sptr = service_find(ce->prevlevel->varname);
188 	if (!sptr)
189 		return -1;
190 
191 	free(sptr->real);
192 	sptr->real = sstrdup(ce->vardata);
193 
194 	return 0;
195 }
196 
conf_service_aliases(mowgli_config_file_entry_t * ce)197 static int conf_service_aliases(mowgli_config_file_entry_t *ce)
198 {
199 	service_t *sptr;
200 	mowgli_config_file_entry_t *subce;
201 
202 	sptr = service_find(ce->prevlevel->varname);
203 	if (!sptr)
204 		return -1;
205 
206 	if (sptr->aliases)
207 		mowgli_patricia_destroy(sptr->aliases, free_alias_string, NULL);
208 
209 	sptr->aliases = NULL;
210 	if (!ce->entries)
211 		return 0;
212 
213 	sptr->aliases = mowgli_patricia_create(strcasecanon);
214 
215 	MOWGLI_ITER_FOREACH(subce, ce->entries)
216 	{
217 		if (subce->vardata == NULL || subce->entries != NULL)
218 		{
219 			conf_report_warning(subce, "Invalid alias entry");
220 			continue;
221 		}
222 
223 		mowgli_patricia_add(sptr->aliases, subce->varname,
224 				sstrdup(subce->vardata));
225 	}
226 
227 	return 0;
228 }
229 
conf_service_access(mowgli_config_file_entry_t * ce)230 static int conf_service_access(mowgli_config_file_entry_t *ce)
231 {
232 	service_t *sptr;
233 	mowgli_config_file_entry_t *subce;
234 
235 	sptr = service_find(ce->prevlevel->varname);
236 	if (!sptr)
237 		return -1;
238 
239 	if (sptr->access)
240 		mowgli_patricia_destroy(sptr->access, free_access_string, NULL);
241 
242 	sptr->access = NULL;
243 	if (!ce->entries)
244 		return 0;
245 
246 	sptr->access = mowgli_patricia_create(strcasecanon);
247 
248 	MOWGLI_ITER_FOREACH(subce, ce->entries)
249 	{
250 		if (subce->vardata == NULL || subce->entries != NULL)
251 		{
252 			conf_report_warning(subce, "Invalid access entry");
253 			continue;
254 		}
255 
256 		mowgli_patricia_add(sptr->access, subce->varname,
257 				sstrdup(subce->vardata));
258 	}
259 
260 	return 0;
261 }
262 
conf_service(mowgli_config_file_entry_t * ce)263 static int conf_service(mowgli_config_file_entry_t *ce)
264 {
265 	service_t *sptr;
266 
267 	sptr = service_find(ce->varname);
268 	if (!sptr)
269 		return -1;
270 
271 	subblock_handler(ce, &sptr->conf_table);
272 	return 0;
273 }
274 
service_add(const char * name,void (* handler)(sourceinfo_t * si,int parc,char * parv[]))275 service_t *service_add(const char *name, void (*handler)(sourceinfo_t *si, int parc, char *parv[]))
276 {
277 	service_t *sptr;
278 	struct ConfTable *subblock;
279 	const char *nick;
280 	char newnick[NICKLEN];
281 
282 	return_val_if_fail(name != NULL, NULL);
283 	return_val_if_fail(service_find(name) == NULL, NULL);
284 
285 	sptr = mowgli_heap_alloc(service_heap);
286 
287 	sptr->internal_name = sstrdup(name);
288 	/* default these, to reasonably safe values */
289 	nick = strchr(name, ':');
290 	if (nick != NULL)
291 		nick++;
292 	else
293 		nick = name;
294 	mowgli_strlcpy(newnick, nick, sizeof newnick);
295 	if (!is_valid_nick(newnick) || service_find_nick(newnick))
296 	{
297 		create_unique_service_nick(newnick, sizeof newnick);
298 		slog(LG_INFO, "service_add(): using nick %s for service %s due to duplicate or invalid nickname",
299 				newnick, name);
300 	}
301 	sptr->nick = sstrdup(newnick);
302 	sptr->user = sstrndup(nick, 10);
303 	sptr->host = sstrdup("services.int");
304 	sptr->real = sstrndup(name, 50);
305 	sptr->disp = sstrdup(sptr->nick);
306 
307 	if (handler != NULL)
308 		sptr->handler = handler;
309 	else
310 		sptr->handler = service_default_handler;
311 
312 	sptr->notice_handler = dummy_handler;
313 	sptr->aliases = NULL;
314 	sptr->access = NULL;
315 	sptr->chanmsg = false;
316 
317 	sptr->me = NULL;
318 
319 	mowgli_patricia_add(services_name, sptr->internal_name, sptr);
320 	mowgli_patricia_add(services_nick, sptr->nick, sptr);
321 
322 	sptr->commands = mowgli_patricia_create(strcasecanon);
323 
324 	subblock = find_top_conf(name);
325 	if (subblock == NULL)
326 		add_top_conf(sptr->internal_name, conf_service);
327 	add_conf_item("NICK", &sptr->conf_table, conf_service_nick);
328 	add_conf_item("USER", &sptr->conf_table, conf_service_user);
329 	add_conf_item("HOST", &sptr->conf_table, conf_service_host);
330 	add_conf_item("REAL", &sptr->conf_table, conf_service_real);
331 	add_conf_item("ALIASES", &sptr->conf_table, conf_service_aliases);
332 	add_conf_item("ACCESS", &sptr->conf_table, conf_service_access);
333 
334 	return sptr;
335 }
336 
service_delete(service_t * sptr)337 void service_delete(service_t *sptr)
338 {
339 	struct ConfTable *subblock;
340 
341 	mowgli_patricia_delete(services_name, sptr->internal_name);
342 	mowgli_patricia_delete(services_nick, sptr->nick);
343 
344 	del_conf_item("ACCESS", &sptr->conf_table);
345 	del_conf_item("ALIASES", &sptr->conf_table);
346 	del_conf_item("REAL", &sptr->conf_table);
347 	del_conf_item("HOST", &sptr->conf_table);
348 	del_conf_item("USER", &sptr->conf_table);
349 	del_conf_item("NICK", &sptr->conf_table);
350 	subblock = find_top_conf(sptr->internal_name);
351 	if (subblock != NULL && conftable_get_conf_handler(subblock) == conf_service)
352 		del_top_conf(sptr->internal_name);
353 
354 	if (sptr->me != NULL)
355 	{
356 		quit_sts(sptr->me, "Service unloaded.");
357 		user_delete(sptr->me, "Service unloaded.");
358 		sptr->me = NULL;
359 	}
360 	sptr->handler = NULL;
361 	if (sptr->access)
362 		mowgli_patricia_destroy(sptr->access, free_access_string, NULL);
363 	if (sptr->aliases)
364 		mowgli_patricia_destroy(sptr->aliases, free_alias_string, NULL);
365 	if (sptr->commands)
366 		mowgli_patricia_destroy(sptr->commands, NULL, NULL);
367 	free(sptr->disp);	/* service_name() does a malloc() */
368 	free(sptr->internal_name);
369 	free(sptr->nick);
370 	free(sptr->user);
371 	free(sptr->host);
372 	free(sptr->real);
373 
374 	mowgli_heap_free(service_heap, sptr);
375 }
376 
service_add_static(const char * name,const char * user,const char * host,const char * real,void (* handler)(sourceinfo_t * si,int parc,char * parv[]),service_t * logtarget)377 service_t *service_add_static(const char *name, const char *user, const char *host, const char *real, void (*handler)(sourceinfo_t *si, int parc, char *parv[]), service_t *logtarget)
378 {
379 	service_t *sptr;
380 	char internal_name[NICKLEN + 10];
381 
382 	snprintf(internal_name, sizeof internal_name, "static:%s", name);
383 	sptr = service_add(internal_name, handler);
384 
385 	free(sptr->user);
386 	free(sptr->host);
387 	free(sptr->real);
388 	sptr->user = sstrndup(user, 10);
389 	sptr->host = sstrdup(host);
390 	sptr->real = sstrdup(real);
391 	sptr->botonly = true;
392 	sptr->logtarget = logtarget;
393 
394 	servtree_update(NULL);
395 
396 	return sptr;
397 }
398 
service_find_any(void)399 service_t *service_find_any(void)
400 {
401 	service_t *sptr;
402 	mowgli_patricia_iteration_state_t state;
403 
404 	MOWGLI_PATRICIA_FOREACH(sptr, &state, services_name)
405 		return sptr;
406 	return NULL;
407 }
408 
service_find(const char * name)409 service_t *service_find(const char *name)
410 {
411 	return mowgli_patricia_retrieve(services_name, name);
412 }
413 
service_find_nick(const char * nick)414 service_t *service_find_nick(const char *nick)
415 {
416 	return mowgli_patricia_retrieve(services_nick, nick);
417 }
418 
servtree_update(void * dummy)419 void servtree_update(void *dummy)
420 {
421 	service_t *sptr;
422 	mowgli_patricia_iteration_state_t state;
423 	user_t *u;
424 
425 	if (offline_mode)
426 		return;
427 
428 	if (me.me == NULL)
429 		me_me_init();
430 	if (ircd->uses_uid && !me.numeric)
431 	{
432 		slog(LG_ERROR, "servtree_update(): ircd requires numeric, but none was specified in the configuration file");
433 		exit(EXIT_FAILURE);
434 	}
435 
436 	MOWGLI_PATRICIA_FOREACH(sptr, &state, services_name)
437 	{
438 		free(sptr->disp);
439 		sptr->disp = service_name(sptr->nick);
440 		if (sptr->me != NULL)
441 		{
442 			if (!strcmp(sptr->nick, sptr->me->nick) &&
443 					!strcmp(sptr->user, sptr->me->user) &&
444 					!strcmp(sptr->host, sptr->me->host) &&
445 					!strcmp(sptr->real, sptr->me->gecos))
446 				continue;
447 			if (me.connected)
448 				quit_sts(sptr->me, "Updating information");
449 			u = user_find_named(sptr->nick);
450 			if (u != NULL && u != sptr->me)
451 				kill_user(NULL, u, "Nick taken by service");
452 			user_changenick(sptr->me, sptr->nick, CURRTIME);
453 			strshare_unref(sptr->me->user);
454 			sptr->me->user = strshare_get(sptr->user);
455 			strshare_unref(sptr->me->host);
456 			sptr->me->host = strshare_get(sptr->host);
457 			strshare_unref(sptr->me->chost);
458 			sptr->me->chost = strshare_ref(sptr->me->host);
459 			strshare_unref(sptr->me->vhost);
460 			sptr->me->vhost = strshare_ref(sptr->me->host);
461 			strshare_unref(sptr->me->gecos);
462 			sptr->me->gecos = strshare_get(sptr->real);
463 			if (me.connected)
464 				reintroduce_user(sptr->me);
465 		}
466 		else
467 		{
468 			u = user_find_named(sptr->nick);
469 			if (u != NULL)
470 				kill_user(NULL, u, "Nick taken by service");
471 			sptr->me = user_add(sptr->nick, sptr->user, sptr->host, NULL, NULL, ircd->uses_uid ? uid_get() : NULL, sptr->real, me.me, CURRTIME);
472 			sptr->me->flags |= UF_IRCOP | UF_INVIS | UF_SERVICE;
473 			if ((sptr == chansvs.me) && !chansvs.fantasy)
474 				sptr->me->flags |= UF_DEAF;
475 
476 			if (me.connected)
477 			{
478 				/*
479 				 * Possibly send a kill for the service nick now
480 				 * - do not send a kill if we already sent
481 				 *   one above
482 				 * - do not send a kill if we use UID, with
483 				 *   UID we can always kill the other user
484 				 *   in nick collisions and killing a nick
485 				 *   with UID is likely to cause the desyncs
486 				 *   UID is meant to avoid
487 				 */
488 				if (u == NULL && !ircd->uses_uid)
489 					kill_id_sts(NULL, sptr->nick, "Attempt to use service nick");
490 				introduce_nick(sptr->me);
491 				hook_call_service_introduce(sptr);
492 			}
493 		}
494 	}
495 }
496 
service_name(char * name)497 char *service_name(char *name)
498 {
499 	char *str;
500 
501 	if (config_options.secure)
502 	{
503 		str = smalloc(strlen(name) + 1 + strlen(me.name) + 1);
504 		sprintf(str, "%s@%s", name, me.name);
505 	}
506 	else
507 		str = sstrdup(name);
508 
509 	return str;
510 }
511 
service_set_chanmsg(service_t * service,bool chanmsg)512 void service_set_chanmsg(service_t *service, bool chanmsg)
513 {
514 	return_if_fail(service != NULL);
515 
516 	service->chanmsg = chanmsg;
517 }
518 
service_resolve_alias(service_t * sptr,const char * context,const char * cmd)519 const char *service_resolve_alias(service_t *sptr, const char *context, const char *cmd)
520 {
521 	char fullname[256];
522 	char *alias;
523 
524 	if (sptr->aliases == NULL)
525 		return cmd;
526 	fullname[0] = '\0';
527 	if (context != NULL)
528 	{
529 		mowgli_strlcpy(fullname, context, sizeof fullname);
530 		mowgli_strlcat(fullname, " ", sizeof fullname);
531 	}
532 	mowgli_strlcat(fullname, cmd, sizeof fullname);
533 	alias = mowgli_patricia_retrieve(sptr->aliases, fullname);
534 	return alias != NULL ? alias : cmd;
535 }
536 
service_set_access(service_t * sptr,const char * cmd,const char * oldaccess)537 const char *service_set_access(service_t *sptr, const char *cmd, const char *oldaccess)
538 {
539 	char *newaccess;
540 
541 	if (sptr->access == NULL)
542 		return oldaccess;
543 
544 	newaccess = mowgli_patricia_retrieve(sptr->access, cmd);
545 	return newaccess != NULL ? newaccess : oldaccess;
546 }
547 
service_bind_command(service_t * sptr,command_t * cmd)548 void service_bind_command(service_t *sptr, command_t *cmd)
549 {
550 	return_if_fail(sptr != NULL);
551 	return_if_fail(cmd != NULL);
552 
553 	command_add(cmd, sptr->commands);
554 }
555 
service_unbind_command(service_t * sptr,command_t * cmd)556 void service_unbind_command(service_t *sptr, command_t *cmd)
557 {
558 	return_if_fail(sptr != NULL);
559 	return_if_fail(cmd != NULL);
560 
561 	command_delete(cmd, sptr->commands);
562 }
563 
service_named_bind_command(const char * svs,command_t * cmd)564 void service_named_bind_command(const char *svs, command_t *cmd)
565 {
566 	service_t *sptr = NULL;
567 
568 	return_if_fail(svs != NULL);
569 	return_if_fail(cmd != NULL);
570 
571 	sptr = service_find(svs);
572 	if (sptr == NULL)
573 		return;
574 
575 	service_bind_command(sptr, cmd);
576 }
577 
service_named_unbind_command(const char * svs,command_t * cmd)578 void service_named_unbind_command(const char *svs, command_t *cmd)
579 {
580 	service_t *sptr = NULL;
581 
582 	return_if_fail(svs != NULL);
583 	return_if_fail(cmd != NULL);
584 
585 	sptr = service_find(svs);
586 	if (sptr == NULL)
587 		return;
588 
589 	service_unbind_command(sptr, cmd);
590 }
591 
592 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
593  * vim:ts=8
594  * vim:sw=8
595  * vim:noexpandtab
596  */
597