1 /*
2 	ctrlproxy: A modular IRC proxy
3 	admin: module for remote administration.
4 
5 	(c) 2003-2007 Jelmer Vernooij <jelmer@nl.linux.org>
6 
7 	This program is free software; you can redistribute it and/or modify
8 	it under the terms of the GNU General Public License as published by
9 	the Free Software Foundation; either version 3 of the License, or
10 	(at your option) any later version.
11 
12 	This program is distributed in the hope that it will be useful,
13 	but WITHOUT ANY WARRANTY; without even the implied warranty of
14 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 	GNU General Public License for more details.
16 
17 	You should have received a copy of the GNU General Public License
18 	along with this program; if not, write to the Free Software
19 	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21 
22 #include "internals.h"
23 #include <string.h>
24 #include <sys/un.h>
25 #include "admin.h"
26 #include "help.h"
27 #include "irc.h"
28 
29 help_t *help;
30 
31 GList *admin_commands = NULL;
32 guint longest_command = 0;
33 
34 static void add_network_helper(admin_handle h, const char *name);
35 static void del_network_helper(admin_handle h, const char *name);
36 static void list_networks_helper(admin_handle h);
37 
38 #define ADMIN_USERNAME "ctrlproxy"
39 #define ADMIN_NICK "ctrlproxy"
40 
41 /**
42  * Determine the hostmask for the admin user.
43  *
44  * @param n Network for which to generate the hostmask.
45  * @return Host mask, should be freed by the caller.
46  */
admin_hostmask(struct irc_network * n)47 static char *admin_hostmask(struct irc_network *n)
48 {
49 	return g_strdup_printf(ADMIN_NICK"!"ADMIN_USERNAME"@%s", n->name);
50 }
51 
52 /**
53  * Handle private message sent to the admin user.
54  *
55  * @param h Admin context handle.
56  * @param data Text sent.
57  */
privmsg_admin_out(admin_handle h,const char * data)58 static void privmsg_admin_out(admin_handle h, const char *data)
59 {
60 	struct irc_client *c = h->client;
61 	char *nick = c->state->me.nick;
62 	char *hostmask;
63 
64 	hostmask = admin_hostmask(c->network);
65 	if (c->network->external_state != NULL)
66 		nick = c->network->external_state->me.nick;
67 	client_send_args_ex(c, hostmask, "NOTICE", nick, data, NULL);
68 
69 	g_free(hostmask);
70 }
71 
72 /**
73  * Sent data to the admin network channel.
74  *
75  * @param h Admin context handle
76  * @param data Text to send.
77  */
network_admin_out(admin_handle h,const char * data)78 static void network_admin_out(admin_handle h, const char *data)
79 {
80 	struct irc_client *c = h->client;
81 	char *hostmask;
82 
83 	hostmask = admin_hostmask(c->network);
84 	virtual_network_recv_args(c->network, hostmask, "PRIVMSG", ADMIN_CHANNEL,
85 							  data, NULL);
86 
87 	g_free(hostmask);
88 }
89 
90 /**
91  * Handles the 'help' command.
92  * @param h Admin context handle
93  * @param args Arguments
94  * @param userdata User context data (ignored)
95  */
cmd_help(admin_handle h,const char * const * args,void * userdata)96 static void cmd_help(admin_handle h, const char * const *args, void *userdata)
97 {
98 	const char *s;
99 
100 	s = help_get(help, args[1] != NULL?args[1]:"index");
101 
102 	if (s == NULL) {
103 		if (args[1] == NULL)
104 			admin_out(h, "Sorry, help not available");
105 		else
106 			admin_out(h, "Sorry, no help for %s available", args[1]);
107 		return;
108 	}
109 
110 	while (strncmp(s, "%\n", 2) != 0) {
111 		char *tmp;
112 		admin_out(h, "%s", tmp = g_strndup(s, strchr(s, '\n')-s));
113 		g_free(tmp);
114 
115 		s = strchr(s, '\n')+1;
116 	}
117 }
118 
119 /**
120  * Return the client handle associated with an admin context.
121  * @param h Admin context handle
122  * @return Client, or NULL if no client is associated.
123  */
admin_get_client(admin_handle h)124 struct irc_client *admin_get_client(admin_handle h)
125 {
126 	return h->client;
127 }
128 
admin_get_global(admin_handle h)129 struct global *admin_get_global(admin_handle h)
130 {
131 	return h->global;
132 }
133 
134 /**
135  * Return the network handle associated with an admin context.
136  * @param h Admin context handle
137  * @return Network, or NULL if no network is associated.
138  */
admin_get_network(admin_handle h)139 struct irc_network *admin_get_network(admin_handle h)
140 {
141 	return h->network;
142 }
143 
144 /**
145  * Send data to the user from the admin user.
146  * @param h Admin context handle
147  * @param fmt Format, printf-style
148  * @param ... Format arguments
149  */
admin_out(admin_handle h,const char * fmt,...)150 void admin_out(admin_handle h, const char *fmt, ...)
151 {
152 	va_list ap;
153 	char *msg;
154 	va_start(ap, fmt);
155 	msg = g_strdup_vprintf(fmt, ap);
156 	va_end(ap);
157 
158 	h->send_fn(h, msg);
159 
160 	g_free(msg);
161 }
162 
163 /**
164  * Handles the 'ADDNETWORK' command.
165  *
166  * @param h Admin context handle.
167  * @param args String arguments, argv-style.
168  * @param userdata Optional user data, always NULL.
169  */
cmd_add_network(admin_handle h,const char * const * args,void * userdata)170 static void cmd_add_network (admin_handle h, const char * const *args, void *userdata)
171 {
172 	if (args[1] == NULL) {
173 		admin_out(h, "No name specified");
174 		return;
175 	}
176 
177 	add_network_helper(h, args[1]);
178 }
179 
cmd_del_network(admin_handle h,const char * const * args,void * userdata)180 static void cmd_del_network (admin_handle h, const char * const *args, void *userdata)
181 {
182 	if (args[1] == NULL) {
183 		admin_out(h, "Not enough parameters");
184 		return;
185 	}
186 
187 	del_network_helper(h, args[1]);
188 }
189 
cmd_add_server(admin_handle h,const char * const * args,void * userdata)190 static void cmd_add_server (admin_handle h, const char * const *args, void *userdata)
191 {
192 	struct irc_network *n;
193 	struct tcp_server_config *s;
194 	struct network_config *nc;
195 	char *t;
196 
197 	if (!args[1] || !args[2]) {
198 		admin_out(h, "Not enough parameters");
199 		return;
200 	}
201 
202 	n = find_network(admin_get_global(h)->networks, args[1]);
203 
204 	if (!n) {
205 		admin_out(h, "No such network '%s'", args[1]);
206 		return;
207 	}
208 
209 	nc = n->private_data;
210 
211 	if (nc->type != NETWORK_TCP) {
212 		admin_out(h, "Not a TCP/IP network!");
213 		return;
214 	}
215 
216 	s = g_new0(struct tcp_server_config, 1);
217 
218 	s->host = g_strdup(args[2]);
219 	if ((t = strchr(s->host, ':'))) {
220 		*t = '\0';
221 		s->port = g_strdup(t+1);
222 	} else {
223 		s->port = g_strdup(DEFAULT_IRC_PORT);
224 	}
225 	s->ssl = FALSE;
226 	s->password = args[3]?g_strdup(args[3]):NULL;
227 
228 	nc->type_settings.tcp.servers = g_list_append(nc->type_settings.tcp.servers, s);
229 
230 	admin_out(h, "Server added to `%s'", args[1]);
231 }
232 
cmd_connect_network(admin_handle h,const char * const * args,void * userdata)233 static void cmd_connect_network (admin_handle h, const char * const *args, void *userdata)
234 {
235 	struct irc_network *s;
236 	if (!args[1]) {
237 		 admin_out(h, "No network specified");
238 		 return;
239 	}
240 
241 	s = find_network(admin_get_global(h)->networks, args[1]);
242 
243 	if (!s) {
244 		admin_out(h, "No such network `%s'", args[1]);
245 		return;
246 	}
247 
248 	switch (s->connection.state) {
249 		case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
250 			admin_out(h, "Connecting to `%s'", args[1]);
251 			connect_network(s);
252 			break;
253 		case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
254 			admin_out(h, "Forcing reconnect to `%s'", args[1]);
255 			disconnect_network(s);
256 			network_select_next_server(s);
257 			connect_network(s);
258 			break;
259 		case NETWORK_CONNECTION_STATE_CONNECTED:
260 		case NETWORK_CONNECTION_STATE_CONNECTING:
261 		case NETWORK_CONNECTION_STATE_LOGIN_SENT:
262 			admin_out(h, "Connect to `%s' already in progress", args[1]);
263 			break;
264 		case NETWORK_CONNECTION_STATE_MOTD_RECVD:
265 			admin_out(h, "Already connected to `%s'", args[1]);
266 			break;
267 	}
268 }
269 
cmd_disconnect_network(admin_handle h,const char * const * args,void * userdata)270 static void cmd_disconnect_network (admin_handle h, const char * const *args, void *userdata)
271 {
272 	struct irc_network *n;
273 	struct network_config *nc;
274 
275 	if (args[1] != NULL) {
276 		n = find_network(admin_get_global(h)->networks, args[1]);
277 		if (!n) {
278 			admin_out(h, "Can't find active network with that name");
279 			return;
280 		}
281 	} else {
282 		n = admin_get_network(h);
283 		if (n == NULL) {
284 			admin_out(h, "Usage: DISCONNECT <network>");
285 			return;
286 		}
287 	}
288 
289 	nc = n->private_data;
290 
291 	if (n->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
292 		admin_out(h, "Already disconnected from `%s'", n->name);
293 	} else if (nc->type == NETWORK_VIRTUAL &&
294 		n->connection.data.virtual.ops->not_disconnectable) {
295 		admin_out(h, "Built-in network `%s' can't be disconnected",
296 				  n->name);
297 	} else {
298 		admin_out(h, "Disconnecting from `%s'", n->name);
299 		disconnect_network(n);
300 	}
301 }
302 
cmd_next_server(admin_handle h,const char * const * args,void * userdata)303 static void cmd_next_server (admin_handle h, const char * const *args, void *userdata)
304 {
305 	struct irc_network *n;
306 	const char *name;
307 
308 
309 	if (args[1] != NULL) {
310 		name = args[1];
311 		n = find_network(admin_get_global(h)->networks, args[1]);
312 	} else {
313 		n = admin_get_network(h);
314 		if (n == NULL) {
315 			admin_out(h, "No current network");
316 			return;
317 		}
318 		name = n->name;
319 	}
320 	if (!n) {
321 		admin_out(h, "%s: Not connected", name);
322 	} else {
323 		admin_out(h, "%s: Reconnecting", name);
324 		disconnect_network(n);
325 		network_select_next_server(n);
326 		connect_network(n);
327 	}
328 }
329 
cmd_save_config(admin_handle h,const char * const * args,void * userdata)330 static void cmd_save_config (admin_handle h, const char * const *args, void *userdata)
331 {
332 	const char *adm_dir;
333 	struct global *global = admin_get_global(h);
334 	global_update_config(global);
335 	if (global->restricted)
336 		adm_dir = global->config->config_dir;
337 	else
338 		adm_dir = args[1]?args[1]:global->config->config_dir;
339 	save_configuration(global->config, adm_dir);
340 	admin_out(h, "Configuration saved in %s", adm_dir);
341 }
342 
add_network_helper(admin_handle h,const char * name)343 static void add_network_helper(admin_handle h, const char *name)
344 {
345 	struct network_config *nc;
346 	struct irc_network *n;
347 
348 	if (find_network(admin_get_global(h)->networks, name) != NULL) {
349 		admin_out(h, "Network with name `%s' already exists", name);
350 		return;
351 	}
352 
353 	nc = network_config_init(admin_get_global(h)->config);
354 	g_free(nc->name); nc->name = g_strdup(name);
355 	n = load_network(admin_get_global(h), nc);
356 
357 	admin_out(h, "Network `%s' added. Use ADDSERVER to add a server to this network.", name);
358 }
359 
rename_network_helper(admin_handle h,const char * oldname,const char * newname)360 static void rename_network_helper(admin_handle h, const char *oldname,
361 								  const char *newname)
362 {
363 	struct irc_network *n;
364 
365 	n = find_network(admin_get_global(h)->networks, oldname);
366 
367 	if (n == NULL) {
368 		admin_out(h, "Network with name `%s' does not exist", oldname);
369 		return;
370 	}
371 
372 	if (find_network(admin_get_global(h)->networks, newname) != NULL) {
373 		admin_out(h, "Network with name `%s' already exists", newname);
374 		return;
375 	}
376 
377 	g_free(n->name);
378 	n->name = g_strdup(newname);
379 }
380 
del_network_helper(admin_handle h,const char * name)381 static void del_network_helper(admin_handle h, const char *name)
382 {
383 	struct irc_network *n = find_network(admin_get_global(h)->networks, name);
384 	if (n == NULL) {
385 		admin_out(h, "No such network `%s'", name);
386 		return;
387 	}
388 
389 	disconnect_network(n);
390 
391 	unload_network(n);
392 
393 	config_del_network(admin_get_global(h)->config, name);
394 
395 	admin_out(h, "Network `%s' deleted", name);
396 }
397 
info_network_helper(admin_handle h,const char * name)398 static void info_network_helper(admin_handle h, const char *name)
399 {
400 	struct irc_network *n = find_network(admin_get_global(h)->networks, name);
401 	struct network_config *nc;
402 	if (n == NULL) {
403 		admin_out(h, "No such network `%s'", name);
404 		return;
405 	}
406 	nc = n->private_data;
407 	if (nc->type == NETWORK_TCP) {
408 		GList *gl;
409 		admin_out(h, "Servers:");
410 		for (gl = nc->type_settings.tcp.servers; gl; gl = gl->next) {
411 			struct tcp_server_config *server = gl->data;
412 			admin_out(h, "  %s:%s%s", server->host, server->port,
413 					  (server == n->connection.data.tcp.current_server?" (Current)":""));
414 		}
415 	} else if (nc->type == NETWORK_VIRTUAL) {
416 		admin_out(h, "Virtual network");
417 	} else if (nc->type == NETWORK_PROGRAM) {
418 		admin_out(h, "Interface to %s", nc->type_settings.program_location);
419 	}
420 
421 	if (n->clients == NULL) {
422 		admin_out(h, "No clients connected");
423 	} else {
424 		GList *gl;
425 		admin_out(h, "Clients:");
426 		for (gl = n->clients; gl; gl = gl->next) {
427 			struct irc_client *cl = gl->data;
428 			admin_out(h, "  %s", cl->description);
429 		}
430 	}
431 }
432 
433 
list_networks_helper(admin_handle h)434 static void list_networks_helper(admin_handle h)
435 {
436 	GList *gl;
437 	for (gl = admin_get_global(h)->networks; gl; gl = gl->next) {
438 		struct irc_network *n = gl->data;
439 
440 		switch (n->connection.state) {
441 		case NETWORK_CONNECTION_STATE_CONNECTING:
442 			admin_out(h, "%s: Connect in progress", n->name);
443 			break;
444 		case NETWORK_CONNECTION_STATE_CONNECTED:
445 			admin_out(h, "%s: Connected, logging in", n->name);
446 			break;
447 		case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
448 			if (n->connection.data.tcp.last_disconnect_reason)
449 				admin_out(h, "%s: Not connected: %s", n->name,
450 						  n->connection.data.tcp.last_disconnect_reason);
451 			else
452 				admin_out(h, "%s: Not connected", n->name);
453 			break;
454 		case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
455 			admin_out(h, "%s: Reconnecting", n->name);
456 			break;
457 		case NETWORK_CONNECTION_STATE_LOGIN_SENT:
458 		case NETWORK_CONNECTION_STATE_MOTD_RECVD:
459 			admin_out(h, "%s: connected", n->name);
460 			break;
461 		}
462 	}
463 }
464 
465 /* NETWORK LIST */
466 /* NETWORK ADD OFTC */
467 /* NETWORK DEL OFTC */
468 /* NETWORK INFO OFTC */
cmd_network(admin_handle h,const char * const * args,void * userdata)469 static void cmd_network(admin_handle h, const char * const *args, void *userdata)
470 {
471 	if (args[1] == NULL)
472 		goto usage;
473 
474 	if (!strcasecmp(args[1], "list")) {
475 		list_networks_helper(h);
476 	} else if (!strcasecmp(args[1], "add")) {
477 		if (args[2] == NULL) {
478 			admin_out(h, "Usage: network add <name>");
479 			return;
480 		}
481 		add_network_helper(h, args[2]);
482 	} else if (!strcasecmp(args[1], "rename")) {
483 		if (args[2] == NULL || args[3] == NULL) {
484 			admin_out(h, "Usage: network rename <oldname> <newname>");
485 			return;
486 		}
487 		rename_network_helper(h, args[2], args[3]);
488 	} else if (!strcasecmp(args[1], "del") ||
489 			   !strcasecmp(args[1], "delete") ||
490 			   !strcasecmp(args[1], "remove")) {
491 		if (args[2] == NULL) {
492 			admin_out(h, "Usage: network del <name>");
493 			return;
494 		}
495 		del_network_helper(h, args[2]);
496 		return;
497 	} else if (!strcasecmp(args[1], "info")) {
498 		if (args[2] == NULL) {
499 			admin_out(h, "Usage: network info <name>");
500 			return;
501 		}
502 		info_network_helper(h, args[2]);
503 		return;
504 	} else {
505 		goto usage;
506 	}
507 
508 	return;
509 usage:
510 	admin_out(h, "Usage: network [list|add <name>|del <name>]");
511 }
512 
cmd_list_networks(admin_handle h,const char * const * args,void * userdata)513 static void cmd_list_networks(admin_handle h, const char * const *args, void *userdata)
514 {
515 	list_networks_helper(h);
516 }
517 
cmd_detach(admin_handle h,const char * const * args,void * userdata)518 static void cmd_detach(admin_handle h, const char * const *args, void *userdata)
519 {
520 	struct irc_client *c = admin_get_client(h);
521 
522 	if (c == NULL) {
523 		admin_out(h, "No client to detach.");
524 		return;
525 	}
526 
527 	client_disconnect(c, "Client exiting");
528 }
529 
dump_joined_channels(admin_handle h,const char * const * args,void * userdata)530 static void dump_joined_channels(admin_handle h, const char * const *args, void *userdata)
531 {
532 	struct irc_network *n;
533 	GList *gl;
534 
535 	if (args[1] != NULL) {
536 		n = find_network(admin_get_global(h)->networks, args[1]);
537 		if (n == NULL) {
538 			admin_out(h, "Can't find network '%s'", args[1]);
539 			return;
540 		}
541 	} else {
542 		n = admin_get_network(h);
543 		if (n == NULL) {
544 			admin_out(h, "No network specified and no current network.");
545 			return;
546 		}
547 	}
548 
549 	if (!n->external_state) {
550 		admin_out(h, "Network '%s' not connected", n->name);
551 		return;
552 	}
553 
554 	for (gl = n->external_state->channels; gl; gl = gl->next) {
555 		struct irc_channel_state *ch = (struct irc_channel_state *)gl->data;
556 		admin_out(h, "%s", ch->name);
557 	}
558 }
559 
560 #ifdef DEBUG
cmd_abort(admin_handle h,const char * const * args,void * userdata)561 static void cmd_abort(admin_handle h, const char * const *args, void *userdata)
562 {
563 	abort();
564 }
565 #endif
566 
cmd_die(admin_handle h,const char * const * args,void * userdata)567 static void cmd_die(admin_handle h, const char * const *args, void *userdata)
568 {
569 	exit(0);
570 }
571 
572 static GHashTable *markers = NULL;
573 
cmd_backlog(admin_handle h,const char * const * args,void * userdata)574 static void cmd_backlog(admin_handle h, const char * const *args, void *userdata)
575 {
576 	struct linestack_marker *lm;
577 	struct irc_network *n;
578 
579 	if (admin_get_client(h) == NULL) {
580 		admin_out(h, "No client to send backlog to");
581 		return;
582 	}
583 
584 	n = admin_get_network(h);
585 
586 	if (n == NULL) {
587 		admin_out(h, "No network connected.");
588 		return;
589 	}
590 
591 	lm = g_hash_table_lookup(markers, n);
592 
593 	if (n->linestack == NULL) {
594 		admin_out(h, "No backlog available. Perhaps the connection to the network is down?");
595 		return;
596 	}
597 
598 	if (!args[1] || strlen(args[1]) == 0) {
599 		admin_out(h, "Sending backlog for network '%s'", n->name);
600 
601 		linestack_send(n->linestack, lm, NULL, admin_get_client(h),
602 					   TRUE, n->global->config->report_time != REPORT_TIME_NEVER,
603 					   n->global->config->report_time_offset);
604 
605 		g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
606 
607 		return;
608 	}
609 
610 	/* Backlog for specific nick/channel */
611 	admin_out(h, "Sending backlog for channel %s", args[1]);
612 
613 	linestack_send_object(n->linestack, args[1], lm, NULL,
614 						  admin_get_client(h), TRUE,
615 						  n->global->config->report_time != REPORT_TIME_NEVER,
616 						  n->global->config->report_time_offset);
617 
618 	g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
619 }
620 
linestack_get(admin_handle h)621 static char *linestack_get(admin_handle h)
622 {
623 	struct global *g = admin_get_global(h);
624 
625 	return g_strdup(g->config->linestack_backend);
626 }
627 
linestack_set(admin_handle h,const char * value)628 static gboolean linestack_set(admin_handle h, const char *value)
629 {
630 	struct global *g = admin_get_global(h);
631 
632 	g_free(g->config->linestack_backend);
633 	/* FIXME: Check if this is a valid linestack backend name */
634 	g->config->linestack_backend = g_strdup(g->config->linestack_backend);
635 
636 	return TRUE;
637 }
638 
logging_get(admin_handle h)639 static char *logging_get(admin_handle h)
640 {
641 	struct global *g = admin_get_global(h);
642 
643 	if (g->config->log_file == NULL)
644 		return g_strdup("none");
645 
646 	if (g->config->log_file->is_irssi)
647 		return g_strdup("irssi");
648 
649 	return g_strdup("custom");
650 }
651 
logging_set(admin_handle h,const char * value)652 static gboolean logging_set(admin_handle h, const char *value)
653 {
654 	/* FIXME: Implement */
655 
656 	return FALSE;
657 }
658 
log_level_get(admin_handle h)659 static char *log_level_get(admin_handle h)
660 {
661 	extern enum log_level current_log_level;
662 
663 	return g_strdup_printf("%d", current_log_level);
664 }
665 
log_level_set(admin_handle h,const char * value)666 static gboolean log_level_set(admin_handle h, const char *value)
667 {
668 	extern enum log_level current_log_level;
669 	int x;
670 
671 	if (value == NULL)
672 		return FALSE;
673 
674 	x = atoi(value);
675 	if (x < 0 || x > 5) {
676 		admin_out(h, "Invalid log level %d", x);
677 		return FALSE;
678 	}
679 
680 	current_log_level = x;
681 	admin_out(h, "Log level changed to %d", x);
682 	return TRUE;
683 }
684 
cmd_charset(admin_handle h,const char * const * args,void * userdata)685 static void cmd_charset(admin_handle h, const char * const *args, void *userdata)
686 {
687 	struct irc_client *c;
688 
689 	if (args[1] == NULL) {
690 		admin_out(h, "No charset specified");
691 		return;
692 	}
693 
694 	c = admin_get_client(h);
695 	if (c == NULL) {
696 		admin_out(h, "The CHARSET command can only be used on client connections.");
697 		return;
698 	}
699 
700 	if (!client_set_charset(c, args[1])) {
701 		admin_out(h, "Error setting charset: %s", args[1]);
702 	}
703 }
704 
cmd_echo(admin_handle h,const char * const * args,void * userdata)705 static void cmd_echo(admin_handle h, const char * const *args, void *userdata)
706 {
707 	admin_out(h, "%s", args[1]);
708 }
709 
cmp_cmd(gconstpointer a,gconstpointer b)710 static gint cmp_cmd(gconstpointer a, gconstpointer b)
711 {
712 	const struct admin_command *cmda = a, *cmdb = b;
713 
714 	return g_strcasecmp(cmda->name, cmdb->name);
715 }
716 
register_admin_command(const struct admin_command * cmd)717 void register_admin_command(const struct admin_command *cmd)
718 {
719 	admin_commands = g_list_insert_sorted(admin_commands, g_memdup(cmd, sizeof(*cmd)), cmp_cmd);
720 	if (strlen(cmd->name) > longest_command) longest_command = strlen(cmd->name);
721 }
722 
unregister_admin_command(const struct admin_command * cmd)723 void unregister_admin_command(const struct admin_command *cmd)
724 {
725 	admin_commands = g_list_remove(admin_commands, cmd);
726 }
727 
process_cmd(admin_handle h,const char * cmd)728 gboolean process_cmd(admin_handle h, const char *cmd)
729 {
730 	char **args = NULL;
731 	GList *gl;
732 
733 	if (!cmd) {
734 		admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
735 		return TRUE;
736 	}
737 
738 	args = g_strsplit(cmd, " ", 0);
739 
740 	if (!args[0]) {
741 		admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
742 		return TRUE;
743 	}
744 
745 	/* Ok, arguments are processed now. Execute the corresponding command */
746 	for (gl = admin_commands; gl; gl = gl->next) {
747 		struct admin_command *cmd = (struct admin_command *)gl->data;
748 		if (!g_strcasecmp(cmd->name, args[0])) {
749 			cmd->handler(h, (const char * const *)args, cmd->userdata);
750 			g_strfreev(args);
751 			return TRUE;
752 		}
753 	}
754 
755 	admin_out(h, "Can't find command '%s'. Type 'help' for a list of available commands. ", args[0]);
756 
757 	g_strfreev(args);
758 
759 	return TRUE;
760 }
761 
admin_process_command(struct irc_client * c,struct irc_line * l,int cmdoffset)762 gboolean admin_process_command(struct irc_client *c, struct irc_line *l, int cmdoffset)
763 {
764 	int i;
765 	char *tmp;
766 	gboolean ret;
767 	struct admin_handle ah;
768 
769 	if (l->args[cmdoffset] == NULL) {
770 		client_send_response(c, ERR_NEEDMOREPARAMS, l->args[0], "Not enough parameters", NULL);
771 		return TRUE;
772 	}
773 
774 	tmp = g_strdup(l->args[cmdoffset]);
775 
776 	/* Add everything after l->args[cmdoffset] to tmp */
777 	for(i = cmdoffset+1; l->args[i]; i++) {
778 		char *oldtmp = tmp;
779 		tmp = g_strdup_printf("%s %s", oldtmp, l->args[i]);
780 		g_free(oldtmp);
781 	}
782 
783 	ah.send_fn = privmsg_admin_out;
784 	ah.client = c;
785 	ah.network = c->network;
786 	ah.global = c->network->global;
787 	ret = process_cmd(&ah, tmp);
788 
789 	g_free(tmp);
790 
791 	return ret;
792 }
793 
admin_net_init(struct irc_network * n)794 static gboolean admin_net_init(struct irc_network *n)
795 {
796 	char *hostmask;
797 	char *nicks;
798 
799 	hostmask = admin_hostmask(n);
800 
801 	virtual_network_recv_args(n, n->external_state->me.hostmask, "JOIN", ADMIN_CHANNEL, NULL);
802 	virtual_network_recv_response(n, RPL_TOPIC, ADMIN_CHANNEL,
803 		"CtrlProxy administration channel | Type `help' for more information",
804 							  NULL);
805 	nicks = g_strdup_printf("@ctrlproxy %s", n->external_state->me.nick);
806 
807 	virtual_network_recv_response(n, RPL_NAMREPLY, "=", ADMIN_CHANNEL, nicks, NULL);
808 	g_free(nicks);
809 	virtual_network_recv_response(n, RPL_ENDOFNAMES, ADMIN_CHANNEL, "End of /NAMES list.", NULL);
810 
811 	g_free(hostmask);
812 
813 	return TRUE;
814 }
815 
admin_to_server(struct irc_network * n,struct irc_client * c,const struct irc_line * l)816 static gboolean admin_to_server (struct irc_network *n, struct irc_client *c, const struct irc_line *l)
817 {
818 	if (!g_strcasecmp(l->args[0], "PRIVMSG") ||
819 		!g_strcasecmp(l->args[0], "NOTICE")) {
820 		struct admin_handle ah;
821 
822 		if (g_strcasecmp(l->args[0], n->external_state->me.nick) == 0) {
823 			virtual_network_recv_args(n, n->external_state->me.hostmask, l->args[0], l->args[1], NULL);
824 			return TRUE;
825 		}
826 
827 		if (g_strcasecmp(l->args[1], ADMIN_CHANNEL) &&
828 			g_strcasecmp(l->args[1], "ctrlproxy")) {
829 			virtual_network_recv_response(n, ERR_NOSUCHNICK, l->args[1], "No such nick/channel", NULL);
830 			return TRUE;
831 		}
832 
833 		ah.send_fn = network_admin_out;
834 		ah.user_data = NULL;
835 		ah.client = c;
836 		ah.network = n;
837 		ah.global = n->global;
838 
839 		return process_cmd(&ah, l->args[2]);
840 	} else if (!g_strcasecmp(l->args[0], "ISON")) {
841 		int i;
842 		char *tmp;
843 		GList *gl = NULL;
844 
845 		if (l->args[1] == NULL) {
846 			virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
847 			return TRUE;
848 		}
849 
850 		for (i = 1; l->args[i]; i++) {
851 			if (!g_strcasecmp(l->args[i], "ctrlproxy") ||
852 				!g_strcasecmp(l->args[i], n->external_state->me.nick)) {
853 				gl = g_list_append(gl, l->args[i]);
854 			}
855 		}
856 		virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
857 		g_free(tmp);
858 		g_list_free(gl);
859 		return TRUE;
860 	} else if (!g_strcasecmp(l->args[0], "USERHOST")) {
861 		GList *gl = NULL;
862 		char *tmp;
863 		int i;
864 
865 		if (l->args[1] == NULL) {
866 			virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
867 			return TRUE;
868 		}
869 
870 		for (i = 1; l->args[i]; i++) {
871 			if (!g_strcasecmp(l->args[i], "ctrlproxy")) {
872 				gl = g_list_append(gl, g_strdup_printf("%s=+%s@%s", l->args[i], ADMIN_USERNAME, get_my_hostname()));
873 			}
874 			if (!g_strcasecmp(l->args[i], n->external_state->me.nick)) {
875 				gl = g_list_append(gl, g_strdup_printf("%s=+%s@%s", l->args[i], n->external_state->me.username, n->external_state->me.hostname));
876 			}
877 		}
878 
879 		virtual_network_recv_response(n, RPL_USERHOST, tmp = list_make_string(gl), NULL);
880 		g_free(tmp);
881 		while (gl) {
882 			g_free(gl->data);
883 			gl = g_list_remove(gl, gl->data);
884 		}
885 		return TRUE;
886 	} else if (!g_strcasecmp(l->args[0], "QUIT")) {
887 		return TRUE;
888 	} else if (!g_strcasecmp(l->args[0], "MODE")) {
889 		/* FIXME: Do something here ? */
890 		return TRUE;
891 	} else if (!g_strcasecmp(l->args[0], "WHO")) {
892 		if (!strcmp(l->args[1], ADMIN_CHANNEL) ||
893 			!strcmp(l->args[1], "ctrlproxy")) {
894 			virtual_network_recv_response(n, RPL_WHOREPLY, ADMIN_CHANNEL,
895 									  "ctrlproxy",
896 									  get_my_hostname(),
897 									  get_my_hostname(),
898 									  "ctrlproxy",
899 									  "H",
900 									  "0 CtrlProxy user",
901 									  NULL);
902 		}
903 		if (!strcmp(l->args[1], ADMIN_CHANNEL) ||
904 			!strcmp(l->args[1], n->external_state->me.nick)) {
905 			char *fullname = g_strdup_printf("0 %s", n->external_state->me.fullname);
906 			virtual_network_recv_response(n, RPL_WHOREPLY, ADMIN_CHANNEL,
907 									  n->external_state->me.username,
908 									  n->external_state->me.hostname,
909 									  get_my_hostname(),
910 									  n->external_state->me.nick,
911 									  "H",
912 									  fullname,
913 									  NULL);
914 			g_free(fullname);
915 		}
916 
917 		virtual_network_recv_response(n, RPL_ENDOFWHO, l->args[1], "End of /WHO list.", NULL);
918 
919 		return TRUE;
920 	} else if (!g_strcasecmp(l->args[0], "JOIN")) {
921 		if (strcmp(l->args[1], ADMIN_CHANNEL) != 0) {
922 			virtual_network_recv_response(n, ERR_NOSUCHCHANNEL, l->args[1], "No such channel", NULL);
923 		}
924 		return TRUE;
925 	} else if (!g_strcasecmp(l->args[0], "PART")) {
926 		if (strcmp(l->args[1], ADMIN_CHANNEL) != 0) {
927 			virtual_network_recv_response(n, ERR_NOTONCHANNEL, l->args[1], "You're not on that channel", NULL);
928 		} else {
929 			virtual_network_recv_args(n, n->external_state->me.hostmask, "PART", l->args[1], NULL);
930 			admin_net_init(n);
931 		}
932 		return TRUE;
933 	} else if (!g_strcasecmp(l->args[0], "WHOIS")) {
934 		/* FIXME: Send something sensible */
935 		virtual_network_recv_response(n, RPL_ENDOFWHOIS, l->args[1],
936 									  "End of /WHOIS list.", NULL);
937 		return TRUE;
938 	} else if (!g_strcasecmp(l->args[0], "AWAY")) {
939 		if (l->args[1] != NULL && strcmp(l->args[1], "") != 0) {
940 			virtual_network_recv_response(n, RPL_NOWAWAY, "You are now marked as being away", NULL);
941 		} else {
942 			virtual_network_recv_response(n, RPL_UNAWAY, "You are no longer marked as being away", NULL);
943 		}
944 		return TRUE;
945 	} else {
946 		virtual_network_recv_response(n, ERR_UNKNOWNCOMMAND, l->args[0], "Unknown command", NULL);
947 		log_global(LOG_TRACE, "Unhandled command `%s' to admin network",
948 				   l->args[0]);
949 		return TRUE;
950 	}
951 }
952 
953 struct virtual_network_ops admin_network = {
954 	.name = "admin",
955 	.init = admin_net_init,
956 	.to_server = admin_to_server,
957 	.not_disconnectable = TRUE,
958 };
959 
960 
admin_log(enum log_level level,const struct irc_network * n,const struct irc_client * c,const char * data)961 void admin_log(enum log_level level, const struct irc_network *n, const struct irc_client *c, const char *data)
962 {
963 	extern struct global *my_global;
964 	struct irc_line *l;
965 	char *tmp, *hostmask;
966 	GList *gl;
967 	static gboolean entered = FALSE;
968 
969 	if (!my_global || !my_global->config ||
970 		!my_global->config->admin_log)
971 		return;
972 
973 	if (level < LOG_INFO)
974 		return;
975 
976 	if (entered)
977 		return; /* Prevent inifinite recursion.. */
978 
979 	entered = TRUE;
980 
981 	tmp = g_strdup_printf("%s%s%s%s%s%s",
982 						  data,
983 						  n?" (":"",
984 						  n?n->name:"",
985 						  c?"/":"",
986 						  c?c->description:"",
987 						  n?")":"");
988 
989 	for (gl = my_global->networks; gl; gl = gl->next) {
990 		struct irc_network *network = gl->data;
991 
992 		if (network->connection.data.virtual.ops != &admin_network)
993 			continue;
994 
995 		hostmask = admin_hostmask(network);
996 		l = irc_parse_line_args(hostmask, "PRIVMSG", ADMIN_CHANNEL, tmp, NULL);
997 		g_free(hostmask);
998 
999 		virtual_network_recv_line(network, l);
1000 
1001 		free_line(l);
1002 	}
1003 
1004 	g_free(tmp);
1005 
1006 	entered = FALSE;
1007 }
1008 
cmd_start_listener(admin_handle h,const char * const * args,void * userdata)1009 static void cmd_start_listener(admin_handle h, const char * const *args, void *userdata)
1010 {
1011 	char *b, *p;
1012 	struct listener_config *cfg;
1013 	struct irc_listener *l;
1014 
1015 	if (!args[1]) {
1016 		admin_out(h, "No port specified");
1017 		return;
1018 	}
1019 
1020 	if (!args[2]) {
1021 		admin_out(h, "No password specified");
1022 		return;
1023 	}
1024 
1025 	cfg = g_new0(struct listener_config, 1);
1026 
1027 	b = g_strdup(args[1]);
1028 	if ((p = strchr(b, ':'))) {
1029 		*p = '\0';
1030 		p++;
1031 		cfg->address = b;
1032 		cfg->port = g_strdup(p);
1033 	} else {
1034 		cfg->port = g_strdup(b);
1035 		cfg->address = NULL;
1036 	}
1037 
1038 	if (args[3]) {
1039 		cfg->network = g_strdup(args[3]);
1040 		if (find_network(admin_get_global(h)->networks, args[3]) == NULL) {
1041 			admin_out(h, "No such network `%s`", args[3]);
1042 			return;
1043 		}
1044 	}
1045 
1046 	cfg->password = g_strdup(args[2]);
1047 
1048 	l = listener_init(admin_get_global(h), cfg);
1049 	l->global = admin_get_global(h);
1050 
1051 	listener_start_tcp(l, l->config->address, l->config->port);
1052 }
1053 
cmd_stop_listener(admin_handle h,const char * const * args,void * userdata)1054 static void cmd_stop_listener(admin_handle h, const char * const *args, void *userdata)
1055 {
1056 	GList *gl;
1057 	char *b, *p;
1058 	int i = 0;
1059 
1060 	if (args[1] == NULL) {
1061 		admin_out(h, "No port specified");
1062 		return;
1063 	}
1064 
1065 	b = g_strdup(args[1]);
1066 	if ((p = strchr(b, ':'))) {
1067 		*p = '\0';
1068 		p++;
1069 	} else {
1070 		p = b;
1071 		b = NULL;
1072 	}
1073 
1074 	for (gl = admin_get_global(h)->listeners; gl; gl = gl->next) {
1075 		struct irc_listener *l = gl->data;
1076 
1077 		if (b && l->config->address == NULL)
1078 			continue;
1079 
1080 		if (b && l->config->address != NULL && strcmp(b, l->config->address) != 0)
1081 			continue;
1082 
1083 		if (strcmp(p, l->config->port) != 0)
1084 			continue;
1085 
1086 		listener_stop(l);
1087 		free_listener(l);
1088 		i++;
1089 	}
1090 
1091 	if (b) g_free(b); else g_free(p);
1092 
1093 	admin_out(h, "%d listeners stopped", i);
1094 }
1095 
cmd_list_listener(admin_handle h,const char * const * args,void * userdata)1096 static void cmd_list_listener(admin_handle h, const char * const *args, void *userdata)
1097 {
1098 	GList *gl;
1099 
1100 	for (gl = admin_get_global(h)->listeners; gl; gl = gl->next) {
1101 		struct irc_listener *l = gl->data;
1102 
1103 		admin_out(h, "%s:%s%s%s%s", l->config->address?l->config->address:"", l->config->port,
1104 				  l->network?" (":"", l->network?l->network->name:"",
1105 				  l->network?")":"");
1106 	}
1107 }
1108 
motd_file_set(admin_handle h,const char * name)1109 static gboolean motd_file_set(admin_handle h, const char *name)
1110 {
1111 	struct global *g = admin_get_global(h);
1112 
1113 	if (!g_file_test(name, G_FILE_TEST_EXISTS)) {
1114 		admin_out(h, "%s: file does not exist", name);
1115 		return FALSE;
1116 	}
1117 
1118 	g_free(g->config->motd_file);
1119 	g->config->motd_file = g_strdup(name);
1120 
1121 	return TRUE;
1122 }
1123 
motd_file_get(admin_handle h)1124 static char *motd_file_get(admin_handle h)
1125 {
1126 	struct global *g = admin_get_global(h);
1127 
1128 	return g_strdup(g->config->motd_file);
1129 }
1130 
interpret_boolean(admin_handle h,const char * value,gboolean * ret)1131 static gboolean interpret_boolean(admin_handle h, const char *value,
1132 								  gboolean *ret)
1133 {
1134 	if (value == NULL) {
1135 		admin_out(h, "Boolean settings can't be unset.");
1136 		return FALSE;
1137 	}
1138 
1139 	if (!g_strcasecmp(value, "true")) {
1140 		*ret = TRUE;
1141 		return TRUE;
1142 	} else if (!g_strcasecmp(value, "false")) {
1143 		*ret = FALSE;
1144 		return TRUE;
1145 	}
1146 
1147 	admin_out(h, "Invalid boolean value `%s'", value);
1148 	return FALSE;
1149 }
1150 
1151 #define BOOL_SETTING(name) \
1152 static char *name ## _get(admin_handle h) \
1153 { \
1154 	struct global *g = admin_get_global(h); \
1155 	return g_strdup(g->config->name?"true":"false"); \
1156 } \
1157 static gboolean name ## _set(admin_handle h, const char *value) \
1158 { \
1159 	struct global *g = admin_get_global(h); \
1160 	return interpret_boolean(h, value, &g->config->name); \
1161 }
1162 
1163 BOOL_SETTING(autosave)
BOOL_SETTING(admin_log)1164 BOOL_SETTING(admin_log)
1165 BOOL_SETTING(learn_network_name)
1166 
1167 static char *report_time_get(admin_handle h)
1168 {
1169 	struct global *g = admin_get_global(h);
1170 
1171 	switch (g->config->report_time) {
1172 	case REPORT_TIME_ALWAYS:
1173 		return g_strdup("always");
1174 	case REPORT_TIME_NEVER:
1175 		return g_strdup("never");
1176 	case REPORT_TIME_REPLICATION:
1177 		return g_strdup("replication");
1178 	}
1179 
1180 	return g_strdup("UNKNOWN");
1181 }
1182 
report_time_set(admin_handle h,const char * value)1183 static gboolean report_time_set(admin_handle h, const char *value)
1184 {
1185 	struct global *g = admin_get_global(h);
1186 
1187 	if (!g_strcasecmp(value, "never") || !g_strcasecmp(value, "false"))
1188 		g->config->report_time = REPORT_TIME_NEVER;
1189 	else if (!g_strcasecmp(value, "always"))
1190 		g->config->report_time = REPORT_TIME_ALWAYS;
1191 	else if  (!g_strcasecmp(value, "replication") ||
1192 			  !g_strcasecmp(value, "true"))
1193 		g->config->report_time = REPORT_TIME_REPLICATION;
1194 	else {
1195 		return FALSE;
1196 	}
1197 
1198 	return TRUE;
1199 }
1200 
replication_get(admin_handle h)1201 static char *replication_get(admin_handle h)
1202 {
1203 	struct global *g = admin_get_global(h);
1204 
1205 	return g_strdup(g->config->replication);
1206 }
1207 
replication_set(admin_handle h,const char * value)1208 static gboolean replication_set(admin_handle h, const char *value)
1209 {
1210 	struct global *g = admin_get_global(h);
1211 
1212 	if (repl_find_backend(value) == NULL) {
1213 		admin_out(h, "No such replication backend `%s'", value);
1214 		return FALSE;
1215 	}
1216 
1217 	g_free(g->config->replication);
1218 	g->config->replication = g_strdup(value);
1219 
1220 	return TRUE;
1221 }
1222 
admin_user_get(admin_handle h)1223 static char *admin_user_get(admin_handle h)
1224 {
1225 	struct global *g = admin_get_global(h);
1226 
1227 	return g_strdup(g->config->admin_user);
1228 }
1229 
admin_user_set(admin_handle h,const char * value)1230 static gboolean admin_user_set(admin_handle h, const char *value)
1231 {
1232 	struct global *g = admin_get_global(h);
1233 
1234 	g_free(g->config->admin_user);
1235 	if (strlen(value) == 0) {
1236 		/* FIXME: Check whether value is a somewhat valid IRC nick? */
1237 		g->config->admin_user = g_strdup(value);
1238 	} else
1239 		g->config->admin_user = NULL;
1240 
1241 	return TRUE;
1242 }
1243 
BOOL_SETTING(learn_nickserv)1244 BOOL_SETTING(learn_nickserv)
1245 
1246 static char *max_who_age_get(admin_handle h)
1247 {
1248 	struct global *g = admin_get_global(h);
1249 
1250 	if (g->config->cache.max_who_age == 0)
1251 		return NULL;
1252 
1253 	return g_strdup_printf("%d", g->config->cache.max_who_age);
1254 }
1255 
max_who_age_set(admin_handle h,const char * value)1256 static gboolean max_who_age_set(admin_handle h, const char *value)
1257 {
1258 	struct global *g = admin_get_global(h);
1259 
1260 	g->config->cache.max_who_age = (value == NULL?0:atoi(value));
1261 
1262 	return TRUE;
1263 }
1264 
auto_away_time_get(admin_handle h)1265 static char *auto_away_time_get(admin_handle h)
1266 {
1267 	struct global *g = admin_get_global(h);
1268 
1269 	if (g->config->auto_away.max_idle_time == 0)
1270 		return NULL;
1271 
1272 	return g_strdup_printf("%d", g->config->auto_away.max_idle_time);
1273 }
1274 
auto_away_time_set(admin_handle h,const char * value)1275 static gboolean auto_away_time_set(admin_handle h, const char *value)
1276 {
1277 	struct global *g = admin_get_global(h);
1278 
1279 	g->config->auto_away.max_idle_time = (value == NULL?-1:atoi(value));
1280 
1281 	/* FIXME: Restart auto-away */
1282 
1283 	return TRUE;
1284 }
1285 
auto_away_enable_get(admin_handle h)1286 static char *auto_away_enable_get(admin_handle h)
1287 {
1288 	struct global *g = admin_get_global(h);
1289 
1290 	return g_strdup(g->config->auto_away.enabled?"true":"false");
1291 }
1292 
auto_away_enable_set(admin_handle h,const char * value)1293 static gboolean auto_away_enable_set(admin_handle h, const char *value)
1294 {
1295 	struct global *g = admin_get_global(h);
1296 
1297 	return interpret_boolean(h, value, &g->config->auto_away.enabled);
1298 }
1299 
auto_away_message_get(admin_handle h)1300 static char *auto_away_message_get(admin_handle h)
1301 {
1302 	struct global *g = admin_get_global(h);
1303 
1304 	return g_strdup(g->config->auto_away.message);
1305 }
1306 
auto_away_message_set(admin_handle h,const char * value)1307 static gboolean auto_away_message_set(admin_handle h, const char *value)
1308 {
1309 	struct global *g = admin_get_global(h);
1310 
1311 	g_free(g->config->auto_away.message);
1312 
1313 	g->config->auto_away.message = g_strdup(value);
1314 
1315 	return TRUE;
1316 }
1317 
auto_away_nick_get(admin_handle h)1318 static char *auto_away_nick_get(admin_handle h)
1319 {
1320 	struct global *g = admin_get_global(h);
1321 
1322 	return g_strdup(g->config->auto_away.nick);
1323 }
1324 
auto_away_nick_set(admin_handle h,const char * value)1325 static gboolean auto_away_nick_set(admin_handle h, const char *value)
1326 {
1327 	struct global *g = admin_get_global(h);
1328 
1329 	g_free(g->config->auto_away.nick);
1330 
1331 	g->config->auto_away.nick = g_strdup(value);
1332 
1333 	return TRUE;
1334 }
1335 
auto_away_client_limit_get(admin_handle h)1336 static char *auto_away_client_limit_get(admin_handle h)
1337 {
1338 	struct global *g = admin_get_global(h);
1339 
1340 	return g_strdup_printf("%d", g->config->auto_away.client_limit);
1341 }
1342 
auto_away_client_limit_set(admin_handle h,const char * value)1343 static gboolean auto_away_client_limit_set(admin_handle h, const char *value)
1344 {
1345 	struct global *g = admin_get_global(h);
1346 
1347 	g->config->auto_away.client_limit = (value == NULL?-1:atoi(value));
1348 
1349 	return TRUE;
1350 }
1351 
report_time_offset_get(admin_handle h)1352 static char *report_time_offset_get(admin_handle h)
1353 {
1354 	struct global *g = admin_get_global(h);
1355 
1356 	if (g->config->report_time_offset == 0)
1357 		return NULL;
1358 
1359 	return g_strdup_printf("%d", g->config->report_time_offset);
1360 }
1361 
report_time_offset_set(admin_handle h,const char * value)1362 static gboolean report_time_offset_set(admin_handle h, const char *value)
1363 {
1364 	struct global *g = admin_get_global(h);
1365 
1366 	g->config->report_time_offset = (value == NULL?0:atoi(value));
1367 
1368 	return TRUE;
1369 }
1370 
port_get(admin_handle h)1371 static char *port_get(admin_handle h)
1372 {
1373 	GList *gl;
1374 	struct global *g = admin_get_global(h);
1375 
1376 	for (gl = g->config->listeners; gl; gl = gl->next) {
1377 		struct listener_config *l = gl->data;
1378 
1379 		if (l->is_default)
1380 			return g_strdup_printf("%s", l->port);
1381 	}
1382 
1383 	return NULL;
1384 }
1385 
port_set(admin_handle h,const char * value)1386 static gboolean port_set(admin_handle h, const char *value)
1387 {
1388 	GList *gl;
1389 	struct global *g = admin_get_global(h);
1390 
1391 	/* FIXME: What if there is no default listener ? */
1392 	/* FIXME: Check that value is a valid service name or port number */
1393 
1394 	for (gl = g->config->listeners; gl; gl = gl->next) {
1395 		struct listener_config *l = gl->data;
1396 
1397 		if (l->is_default) {
1398 			l->port = g_strdup(value);
1399 			return TRUE;
1400 		}
1401 	}
1402 
1403 	return FALSE;
1404 }
1405 
bind_get(admin_handle h)1406 static char *bind_get(admin_handle h)
1407 {
1408 	GList *gl;
1409 	struct global *g = admin_get_global(h);
1410 
1411 	for (gl = g->config->listeners; gl; gl = gl->next) {
1412 		struct listener_config *l = gl->data;
1413 
1414 		if (l->is_default)
1415 			return g_strdup(l->address);
1416 	}
1417 
1418 	return NULL;
1419 }
1420 
bind_set(admin_handle h,const char * value)1421 static gboolean bind_set(admin_handle h, const char *value)
1422 {
1423 	GList *gl;
1424 	struct global *g = admin_get_global(h);
1425 
1426 	/* FIXME: What if there is no default listener ? */
1427 	/* FIXME: Check that value is a valid service name or port number */
1428 
1429 	for (gl = g->config->listeners; gl; gl = gl->next) {
1430 		struct listener_config *l = gl->data;
1431 
1432 		if (l->is_default) {
1433 			l->address = g_strdup(value);
1434 			return TRUE;
1435 		}
1436 	}
1437 
1438 	return FALSE;
1439 }
1440 
password_get(admin_handle h)1441 static char *password_get(admin_handle h)
1442 {
1443 	GList *gl;
1444 	struct global *g = admin_get_global(h);
1445 
1446 	for (gl = g->config->listeners; gl; gl = gl->next) {
1447 		struct listener_config *l = gl->data;
1448 
1449 		if (l->is_default)
1450 			return g_strdup_printf("%s", l->password);
1451 	}
1452 
1453 	return NULL;
1454 }
1455 
password_set(admin_handle h,const char * value)1456 static gboolean password_set(admin_handle h, const char *value)
1457 {
1458 	GList *gl;
1459 	struct global *g = admin_get_global(h);
1460 
1461 	/* FIXME: What if there is no default listener ? */
1462 	/* FIXME: Check that value is a valid service name or port number */
1463 
1464 	for (gl = g->config->listeners; gl; gl = gl->next) {
1465 		struct listener_config *l = gl->data;
1466 
1467 		if (l->is_default) {
1468 			l->password = g_strdup(value);
1469 			return TRUE;
1470 		}
1471 	}
1472 
1473 	return FALSE;
1474 }
1475 
default_network_get(admin_handle h)1476 static char *default_network_get(admin_handle h)
1477 {
1478 	GList *gl;
1479 	struct global *g = admin_get_global(h);
1480 
1481 	for (gl = g->config->listeners; gl; gl = gl->next) {
1482 		struct listener_config *l = gl->data;
1483 
1484 		if (l->is_default)
1485 			return g_strdup(l->network);
1486 	}
1487 
1488 	return NULL;
1489 }
1490 
default_network_set(admin_handle h,const char * value)1491 static gboolean default_network_set(admin_handle h, const char *value)
1492 {
1493 	GList *gl;
1494 	struct global *g = admin_get_global(h);
1495 
1496 	/* FIXME: What if there is no default listener ? */
1497 	/* FIXME: Check that value is a valid service name or port number */
1498 
1499 	for (gl = g->config->listeners; gl; gl = gl->next) {
1500 		struct listener_config *l = gl->data;
1501 
1502 		if (l->is_default) {
1503 			l->network = g_strdup(value);
1504 			return TRUE;
1505 		}
1506 	}
1507 
1508 	return FALSE;
1509 }
1510 
default_client_charset_get(admin_handle h)1511 static char *default_client_charset_get(admin_handle h)
1512 {
1513 	struct global *g = admin_get_global(h);
1514 
1515 	return g_strdup(g->config->client_charset);
1516 }
1517 
default_client_charset_set(admin_handle h,const char * value)1518 static gboolean default_client_charset_set(admin_handle h, const char *value)
1519 {
1520 	struct global *g = admin_get_global(h);
1521 
1522 	g->config->client_charset = g_strdup(value);
1523 
1524 	return TRUE;
1525 }
1526 
default_nick_get(admin_handle h)1527 static char *default_nick_get(admin_handle h)
1528 {
1529 	struct global *g = admin_get_global(h);
1530 	return g_strdup(g->config->default_nick);
1531 }
1532 
default_nick_set(admin_handle h,const char * value)1533 static gboolean default_nick_set(admin_handle h, const char *value)
1534 {
1535 	struct global *g = admin_get_global(h);
1536 	if (strlen(value) == 0) {
1537 		admin_out(h, "Nick name can't be empty.");
1538 		return FALSE;
1539 	}
1540 	if (strchr(value, ' ') != NULL) {
1541 		admin_out(h, "Nick name can't contain spaces.");
1542 		return FALSE;
1543 	}
1544 	if (value[0] == ':') {
1545 		admin_out(h, "Nick name can't start with semicolon.");
1546 		return FALSE;
1547 	}
1548 
1549 	g_free(g->config->default_nick);
1550 	g->config->default_nick = g_strdup(value);
1551 	return TRUE;
1552 }
1553 
1554 
default_username_get(admin_handle h)1555 static char *default_username_get(admin_handle h)
1556 {
1557 	struct global *g = admin_get_global(h);
1558 	return g_strdup(g->config->default_username);
1559 }
1560 
default_username_set(admin_handle h,const char * value)1561 static gboolean default_username_set(admin_handle h, const char *value)
1562 {
1563 	struct global *g = admin_get_global(h);
1564 	if (strlen(value) == 0) {
1565 		admin_out(h, "Username can't be empty.");
1566 		return FALSE;
1567 	}
1568 	if (strchr(value, ' ') != NULL) {
1569 		admin_out(h, "Username can't contain spaces.");
1570 		return FALSE;
1571 	}
1572 	if (value[0] == ':') {
1573 		admin_out(h, "Username can't start with semicolon.");
1574 		return FALSE;
1575 	}
1576 
1577 	g_free(g->config->default_username);
1578 	g->config->default_username = g_strdup(value);
1579 	return TRUE;
1580 }
1581 
1582 
default_fullname_get(admin_handle h)1583 static char *default_fullname_get(admin_handle h)
1584 {
1585 	struct global *g = admin_get_global(h);
1586 	return g_strdup(g->config->default_realname);
1587 }
1588 
default_fullname_set(admin_handle h,const char * value)1589 static gboolean default_fullname_set(admin_handle h, const char *value)
1590 {
1591 	struct global *g = admin_get_global(h);
1592 
1593 	if (strlen(value) == 0) {
1594 		admin_out(h, "Fullname can't be empty");
1595 		return FALSE;
1596 	}
1597 
1598 	g_free(g->config->default_realname);
1599 	g->config->default_realname = g_strdup(value);
1600 	return TRUE;
1601 }
1602 
1603 /**
1604  * Table of administration settings that can be
1605  * viewed and changed using the SET command.
1606  */
1607 static struct admin_setting {
1608 	const char *name;
1609 	char *(*get) (admin_handle h);
1610 	gboolean (*set) (admin_handle h, const char *value);
1611 } settings[] = {
1612 	{ "admin-log", admin_log_get, admin_log_set },
1613 	{ "admin-user", admin_user_get, admin_user_set },
1614 	{ "auto-away-enable", auto_away_enable_get, auto_away_enable_set },
1615 	{ "auto-away-client-limit", auto_away_client_limit_get, auto_away_client_limit_set },
1616 	{ "auto-away-message", auto_away_message_get, auto_away_message_set },
1617 	{ "auto-away-nick", auto_away_nick_get, auto_away_nick_set },
1618 	{ "auto-away-time", auto_away_time_get, auto_away_time_set },
1619 	{ "autosave", autosave_get, autosave_set },
1620 	{ "bind", bind_get, bind_set },
1621 	{ "default-client-charset", default_client_charset_get, default_client_charset_set },
1622 	{ "default-network", default_network_get, default_network_set },
1623 	{ "learn-network-name", learn_network_name_get, learn_network_name_set },
1624 	{ "learn-nickserv", learn_nickserv_get, learn_nickserv_set },
1625 	{ "linestack", linestack_get, linestack_set },
1626 	{ "log_level", log_level_get, log_level_set },
1627 	{ "logging", logging_get, logging_set },
1628 	{ "max_who_age", max_who_age_get, max_who_age_set },
1629 	{ "motd-file", motd_file_get, motd_file_set },
1630 	{ "password", password_get, password_set },
1631 	{ "port", port_get, port_set },
1632 	{ "report-time", report_time_get, report_time_set },
1633 	{ "report-time-offset", report_time_offset_get, report_time_offset_set },
1634 	{ "replication", replication_get, replication_set },
1635 	{ "default-nick", default_nick_get, default_nick_set },
1636 	{ "default-username", default_username_get, default_username_set },
1637 	{ "default-fullname", default_fullname_get, default_fullname_set },
1638 	{ NULL, NULL, NULL }
1639 };
1640 
cmd_unset(admin_handle h,const char * const * args,void * userdata)1641 static void cmd_unset(admin_handle h, const char * const *args, void *userdata)
1642 {
1643 	int i;
1644 	if (args[1] == NULL) {
1645 		admin_out(h, "Usage: unset <setting>");
1646 		return;
1647 	}
1648 
1649 	for (i = 0; settings[i].name; i++) {
1650 		if (!strcasecmp(settings[i].name, args[1])) {
1651 			settings[i].set(h, NULL);
1652 		}
1653 	}
1654 }
1655 
cmd_set(admin_handle h,const char * const * args,void * userdata)1656 static void cmd_set(admin_handle h, const char * const *args, void *userdata)
1657 {
1658 	int i;
1659 	char *tmp;
1660 
1661 	if (args[1] == NULL) {
1662 		for (i = 0; settings[i].name != NULL; i++) {
1663 			tmp = settings[i].get(h);
1664 			if (tmp == NULL)
1665 				admin_out(h, "%s is not set", settings[i]);
1666 			else
1667 				admin_out(h, "%s = %s", settings[i].name, tmp);
1668 			g_free(tmp);
1669 		}
1670 	} else {
1671 		for (i = 0; settings[i].name; i++) {
1672 			if (!strcasecmp(settings[i].name, args[1])) {
1673 				if (args[2] != NULL) {
1674 					settings[i].set(h, args[2]);
1675 				} else {
1676 					tmp = settings[i].get(h);
1677 					admin_out(h, "%s", tmp);
1678 					g_free(tmp);
1679 				}
1680 				return;
1681 			}
1682 		}
1683 
1684 		admin_out(h, "Unknown setting `%s'", args[1]);
1685 	}
1686 }
1687 
1688 const static struct admin_command builtin_commands[] = {
1689 	/* Provided for backwards compatibility */
1690 	{ "ADDNETWORK", cmd_add_network },
1691 	{ "DELNETWORK", cmd_del_network },
1692 	{ "LISTNETWORKS", cmd_list_networks },
1693 
1694 	/* Commands */
1695 	{ "SET", cmd_set },
1696 	{ "UNSET", cmd_unset },
1697 
1698 	{ "ADDSERVER", cmd_add_server },
1699 	{ "BACKLOG", cmd_backlog},
1700 	{ "CONNECT", cmd_connect_network },
1701 	{ "DISCONNECT", cmd_disconnect_network },
1702 	{ "ECHO", cmd_echo },
1703 	{ "NEXTSERVER", cmd_next_server },
1704 	{ "CHARSET", cmd_charset },
1705 	{ "DIE", cmd_die },
1706 	{ "NETWORK", cmd_network },
1707 	{ "SAVECONFIG", cmd_save_config },
1708 	{ "DETACH", cmd_detach },
1709 	{ "HELP", cmd_help },
1710 	{ "DUMPJOINEDCHANNELS", dump_joined_channels },
1711 	{ "STARTLISTENER", cmd_start_listener },
1712 	{ "STOPLISTENER", cmd_stop_listener },
1713 	{ "LISTLISTENER", cmd_list_listener },
1714 #ifdef DEBUG
1715 	{ "ABORT", cmd_abort },
1716 #endif
1717 	{ NULL }
1718 };
1719 
init_admin(void)1720 void init_admin(void)
1721 {
1722 	int i;
1723 	for(i = 0; builtin_commands[i].name; i++) {
1724 		register_admin_command(&builtin_commands[i]);
1725 	}
1726 
1727 	register_virtual_network(&admin_network);
1728 
1729 	markers = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)linestack_free_marker);
1730 }
1731 
iochannel_admin_out(admin_handle h,const char * data)1732 static void iochannel_admin_out(admin_handle h, const char *data)
1733 {
1734 	gsize bytes_written;
1735 	GError *error = NULL;
1736 	GIOStatus status;
1737 
1738 	status = g_io_channel_write_chars(h->user_data, data, -1, &bytes_written, &error);
1739 
1740 	status = g_io_channel_write_chars(h->user_data, "\n", -1, &bytes_written, &error);
1741 
1742 	status = g_io_channel_flush(h->user_data, &error);
1743 }
1744 
handle_client_data(GIOChannel * channel,GIOCondition condition,void * _global)1745 static gboolean handle_client_data(GIOChannel *channel,
1746 								  GIOCondition condition, void *_global)
1747 {
1748 	char *raw;
1749 	GError *error = NULL;
1750 	GIOStatus status;
1751 	struct admin_handle ah;
1752 	gsize eol;
1753 
1754 	ah.global = _global;
1755 	ah.client = NULL;
1756 	ah.user_data = channel;
1757 	ah.send_fn = iochannel_admin_out;
1758 
1759 	if (condition & G_IO_IN) {
1760 		status = g_io_channel_read_line(channel, &raw, NULL, &eol, &error);
1761 		if (status == G_IO_STATUS_NORMAL) {
1762 			raw[eol] = '\0';
1763 			process_cmd(&ah, raw);
1764 			g_free(raw);
1765 		}
1766 	}
1767 
1768 	if (condition & G_IO_HUP) {
1769 		return FALSE;
1770 	}
1771 
1772 	if (condition & G_IO_ERR) {
1773 		log_global(LOG_WARNING, "Error from admin client");
1774 		return FALSE;
1775 	}
1776 
1777 	return TRUE;
1778 }
1779 
handle_new_client(GIOChannel * c_server,GIOCondition condition,void * _global)1780 static gboolean handle_new_client(GIOChannel *c_server,
1781 								  GIOCondition condition, void *_global)
1782 {
1783 	GIOChannel *c;
1784 	int sock = accept(g_io_channel_unix_get_fd(c_server), NULL, 0);
1785 
1786 	if (sock < 0) {
1787 		log_global(LOG_WARNING, "Error accepting new connection: %s", strerror(errno));
1788 		return TRUE;
1789 	}
1790 
1791 	c = g_io_channel_unix_new(sock);
1792 
1793 	g_io_channel_set_close_on_unref(c, TRUE);
1794 	g_io_channel_set_encoding(c, NULL, NULL);
1795 	g_io_channel_set_flags(c, G_IO_FLAG_NONBLOCK, NULL);
1796 
1797 	g_io_add_watch(c, G_IO_IN | G_IO_ERR | G_IO_HUP, handle_client_data, _global);
1798 
1799 	g_io_channel_unref(c);
1800 
1801 	return TRUE;
1802 }
1803 
start_admin_socket(struct global * global)1804 gboolean start_admin_socket(struct global *global)
1805 {
1806 	int sock;
1807 	struct sockaddr_un un;
1808 
1809 	sock = socket(PF_UNIX, SOCK_STREAM, 0);
1810 	if (sock < 0) {
1811 		log_global(LOG_ERROR, "error creating unix socket: %s", strerror(errno));
1812 		return FALSE;
1813 	}
1814 
1815 	un.sun_family = AF_UNIX;
1816 	strncpy(un.sun_path, global->config->admin_socket, sizeof(un.sun_path));
1817 	unlink(un.sun_path);
1818 
1819 	if (bind(sock, (struct sockaddr *)&un, sizeof(un)) < 0) {
1820 		log_global(LOG_ERROR, "unable to bind to %s: %s", un.sun_path, strerror(errno));
1821 		return FALSE;
1822 	}
1823 
1824 	if (listen(sock, 5) < 0) {
1825 		log_global(LOG_ERROR, "error listening on socket: %s", strerror(errno));
1826 		return FALSE;
1827 	}
1828 
1829 	global->admin_incoming = g_io_channel_unix_new(sock);
1830 
1831 	if (!global->admin_incoming) {
1832 		log_global(LOG_ERROR, "Unable to create GIOChannel for unix server socket");
1833 		return FALSE;
1834 	}
1835 
1836 	g_io_channel_set_close_on_unref(global->admin_incoming, TRUE);
1837 
1838 	g_io_channel_set_encoding(global->admin_incoming, NULL, NULL);
1839 	global->admin_incoming_id = g_io_add_watch(global->admin_incoming, G_IO_IN, handle_new_client, global);
1840 	g_io_channel_unref(global->admin_incoming);
1841 
1842 	return TRUE;
1843 }
1844 
stop_admin_socket(struct global * global)1845 gboolean stop_admin_socket(struct global *global)
1846 {
1847 	if (global->admin_incoming_id > 0)
1848 		g_source_remove(global->admin_incoming_id);
1849 	unlink(global->config->admin_socket);
1850 	return TRUE;
1851 }
1852