Merges from Irssi CVS.
[crypto.git] / apps / irssi / src / core / commands.c
1 /*
2  commands.c : irssi
3
4     Copyright (C) 1999-2000 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "commands.h"
24 #include "misc.h"
25 #include "special-vars.h"
26 #include "window-item-def.h"
27
28 #include "servers.h"
29 #include "channels.h"
30
31 #include "lib-config/iconfig.h"
32 #include "settings.h"
33
34 GSList *commands;
35 char *current_command;
36
37 static int signal_default_command;
38
39 static GSList *alias_runstack;
40
41 COMMAND_REC *command_find(const char *cmd)
42 {
43         GSList *tmp;
44
45         g_return_val_if_fail(cmd != NULL, NULL);
46
47         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
48                 COMMAND_REC *rec = tmp->data;
49
50                 if (g_strcasecmp(rec->cmd, cmd) == 0)
51                         return rec;
52         }
53
54         return NULL;
55 }
56
57 static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
58                                                const char *module)
59 {
60         GSList *tmp;
61
62         g_return_val_if_fail(rec != NULL, NULL);
63         g_return_val_if_fail(module != NULL, NULL);
64
65         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
66                 COMMAND_MODULE_REC *rec = tmp->data;
67
68                 if (g_strcasecmp(rec->name, module) == 0)
69                         return rec;
70         }
71
72         return NULL;
73 }
74
75 static COMMAND_MODULE_REC *
76 command_module_find_and_remove(COMMAND_REC *rec, SIGNAL_FUNC func)
77 {
78         GSList *tmp, *tmp2;
79
80         g_return_val_if_fail(rec != NULL, NULL);
81         g_return_val_if_fail(func != NULL, NULL);
82
83         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
84                 COMMAND_MODULE_REC *rec = tmp->data;
85
86                 for (tmp2 = rec->callbacks; tmp2 != NULL; tmp2 = tmp2->next) {
87                         COMMAND_CALLBACK_REC *cb = tmp2->data;
88
89                         if (cb->func == func) {
90                                 rec->callbacks =
91                                         g_slist_remove(rec->callbacks, cb);
92                                 return rec;
93                         }
94                 }
95         }
96
97         return NULL;
98 }
99
100 int command_have_sub(const char *command)
101 {
102         GSList *tmp;
103         int len;
104
105         g_return_val_if_fail(command != NULL, FALSE);
106
107         /* find "command "s */
108         len = strlen(command);
109         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
110                 COMMAND_REC *rec = tmp->data;
111
112                 if (g_strncasecmp(rec->cmd, command, len) == 0 &&
113                     rec->cmd[len] == ' ')
114                         return TRUE;
115         }
116
117         return FALSE;
118 }
119
120 static COMMAND_MODULE_REC *
121 command_module_get(COMMAND_REC *rec, const char *module, int protocol)
122 {
123         COMMAND_MODULE_REC *modrec;
124
125         g_return_val_if_fail(rec != NULL, NULL);
126
127         modrec = command_module_find(rec, module);
128         if (modrec == NULL) {
129                 modrec = g_new0(COMMAND_MODULE_REC, 1);
130                 modrec->name = g_strdup(module);
131                 modrec->protocol = -1;
132                 rec->modules = g_slist_append(rec->modules, modrec);
133         }
134
135         if (protocol != -1)
136                 modrec->protocol = protocol;
137
138         return modrec;
139 }
140
141 void command_bind_full(const char *module, int priority, const char *cmd,
142                        int protocol, const char *category, SIGNAL_FUNC func,
143                        void *user_data)
144 {
145         COMMAND_REC *rec;
146         COMMAND_MODULE_REC *modrec;
147         COMMAND_CALLBACK_REC *cb;
148         char *str;
149
150         g_return_if_fail(module != NULL);
151         g_return_if_fail(cmd != NULL);
152
153         rec = command_find(cmd);
154         if (rec == NULL) {
155                 rec = g_new0(COMMAND_REC, 1);
156                 rec->cmd = g_strdup(cmd);
157                 rec->category = category == NULL ? NULL : g_strdup(category);
158                 commands = g_slist_append(commands, rec);
159         }
160         modrec = command_module_get(rec, module, protocol);
161
162         cb = g_new0(COMMAND_CALLBACK_REC, 1);
163         cb->func = func;
164         cb->user_data = user_data;
165         modrec->callbacks = g_slist_append(modrec->callbacks, cb);
166
167         if (func != NULL) {
168                 str = g_strconcat("command ", cmd, NULL);
169                 signal_add_full(module, priority, str, func, user_data);
170                 g_free(str);
171         }
172
173         signal_emit("commandlist new", 1, rec);
174 }
175
176 static void command_free(COMMAND_REC *rec)
177 {
178         commands = g_slist_remove(commands, rec);
179         signal_emit("commandlist remove", 1, rec);
180
181         g_free_not_null(rec->category);
182         g_strfreev(rec->options);
183         g_free(rec->cmd);
184         g_free(rec);
185 }
186
187 static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
188 {
189         rec->modules = g_slist_remove(rec->modules, modrec);
190
191         g_slist_foreach(modrec->callbacks, (GFunc) g_free, NULL);
192         g_slist_free(modrec->callbacks);
193         g_free(modrec->name);
194         g_free_not_null(modrec->options);
195         g_free(modrec);
196 }
197
198 static void command_module_destroy(COMMAND_REC *rec,
199                                    COMMAND_MODULE_REC *modrec)
200 {
201         GSList *tmp, *freelist;
202
203         command_module_free(modrec, rec);
204
205         /* command_set_options() might have added module declaration of it's
206            own without any signals .. check if they're the only ones left
207            and if so, destroy them. */
208         freelist = NULL;
209         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
210                 COMMAND_MODULE_REC *rec = tmp->data;
211
212                 if (rec->callbacks == NULL)
213                         freelist = g_slist_append(freelist, rec);
214                 else {
215                         g_slist_free(freelist);
216                         freelist = NULL;
217                         break;
218                 }
219         }
220
221         g_slist_foreach(freelist, (GFunc) command_module_free, rec);
222         g_slist_free(freelist);
223
224         if (rec->modules == NULL)
225                 command_free(rec);
226 }
227
228 void command_unbind_full(const char *cmd, SIGNAL_FUNC func, void *user_data)
229 {
230         COMMAND_REC *rec;
231         COMMAND_MODULE_REC *modrec;
232         char *str;
233
234         g_return_if_fail(cmd != NULL);
235         g_return_if_fail(func != NULL);
236
237         rec = command_find(cmd);
238         if (rec != NULL) {
239                 modrec = command_module_find_and_remove(rec, func);
240                 g_return_if_fail(modrec != NULL);
241
242                 if (modrec->callbacks == NULL)
243                         command_module_destroy(rec, modrec);
244         }
245
246         str = g_strconcat("command ", cmd, NULL);
247         signal_remove_data(str, func, user_data);
248         g_free(str);
249 }
250
251 /* Expand `cmd' - returns `cmd' if not found, NULL if more than one
252    match is found */
253 static const char *command_expand(char *cmd)
254 {
255         GSList *tmp;
256         const char *match;
257         int len, multiple;
258
259         g_return_val_if_fail(cmd != NULL, NULL);
260
261         multiple = FALSE;
262         match = NULL;
263         len = strlen(cmd);
264         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
265                 COMMAND_REC *rec = tmp->data;
266
267                 if (g_strncasecmp(rec->cmd, cmd, len) == 0 &&
268                     strchr(rec->cmd+len, ' ') == NULL) {
269                         if (rec->cmd[len] == '\0') {
270                                 /* full match */
271                                 return rec->cmd;
272                         }
273
274                         if (match != NULL) {
275                                 /* multiple matches, we still need to check
276                                    if there's some command left that is a
277                                    full match.. */
278                                 multiple = TRUE;
279                         }
280
281                         /* check that this is the only match */
282                         match = rec->cmd;
283                 }
284         }
285
286         if (multiple) {
287                 signal_emit("error command", 2,
288                             GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
289                 return NULL;
290         }
291
292         return match != NULL ? match : cmd;
293 }
294
295 void command_runsub(const char *cmd, const char *data,
296                     void *server, void *item)
297 {
298         const char *newcmd;
299         char *orig, *subcmd, *defcmd, *args;
300
301         g_return_if_fail(data != NULL);
302
303         while (*data == ' ') data++;
304
305         if (*data == '\0') {
306                 /* no subcommand given - list the subcommands */
307                 signal_emit("list subcommands", 2, cmd);
308                 return;
309         }
310
311         /* get command.. */
312         orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
313         args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
314         if (args != NULL) *args++ = '\0'; else args = "";
315         while (*args == ' ') args++;
316
317         /* check if this command can be expanded */
318         newcmd = command_expand(subcmd+8);
319         if (newcmd == NULL) {
320                 /* ambiguous command */
321                 g_free(orig);
322                 return;
323         }
324
325         subcmd = g_strconcat("command ", newcmd, NULL);
326
327         g_strdown(subcmd);
328         if (!signal_emit(subcmd, 3, args, server, item)) {
329                 defcmd = g_strdup_printf("default command %s", cmd);
330                 if (!signal_emit(defcmd, 3, data, server, item)) {
331                         signal_emit("error command", 2,
332                                     GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
333                 }
334                 g_free(defcmd);
335         }
336
337         g_free(subcmd);
338         g_free(orig);
339 }
340
341 static GSList *optlist_find(GSList *optlist, const char *option)
342 {
343         while (optlist != NULL) {
344                 char *name = optlist->data;
345                 if (iscmdtype(*name)) name++;
346
347                 if (g_strcasecmp(name, option) == 0)
348                         return optlist;
349
350                 optlist = optlist->next;
351         }
352
353         return NULL;
354 }
355
356 int command_have_option(const char *cmd, const char *option)
357 {
358         COMMAND_REC *rec;
359         char **tmp;
360
361         g_return_val_if_fail(cmd != NULL, FALSE);
362         g_return_val_if_fail(option != NULL, FALSE);
363
364         rec = command_find(cmd);
365         g_return_val_if_fail(rec != NULL, FALSE);
366
367         if (rec->options == NULL)
368                 return FALSE;
369
370         for (tmp = rec->options; *tmp != NULL; tmp++) {
371                 char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
372
373                 if (g_strcasecmp(name, option) == 0)
374                         return TRUE;
375         }
376
377         return FALSE;
378 }
379
380 static void command_calc_options(COMMAND_REC *rec, const char *options)
381 {
382         char **optlist, **tmp, *name, *str;
383         GSList *list, *oldopt;
384
385         optlist = g_strsplit(options, " ", -1);
386
387         if (rec->options == NULL) {
388                 /* first call - use specified args directly */
389                 rec->options = optlist;
390                 return;
391         }
392
393         /* save old options to linked list */
394         list = NULL;
395         for (tmp = rec->options; *tmp != NULL; tmp++)
396                 list = g_slist_append(list, g_strdup(*tmp));
397         g_strfreev(rec->options);
398
399         /* merge the options */
400         for (tmp = optlist; *tmp != NULL; tmp++) {
401                 name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
402
403                 oldopt = optlist_find(list, name);
404                 if (oldopt != NULL) {
405                         /* already specified - overwrite old defination */
406                         g_free(oldopt->data);
407                         oldopt->data = g_strdup(*tmp);
408                 } else {
409                         /* new option, append to list */
410                         list = g_slist_append(list, g_strdup(*tmp));
411                 }
412         }
413         g_strfreev(optlist);
414
415         /* linked list -> string[] */
416         str = gslist_to_string(list, " ");
417         rec->options = g_strsplit(str, " ", -1);
418         g_free(str);
419
420         g_slist_foreach(list, (GFunc) g_free, NULL);
421         g_slist_free(list);
422 }
423
424 /* recalculate options to command from options in all modules */
425 static void command_update_options(COMMAND_REC *rec)
426 {
427         GSList *tmp;
428
429         g_strfreev(rec->options);
430         rec->options = NULL;
431
432         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
433                 COMMAND_MODULE_REC *modrec = tmp->data;
434
435                 if (modrec->options != NULL)
436                         command_calc_options(rec, modrec->options);
437         }
438 }
439
440 void command_set_options_module(const char *module,
441                                 const char *cmd, const char *options)
442 {
443         COMMAND_REC *rec;
444         COMMAND_MODULE_REC *modrec;
445         int reload;
446
447         g_return_if_fail(module != NULL);
448         g_return_if_fail(cmd != NULL);
449         g_return_if_fail(options != NULL);
450
451         rec = command_find(cmd);
452         g_return_if_fail(rec != NULL);
453         modrec = command_module_get(rec, module, -1);
454
455         reload = modrec->options != NULL;
456         if (reload) {
457                 /* options already set for the module ..
458                    we need to recalculate everything */
459                 g_free(modrec->options);
460         }
461
462         modrec->options = g_strdup(options);
463
464         if (reload)
465                 command_update_options(rec);
466         else
467                 command_calc_options(rec, options);
468 }
469
470 char *cmd_get_param(char **data)
471 {
472         char *pos;
473
474         g_return_val_if_fail(data != NULL, NULL);
475         g_return_val_if_fail(*data != NULL, NULL);
476
477         while (**data == ' ') (*data)++;
478         pos = *data;
479
480         while (**data != '\0' && **data != ' ') (*data)++;
481         if (**data == ' ') *(*data)++ = '\0';
482
483         return pos;
484 }
485
486 static char *cmd_get_quoted_param(char **data)
487 {
488         char *pos, quote;
489
490         g_return_val_if_fail(data != NULL, NULL);
491         g_return_val_if_fail(*data != NULL, NULL);
492
493         while (**data == ' ') (*data)++;
494         if (**data != '\'' && **data != '"')
495                 return cmd_get_param(data);
496
497         quote = **data; (*data)++;
498
499         pos = *data;
500         while (**data != '\0' && **data != quote) {
501                 if (**data == '\\' && (*data)[1] != '\0')
502                         g_memmove(*data, (*data)+1, strlen(*data));
503                 (*data)++;
504         }
505
506         if (**data != '\0') *(*data)++ = '\0';
507
508         return pos;
509 }
510
511 /* Find specified option from list of options - the `option' might be
512    shortened version of the full command. Returns index where the
513    option was found, -1 if not found or -2 if there was multiple matches. */
514 static int option_find(char **array, const char *option)
515 {
516         char **tmp;
517         int index, found, len, multiple;
518
519         g_return_val_if_fail(array != NULL, -1);
520         g_return_val_if_fail(option != NULL, -1);
521
522         len = strlen(option);
523
524         found = -1; index = 0; multiple = FALSE;
525         for (tmp = array; *tmp != NULL; tmp++, index++) {
526                 const char *text = *tmp + iscmdtype(**tmp);
527
528                 if (g_strncasecmp(text, option, len) == 0) {
529                         if (text[len] == '\0') {
530                                 /* full match */
531                                 return index;
532                         }
533
534                         if (found != -1) {
535                                 /* multiple matches - we still need to check
536                                    if there's a full match left.. */
537                                 multiple = TRUE;
538                         }
539
540                         /* partial match, check that it's the only one */
541                         found = index;
542                 }
543         }
544
545         if (multiple)
546                 return -2;
547
548         return found;
549 }
550
551 static int get_cmd_options(char **data, int ignore_unknown,
552                            const char *cmd, GHashTable *options)
553 {
554         COMMAND_REC *rec;
555         char *option, *arg, **optlist;
556         int pos;
557
558         /* get option definations */
559         rec = cmd == NULL ? NULL : command_find(cmd);
560         optlist = rec == NULL ? NULL : rec->options;
561
562         option = NULL; pos = -1;
563         for (;;) {
564                 if (**data == '-') {
565                         if (option != NULL && *optlist[pos] == '+') {
566                                 /* required argument missing! */
567                                 *data = optlist[pos] + 1;
568                                 return CMDERR_OPTION_ARG_MISSING;
569                         }
570
571                         (*data)++;
572                         if (**data == '-' && i_isspace((*data)[1])) {
573                                 /* -- option means end of options even
574                                    if next word starts with - */
575                                 (*data)++;
576                                 while (i_isspace(**data)) (*data)++;
577                                 break;
578                         }
579
580                         if (**data == '\0')
581                                 option = "-";
582                         else if (!i_isspace(**data))
583                                 option = cmd_get_param(data);
584                         else {
585                                 option = "-";
586                                 (*data)++;
587                         }
588
589                         /* check if this option can have argument */
590                         pos = optlist == NULL ? -1 :
591                                 option_find(optlist, option);
592
593                         if (pos == -1 && optlist != NULL &&
594                             is_numeric(option, '\0')) {
595                                 /* check if we want -<number> option */
596                                 pos = option_find(optlist, "#");
597                                 if (pos != -1) {
598                                         g_hash_table_insert(options, "#",
599                                                             option);
600                                         pos = -3;
601                                 }
602                         }
603
604                         if (pos == -1 && !ignore_unknown) {
605                                 /* unknown option! */
606                                 *data = option;
607                                 return CMDERR_OPTION_UNKNOWN;
608                         }
609                         if (pos == -2 && !ignore_unknown) {
610                                 /* multiple matches */
611                                 *data = option;
612                                 return CMDERR_OPTION_AMBIGUOUS;
613                         }
614                         if (pos >= 0) {
615                                 /* if we used a shortcut of parameter, put
616                                    the whole parameter name in options table */
617                                 option = optlist[pos] +
618                                         iscmdtype(*optlist[pos]);
619                         }
620                         if (options != NULL && pos != -3)
621                                 g_hash_table_insert(options, option, "");
622
623                         if (pos < 0 || !iscmdtype(*optlist[pos]) ||
624                             *optlist[pos] == '!')
625                                 option = NULL;
626
627                         while (i_isspace(**data)) (*data)++;
628                         continue;
629                 }
630
631                 if (option == NULL)
632                         break;
633
634                 if (*optlist[pos] == '@' && !i_isdigit(**data))
635                         break; /* expected a numeric argument */
636
637                 /* save the argument */
638                 arg = cmd_get_quoted_param(data);
639                 if (options != NULL) {
640                         g_hash_table_remove(options, option);
641                         g_hash_table_insert(options, option, arg);
642                 }
643                 option = NULL;
644
645                 while (i_isspace(**data)) (*data)++;
646         }
647
648         return 0;
649 }
650
651 typedef struct {
652         char *data;
653         GHashTable *options;
654 } CMD_TEMP_REC;
655
656 static char *get_optional_channel(WI_ITEM_REC *active_item, char **data,
657                                   int require_name)
658 {
659         CHANNEL_REC *chanrec;
660         char *tmp, *origtmp, *channel, *ret;
661
662         if (active_item == NULL) {
663                 /* no active channel in window, channel required */
664                 return cmd_get_param(data);
665         }
666
667         origtmp = tmp = g_strdup(*data);
668         channel = cmd_get_param(&tmp);
669
670         if (strcmp(channel, "*") == 0 && !require_name) {
671                 /* "*" means active channel */
672                 cmd_get_param(data);
673                 ret = active_item->name;
674         } else if (!server_ischannel(active_item->server, channel)) {
675                 /* we don't have channel parameter - use active channel */
676                 ret = active_item->name;
677         } else {
678                 /* Find the channel first and use it's name if found.
679                    This allows automatic !channel -> !XXXXXchannel replaces. */
680                 channel = cmd_get_param(data);
681
682                 chanrec = channel_find(active_item->server, channel);
683                 ret = chanrec == NULL ? channel : chanrec->name;
684         }
685
686         g_free(origtmp);
687         return ret;
688 }
689
690 int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
691 {
692         WI_ITEM_REC *item;
693         CMD_TEMP_REC *rec;
694         GHashTable **opthash;
695         char **str, *arg, *datad;
696         va_list args;
697         int cnt, error, ignore_unknown, require_name;
698
699         g_return_val_if_fail(data != NULL, FALSE);
700
701         va_start(args, count);
702
703         rec = g_new0(CMD_TEMP_REC, 1);
704         rec->data = g_strdup(data);
705         *free_me = rec;
706
707         datad = rec->data;
708         error = FALSE;
709
710         item = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
711                 (WI_ITEM_REC *) va_arg(args, WI_ITEM_REC *);
712
713         if (count & PARAM_FLAG_OPTIONS) {
714                 arg = (char *) va_arg(args, char *);
715                 opthash = (GHashTable **) va_arg(args, GHashTable **);
716
717                 rec->options = *opthash =
718                         g_hash_table_new((GHashFunc) g_istr_hash,
719                                          (GCompareFunc) g_istr_equal);
720
721                 ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
722                 error = get_cmd_options(&datad, ignore_unknown,
723                                         arg, *opthash);
724         }
725
726         if (!error) {
727                 /* and now handle the string */
728                 cnt = PARAM_WITHOUT_FLAGS(count);
729                 if (count & PARAM_FLAG_OPTCHAN) {
730                         /* optional channel as first parameter */
731                         require_name = (count & PARAM_FLAG_OPTCHAN_NAME) ==
732                                 PARAM_FLAG_OPTCHAN_NAME;
733                         arg = get_optional_channel(item, &datad, require_name);
734
735                         str = (char **) va_arg(args, char **);
736                         if (str != NULL) *str = arg;
737                         cnt--;
738                 }
739
740                 while (cnt-- > 0) {
741                         if (cnt == 0 && count & PARAM_FLAG_GETREST) {
742                                 /* get rest */
743                                 arg = datad;
744                         } else {
745                                 arg = (count & PARAM_FLAG_NOQUOTES) ?
746                                         cmd_get_param(&datad) :
747                                         cmd_get_quoted_param(&datad);
748                         }
749
750                         str = (char **) va_arg(args, char **);
751                         if (str != NULL) *str = arg;
752                 }
753         }
754         va_end(args);
755
756         if (error) {
757                 signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
758                 signal_stop();
759
760                 cmd_params_free(rec);
761                 *free_me = NULL;
762         }
763
764         return !error;
765 }
766
767 void cmd_params_free(void *free_me)
768 {
769         CMD_TEMP_REC *rec = free_me;
770
771         if (rec->options != NULL) g_hash_table_destroy(rec->options);
772         g_free(rec->data);
773         g_free(rec);
774 }
775
776 static void command_module_unbind_all(COMMAND_REC *rec,
777                                       COMMAND_MODULE_REC *modrec)
778 {
779         GSList *tmp, *next;
780
781         for (tmp = modrec->callbacks; tmp != NULL; tmp = next) {
782                 COMMAND_CALLBACK_REC *cb = tmp->data;
783                 next = tmp->next;
784
785                 command_unbind_full(rec->cmd, cb->func, cb->user_data);
786         }
787
788         if (g_slist_find(commands, rec) != NULL) {
789                 /* this module might have removed some options
790                    from command, update them. */
791                 command_update_options(rec);
792         }
793 }
794
795 void commands_remove_module(const char *module)
796 {
797         GSList *tmp, *next, *modlist;
798
799         g_return_if_fail(module != NULL);
800
801         for (tmp = commands; tmp != NULL; tmp = next) {
802                 COMMAND_REC *rec = tmp->data;
803
804                 next = tmp->next;
805                 modlist = gslist_find_string(rec->modules, module);
806                 if (modlist != NULL)
807                         command_module_unbind_all(rec, modlist->data);
808         }
809 }
810
811 static int cmd_protocol_match(COMMAND_REC *cmd, SERVER_REC *server)
812 {
813         GSList *tmp;
814
815         for (tmp = cmd->modules; tmp != NULL; tmp = tmp->next) {
816                 COMMAND_MODULE_REC *rec = tmp->data;
817
818                 if (rec->protocol == -1) {
819                         /* at least one module accepts the command
820                            without specific protocol */
821                         return 1;
822                 }
823
824                 if (server != NULL && rec->protocol == server->chat_type) {
825                         /* matching protocol found */
826                         return 1;
827                 }
828         }
829
830         return 0;
831 }
832
833 #define alias_runstack_push(alias) \
834         alias_runstack = g_slist_append(alias_runstack, alias)
835
836 #define alias_runstack_pop(alias) \
837         alias_runstack = g_slist_remove(alias_runstack, alias)
838
839 #define alias_runstack_find(alias) \
840         (gslist_find_icase_string(alias_runstack, alias) != NULL)
841
842 static void parse_command(const char *command, int expand_aliases,
843                           SERVER_REC *server, void *item)
844 {
845         COMMAND_REC *rec;
846         const char *alias, *newcmd;
847         char *cmd, *orig, *args, *oldcmd;
848
849         g_return_if_fail(command != NULL);
850
851         cmd = orig = g_strconcat("command ", command, NULL);
852         args = strchr(cmd+8, ' ');
853         if (args != NULL) *args++ = '\0'; else args = "";
854
855         /* check if there's an alias for command. Don't allow
856            recursive aliases */
857         alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
858                 alias_find(cmd+8);
859         if (alias != NULL) {
860                 alias_runstack_push(cmd+8);
861                 eval_special_string(alias, args, server, item);
862                 alias_runstack_pop(cmd+8);
863                 g_free(orig);
864                 return;
865         }
866
867         /* check if this command can be expanded */
868         newcmd = command_expand(cmd+8);
869         if (newcmd == NULL) {
870                 /* ambiguous command */
871                 g_free(orig);
872                 return;
873         }
874
875         rec = command_find(newcmd);
876         if (rec != NULL && !cmd_protocol_match(rec, server)) {
877                 g_free(orig);
878
879                 signal_emit("error command", 2,
880                             GINT_TO_POINTER(server == NULL ?
881                                             CMDERR_NOT_CONNECTED :
882                                             CMDERR_ILLEGAL_PROTO));
883                 return;
884         }
885
886         cmd = g_strconcat("command ", newcmd, NULL);
887         g_strdown(cmd);
888
889         oldcmd = current_command;
890         current_command = cmd+8;
891         if (server != NULL) server_ref(server);
892         if (!signal_emit(cmd, 3, args, server, item)) {
893                 signal_emit_id(signal_default_command, 3,
894                                command, server, item);
895         }
896         if (server != NULL) {
897                 if (server->connection_lost)
898                         server_disconnect(server);
899                 server_unref(server);
900         }
901         current_command = oldcmd;
902
903         g_free(cmd);
904         g_free(orig);
905 }
906
907 static void event_command(const char *line, SERVER_REC *server, void *item)
908 {
909         char *cmdchar;
910         int expand_aliases = TRUE;
911
912         g_return_if_fail(line != NULL);
913
914         if (*line == '\0') {
915                 /* empty line, forget it. */
916                 signal_stop();
917                 return;
918         }
919
920         cmdchar = strchr(settings_get_str("cmdchars"), *line);
921         if (cmdchar != NULL && line[1] == ' ') {
922                 /* "/ text" = same as sending "text" to active channel. */
923                 line += 2;
924                 cmdchar = NULL;
925         }
926         if (cmdchar == NULL) {
927                 /* non-command - let someone else handle this */
928                 signal_emit("send text", 3, line, server, item);
929                 return;
930         }
931
932         /* same cmdchar twice ignores aliases ignores aliases */
933         line++;
934         if (*line == *cmdchar) {
935                 line++;
936                 expand_aliases = FALSE;
937         }
938
939         /* ^command hides the output - we'll do this at fe-common but
940            we have to skip the ^ char here.. */
941         if (*line == '^') line++;
942
943         parse_command(line, expand_aliases, server, item);
944 }
945
946 /* SYNTAX: EVAL <command(s)> */
947 static void cmd_eval(const char *data, SERVER_REC *server, void *item)
948 {
949         g_return_if_fail(data != NULL);
950
951         eval_special_string(data, "", server, item);
952 }
953
954 /* SYNTAX: CD <directory> */
955 static void cmd_cd(const char *data)
956 {
957         char *str;
958
959         g_return_if_fail(data != NULL);
960         if (*data == '\0') return;
961
962         str = convert_home(data);
963         chdir(str);
964         g_free(str);
965 }
966
967 void commands_init(void)
968 {
969         commands = NULL;
970         current_command = NULL;
971         alias_runstack = NULL;
972
973         signal_default_command = signal_get_uniq_id("default command");
974
975         settings_add_str("misc", "cmdchars", "/");
976         signal_add("send command", (SIGNAL_FUNC) event_command);
977
978         command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
979         command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
980 }
981
982 void commands_deinit(void)
983 {
984         g_free_not_null(current_command);
985
986         signal_remove("send command", (SIGNAL_FUNC) event_command);
987
988         command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
989         command_unbind("cd", (SIGNAL_FUNC) cmd_cd);
990 }