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