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