Merge more advanced help support.
[jelmer/ctrlproxy.git] / src / admin.c
1 /*
2         ctrlproxy: A modular IRC proxy
3         admin: module for remote administration. 
4
5         (c) 2003-2006 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 2 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 "admin.h"
25 #include "help.h"
26
27 help_t *help;
28
29 #define ADMIN_CHANNEL "#ctrlproxy"
30
31 GList *admin_commands = NULL;
32 guint longest_command = 0;
33
34 static void privmsg_admin_out(admin_handle h, const char *data)
35 {
36         struct client *c = h->client;
37         char *nick = c->nick;
38         char *hostmask;
39
40         hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->name);
41         if (c->network->state) nick = c->network->state->me.nick;
42         client_send_args_ex(c, hostmask, "NOTICE", nick, data, NULL);
43
44         g_free(hostmask);
45 }
46
47 static void network_admin_out(admin_handle h, const char *data)
48 {
49         struct client *c = h->client;
50         char *hostmask;
51
52         hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->name);
53         virtual_network_recv_args(c->network, hostmask, "PRIVMSG", ADMIN_CHANNEL, 
54                                                           data, NULL);
55
56         g_free(hostmask);
57 }
58
59 static void cmd_help(admin_handle h, char **args, void *userdata)
60 {
61         const char *s;
62         char **lines;
63         int i;
64
65         s = help_get(help, args[1] != NULL?args[1]:"index");
66
67         if (s == NULL) {
68                 if (args[1] == NULL)
69                         admin_out(h, "Sorry, help not available");
70                 else
71                         admin_out(h, "Sorry, no help for %s available", args[1]);
72                 return;
73         }
74
75         lines = g_strsplit(s, "\n", 0);
76
77         for (i = 0; lines[i]; i++) {
78                 admin_out(h, "%s", lines[i]);
79         }
80
81         g_strfreev(lines);
82 }
83
84 struct client *admin_get_client(admin_handle h)
85 {
86         return h->client;
87 }
88
89 struct global *admin_get_global(admin_handle h)
90 {
91         return h->global;
92 }
93
94 struct network *admin_get_network(admin_handle h)
95 {
96         return h->network;
97 }
98
99 void admin_out(admin_handle h, const char *fmt, ...)
100 {
101         va_list ap;
102         char *msg;
103         va_start(ap, fmt);
104         msg = g_strdup_vprintf(fmt, ap);
105         va_end(ap);
106
107         h->send_fn(h, msg);
108
109         g_free(msg);
110 }
111
112 static void add_network (admin_handle h, char **args, void *userdata)
113 {
114         struct network_config *nc;
115
116         if (args[1] == NULL) {
117                 admin_out(h, "No name specified");
118                 return;
119         }
120
121         nc = network_config_init(admin_get_global(h)->config);
122         g_free(nc->name); nc->name = g_strdup(args[1]);
123         load_network(admin_get_global(h), nc);
124
125         admin_out(h, "Network `%s' added. Use ADDSERVER to add a server to this network.", args[1]);
126 }
127
128 static void del_network (admin_handle h, char **args, void *userdata)
129 {
130         struct network *n;
131
132         if (args[1] == NULL) {
133                 admin_out(h, "Not enough parameters");
134                 return;
135         }
136
137         n = find_network(admin_get_global(h), args[1]);
138         if (n == NULL) {
139                 admin_out(h, "No such network `%s'", args[1]);
140                 return;
141         }
142
143         disconnect_network(n);
144
145         admin_out(h, "Network `%s' deleted", args[1]);
146 }
147
148 static void add_server (admin_handle h, char **args, void *userdata)
149 {
150         struct network *n;
151         struct tcp_server_config *s;
152         char *t;
153
154         if(!args[1] || !args[2]) {
155                 admin_out(h, "Not enough parameters");
156                 return;
157         }
158
159         n = find_network(admin_get_global(h), args[1]);
160
161         if (!n) {
162                 admin_out(h, "No such network '%s'", args[1]);
163                 return;
164         }
165
166         if (n->config->type != NETWORK_TCP) {
167                 admin_out(h, "Not a TCP/IP network!");
168                 return;
169         }
170
171         s = g_new0(struct tcp_server_config, 1);
172
173         s->host = g_strdup(args[2]);
174         if ((t = strchr(s->host, ':'))) {
175                 *t = '\0';
176                 s->port = g_strdup(t+1);
177         } else {
178                 s->port = g_strdup("6667");
179         }
180         s->ssl = FALSE;
181         s->password = args[3]?g_strdup(args[3]):NULL;
182
183         n->config->type_settings.tcp_servers = g_list_append(n->config->type_settings.tcp_servers, s);
184
185         admin_out(h, "Server added to `%s'", args[1]);
186 }
187
188 static void com_connect_network (admin_handle h, char **args, void *userdata)
189 {
190         struct network *s;
191         if(!args[1]) {
192                  admin_out(h, "No network specified");
193                  return;
194         }
195
196         s = find_network(admin_get_global(h), args[1]);
197
198         if (!s) {
199                 admin_out(h, "No such network `%s'", args[1]);
200                 return;
201         }
202
203         switch (s->connection.state) {
204                 case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
205                         admin_out(h, "Connecting to `%s'", args[1]);
206                         connect_network(s);
207                         break;
208                 case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
209                         admin_out(h, "Forcing reconnect to `%s'", args[1]);
210                         disconnect_network(s);
211                         network_select_next_server(s);
212                         connect_network(s);
213                         break;
214                 case NETWORK_CONNECTION_STATE_LOGIN_SENT:
215                         admin_out(h, "Connect to `%s' already in progress", args[1]);
216                         break;
217                 case NETWORK_CONNECTION_STATE_MOTD_RECVD:
218                         admin_out(h, "Already connected to `%s'", args[1]);
219                         break;
220         }
221 }
222
223 static void com_disconnect_network (admin_handle h, char **args, void *userdata)
224 {
225         struct network *n;
226
227         n = admin_get_network(h);
228
229         if (args[1] != NULL) {
230                 n = find_network(admin_get_global(h), args[1]);
231                 if(!n) {
232                         admin_out(h, "Can't find active network with that name");
233                         return;
234                 }
235         }
236
237         if (n->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
238                 admin_out(h, "Already disconnected from `%s'", args[1]);
239         } else {
240                 admin_out(h, "Disconnecting from `%s'", args[1]);
241                 disconnect_network(n);
242         }
243 }
244
245 static void com_next_server (admin_handle h, char **args, void *userdata) 
246 {
247         struct network *n;
248         const char *name;
249
250
251         if(args[1] != NULL) {
252                 name = args[1];
253                 n = find_network(admin_get_global(h), args[1]);
254         } else {
255                 n = admin_get_network(h);
256                 name = n->name;
257         }
258         if(!n) {
259                 admin_out(h, "%s: Not connected", name);
260         } else {
261                 admin_out(h, "%s: Reconnecting", name);
262                 disconnect_network(n);
263                 network_select_next_server(n);
264                 connect_network(n);
265         }
266 }
267
268 static void com_save_config (admin_handle h, char **args, void *userdata)
269
270         const char *adm_dir;
271         global_update_config(admin_get_global(h));
272         adm_dir = args[1]?args[1]:admin_get_global(h)->config->config_dir; 
273         save_configuration(admin_get_global(h)->config, adm_dir);
274         admin_out(h, "Configuration saved in %s", adm_dir);
275 }
276
277
278
279 static void list_networks(admin_handle h, char **args, void *userdata)
280 {
281         GList *gl;
282         for (gl = admin_get_global(h)->networks; gl; gl = gl->next) {
283                 struct network *n = gl->data;
284
285                 switch (n->connection.state) {
286                 case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
287                         admin_out(h, ("%s: Not connected"), n->name);
288                         break;
289                 case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
290                         admin_out(h, ("%s: Reconnecting"), n->name);
291                         break;
292                 case NETWORK_CONNECTION_STATE_LOGIN_SENT:
293                 case NETWORK_CONNECTION_STATE_MOTD_RECVD:
294                         admin_out(h, ("%s: connected"), n->name);
295                         break;
296                 }
297         }
298 }
299
300 static void detach_client(admin_handle h, char **args, void *userdata)
301 {
302         struct client *c = admin_get_client(h);
303
304         disconnect_client(c, "Client exiting");
305 }
306
307 static void dump_joined_channels(admin_handle h, char **args, void *userdata)
308 {
309         struct network *n;
310         GList *gl;
311
312         if (args[1] != NULL) {
313                 n = find_network(admin_get_global(h), args[1]);
314                 if(n == NULL) {
315                         admin_out(h, "Can't find network '%s'", args[1]);
316                         return;
317                 }
318         } else {
319                 n = admin_get_network(h);
320         }
321
322         if (!n->state) {
323                 admin_out(h, "Network '%s' not connected", n->name);
324                 return;
325         }
326
327         for (gl = n->state->channels; gl; gl = gl->next) {
328                 struct channel_state *ch = (struct channel_state *)gl->data;
329                 admin_out(h, "%s", ch->name);
330         }
331 }
332
333 #ifdef DEBUG
334 static void do_abort(admin_handle h, char **args, void *userdata)
335 {
336         abort();
337 }
338 #endif
339
340 static void handle_die(admin_handle h, char **args, void *userdata)
341 {
342         exit(0);
343 }
344
345 static GHashTable *markers = NULL;
346
347 static void repl_command(admin_handle h, char **args, void *userdata)
348 {
349         struct linestack_marker *lm;
350         struct network *n;
351         
352         n = admin_get_network(h);
353
354         lm = g_hash_table_lookup(markers, n);
355
356         if (n->linestack == NULL) {
357                 admin_out(h, "No backlog available. Perhaps the connection to the network is down?");
358                 return;
359         }
360
361         if(!args[1]) {
362                 admin_out(h, "Sending backlog for network '%s'", n->name);
363
364                 linestack_send(n->linestack, lm, NULL, admin_get_client(h));
365
366                 g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
367
368                 return;
369         } 
370
371         /* Backlog for specific nick/channel */
372         admin_out(h, "Sending backlog for channel %s", args[1]);
373
374         if (n->global->config->report_time)
375                 linestack_send_object_timed(n->linestack, args[1], lm, NULL, 
376                                                                         admin_get_client(h));
377         else
378                 linestack_send_object(n->linestack, args[1], lm, NULL, 
379                                                           admin_get_client(h));
380
381         g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
382 }
383
384 static void handle_charset(admin_handle h, char **args, void *userdata)
385 {
386         GError *error = NULL;
387         struct client *c;
388
389         c = admin_get_client(h);
390
391         if (args[1] == NULL) {
392                 admin_out(h, "No charset specified");
393                 return;
394         }
395
396         if (!client_set_charset(c, args[1])) {
397                 admin_out(h, "Error setting charset: %s", error->message);
398         }
399 }
400
401 static void cmd_echo(admin_handle h, char **args, void *userdata)
402 {
403         admin_out(h, "%s", args[1]);
404 }
405
406 static gint cmp_cmd(gconstpointer a, gconstpointer b)
407 {
408         const struct admin_command *cmda = a, *cmdb = b;
409
410         return g_strcasecmp(cmda->name, cmdb->name);
411 }
412
413 void register_admin_command(const struct admin_command *cmd)
414 {
415         admin_commands = g_list_insert_sorted(admin_commands, g_memdup(cmd, sizeof(*cmd)), cmp_cmd);
416         if (strlen(cmd->name) > longest_command) longest_command = strlen(cmd->name);
417 }
418
419 void unregister_admin_command(const struct admin_command *cmd)
420 {
421         admin_commands = g_list_remove(admin_commands, cmd);
422 }
423
424 gboolean process_cmd(admin_handle h, const char *cmd)
425 {
426         char **args = NULL;
427         GList *gl;
428
429         if (!cmd) {
430                 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
431                 return TRUE;
432         }
433
434         args = g_strsplit(cmd, " ", 0);
435
436         if (!args[0]) {
437                 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
438                 return TRUE;
439         }
440
441         /* Ok, arguments are processed now. Execute the corresponding command */
442         for (gl = admin_commands; gl; gl = gl->next) {
443                 struct admin_command *cmd = (struct admin_command *)gl->data;
444                 if(!g_strcasecmp(cmd->name, args[0])) {
445                         cmd->handler(h, args, cmd->userdata);
446                         g_strfreev(args);
447                         return TRUE;
448                 }
449         }
450
451         admin_out(h, "Can't find command '%s'. Type 'help' for a list of available commands. ", args[0]);
452
453         g_strfreev(args);
454
455         return TRUE;
456 }
457
458 gboolean admin_process_command(struct client *c, struct line *l, int cmdoffset)
459 {
460         int i;
461         char *tmp = g_strdup(l->args[cmdoffset]);
462         gboolean ret;
463         struct admin_handle ah;
464
465         /* Add everything after l->args[cmdoffset] to tmp */
466         for(i = cmdoffset+1; l->args[i]; i++) {
467                 char *oldtmp = tmp;
468                 tmp = g_strdup_printf("%s %s", oldtmp, l->args[i]);
469                 g_free(oldtmp);
470         }
471         l->is_private = 1;
472
473         ah.send_fn = privmsg_admin_out;
474         ah.client = c;
475         ah.network = c->network;
476         ah.global = c->network->global;
477         ret = process_cmd(&ah, tmp);
478
479         g_free(tmp);
480
481         return ret;
482 }
483
484 static gboolean admin_net_init(struct network *n)
485 {
486         char *hostmask;
487         char *nicks;
488
489         hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", n->name);
490         
491         virtual_network_recv_args(n, n->state->me.hostmask, "JOIN", ADMIN_CHANNEL, NULL);
492         virtual_network_recv_args(n, NULL, "332", n->config->nick, ADMIN_CHANNEL, 
493                 "CtrlProxy administration channel | Type `help' for more information",
494                                                           NULL);
495         nicks = g_strdup_printf("@ctrlproxy %s", n->config->nick);
496
497         virtual_network_recv_args(n, NULL, "353", n->config->nick, "=", ADMIN_CHANNEL, nicks, NULL);
498         g_free(nicks);
499         virtual_network_recv_args(n, NULL, "366", n->config->nick, ADMIN_CHANNEL, "End of /NAMES list.", NULL);
500
501         g_free(hostmask);
502
503         return TRUE;
504 }
505
506 static gboolean admin_to_server (struct network *n, struct client *c, const struct line *l)
507 {
508         struct admin_handle ah;
509
510         if (g_strcasecmp(l->args[0], "PRIVMSG") && 
511                 g_strcasecmp(l->args[0], "NOTICE")) {
512                 log_global(LOG_TRACE, "Unhandled command `%s' to admin network", 
513                                    l->args[0]);
514                 return TRUE;
515         }
516
517         if (g_strcasecmp(l->args[0], n->state->me.nick) == 0) {
518                 virtual_network_recv_args(c->network, n->state->me.hostmask, l->args[0], l->args[1], NULL);
519                 return TRUE;
520         }
521
522         if (g_strcasecmp(l->args[1], ADMIN_CHANNEL) && 
523                 g_strcasecmp(l->args[1], "ctrlproxy")) {
524                 virtual_network_recv_args(c->network, NULL, "401", l->args[1], "No such nick/channel", NULL);
525                 return TRUE;
526         }
527
528         ah.send_fn = network_admin_out;
529         ah.user_data = NULL;
530         ah.client = c;
531         ah.network = n;
532         ah.global = n->global;
533
534         return process_cmd(&ah, l->args[2]);
535 }
536
537 struct virtual_network_ops admin_network = {
538         "admin", admin_net_init, admin_to_server, NULL
539 };
540
541 void admin_log(enum log_level level, const struct network *n, const struct client *c, const char *data)
542 {
543         extern struct global *my_global;
544         struct line *l;
545         char *tmp, *hostmask;
546         GList *gl;
547         static gboolean entered = FALSE;
548
549         if (!my_global || !my_global->config || 
550                 !my_global->config->admin_log) 
551                 return;
552
553         if (level < LOG_INFO)
554                 return;
555
556         if (entered)
557                 return; /* Prevent inifinite recursion.. */
558
559         entered = TRUE;
560
561         tmp = g_strdup_printf("%s%s%s%s%s%s", 
562                                                   data, 
563                                                   n?" (":"",
564                                                   n?n->name:"", 
565                                                   c?"/":"",
566                                                   c?c->description:"",
567                                                   n?")":"");
568
569         for (gl = my_global->networks; gl; gl = gl->next) {
570                 struct network *network = gl->data;
571
572                 if (network->connection.data.virtual.ops != &admin_network)
573                         continue;
574
575                 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", network->name);
576                 l = irc_parse_line_args(hostmask, "PRIVMSG", ADMIN_CHANNEL, tmp, NULL); 
577                 g_free(hostmask);
578                 
579                 virtual_network_recv_line(network, l);
580
581                 free_line(l);
582         }
583
584         g_free(tmp);
585
586         entered = FALSE;
587 }
588
589 const static struct admin_command builtin_commands[] = {
590         { "ADDNETWORK", add_network, "<name>", "Add new network with specified name" },
591         { "ADDSERVER", add_server, "<network> <host>[:<port>] [<password>]", "Add server to network" },
592         { "BACKLOG", repl_command, "[channel]", "Send backlogs for this network or a channel, if specified" },
593         { "CONNECT", com_connect_network, "<network>", "Connect to specified network. Forces reconnect when waiting." },
594         { "DELNETWORK", del_network, "<network>", "Remove specified network" },
595         { "ECHO", cmd_echo, "<DATA>", "Simple echo command" },
596         { "NEXTSERVER", com_next_server, "[network]", "Disconnect and use to the next server in the list" },
597         { "CHARSET", handle_charset, "<charset>", "Change client charset" },
598         { "DIE", handle_die, "", "Exit ctrlproxy" },
599         { "DISCONNECT", com_disconnect_network, "<network>", "Disconnect specified network" },
600         { "LISTNETWORKS", list_networks, "", "List current networks and their status" },
601         { "SAVECONFIG", com_save_config, "<name>", "Save current XML configuration to specified file" },
602         { "DETACH", detach_client, "", "Detach current client" },
603         { "HELP", cmd_help, "[command]", "This help command" },
604         { "DUMPJOINEDCHANNELS", dump_joined_channels, "[network]", NULL, NULL },
605 #ifdef DEBUG
606         { "ABORT", do_abort, "", NULL, NULL },
607 #endif
608         { NULL }
609 };
610
611 void init_admin(void) 
612 {
613         int i;
614         for(i = 0; builtin_commands[i].name; i++) {
615                 register_admin_command(&builtin_commands[i]);
616         }
617
618         register_virtual_network(&admin_network);
619
620         markers = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)linestack_free_marker);
621 }