From 1281b18688171dc57d2e3cafb93c9e2f1dcf6c85 Mon Sep 17 00:00:00 2001 From: Sebastien Helleu Date: Thu, 22 Nov 2012 19:55:38 +0100 Subject: [PATCH] api: use hashtable "options" for command arguments in function hook_process_hashtable (optional, default is a split of string with command) --- ChangeLog | 8 ++-- doc/en/weechat_plugin_api.en.txt | 76 +++++++++++++++++++++++++++++-- doc/fr/weechat_plugin_api.fr.txt | 78 +++++++++++++++++++++++++++++--- doc/it/weechat_plugin_api.it.txt | 78 +++++++++++++++++++++++++++++--- src/core/wee-hook.c | 49 ++++++++++++++++++-- 5 files changed, 266 insertions(+), 23 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3807ddc2f..662ded33f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,14 +1,12 @@ WeeChat ChangeLog ================= Sébastien Helleu -v0.4.0-dev, 2012-11-18 +v0.4.0-dev, 2012-11-22 Version 0.4.0 (under dev!) -------------------------- -* core: do not call shell to execute command in hook_process (fix security - problem when a plugin/script gives untrusted command) (bug #37764) * core: stop cmake if gcrypt lib is not found (bug #37671) * core: add incomplete mouse events "event-down" and "event-drag" (task #11840) * core: add command /eval, use expression in conditions for bars @@ -20,6 +18,10 @@ Version 0.4.0 (under dev!) (problem with nick displayed in first line of screen) (bug #37556) * core: fix wrapping of words with wide chars (the break was made before the correct position) +* api: use hashtable "options" for command arguments in function + hook_process_hashtable (optional, default is a split of string with command) +* api: do not call shell to execute command in hook_process (fix security + problem when a plugin/script gives untrusted command) (bug #37764) * api: add new function "string_eval_expression" * api: connect with IPv6 by default in hook_connect (with fallback to IPv4), shuffle list of hosts for a same address, add argument "retry" for diff --git a/doc/en/weechat_plugin_api.en.txt b/doc/en/weechat_plugin_api.en.txt index 5b4b913f7..51c1421bf 100644 --- a/doc/en/weechat_plugin_api.en.txt +++ b/doc/en/weechat_plugin_api.en.txt @@ -6918,6 +6918,15 @@ weechat_hook_process Hook a process (launched with fork), and catch output. +[NOTE] +Since version 0.3.9.2, the shell is not used any more to execute the command. +WeeChat makes an automatic split of the command and its arguments (like the +shell does). + +If the split is not correct (according to quotes in your command), or if you +want to use shell, you can use function +<<_weechat_hook_process_hashtable,weechat_hook_process_hashtable>> with +arguments in the hashtable 'options' (_new in version 0.4.0_). + Prototype: [source,C] @@ -7066,8 +7075,13 @@ struct t_hook *weechat_hook_process_hashtable (const char *command, Arguments are the same as function <<_weechat_hook_process,weechat_hook_process>>, with an extra argument: -* 'options': options for command executed (see table below); the hashtable is - duplicated in function, so it's safe to free it after this call +* 'options': options for command executed; the hashtable is duplicated in + function, so it's safe to free it after this call + +For a standard command (not beginning with "url:"), the hashtable 'options' can +contain arguments for command (and then 'command' must be only the command +without arguments) (_new in version 0.4.0_). + +The keys in hashtable must be: 'arg1', 'arg2', ... For command "url:...", following options are available (see `man curl_easy_setopt` for a description of each option): @@ -7120,6 +7134,7 @@ my_process_cb (void *data, const char *command, int return_code, return WEECHAT_RC_OK; } +/* example 1: download URL */ struct t_hashtable *options = weechat_hashtable_new (8, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING, @@ -7134,6 +7149,42 @@ if (options) &my_process_cb, NULL); weechat_hashtable_free (options); } + +/* example 2: execute a notify program with a message from someone */ +struct t_hashtable *options_cmd1 = weechat_hashtable_new (8, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); +if (options_cmd1) +{ + weechat_hashtable_set (options_cmd1, "arg1", "-from"); + weechat_hashtable_set (options_cmd1, "arg2", nick); + weechat_hashtable_set (options_cmd1, "arg3", "-msg"); + weechat_hashtable_set (options_cmd1, "arg4", message); /* untrusted argument */ + struct t_hook *my_process_hook = weechat_hook_process_hashtable ("my-notify-command", + options_cmd1, + 20000, + &my_process_cb, NULL); + weechat_hashtable_free (options_cmd1); +} + +/* example 3: call shell to execute a command (command must be SAFE) */ +struct t_hashtable *options_cmd2 = weechat_hashtable_new (8, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); +if (options_cmd2) +{ + weechat_hashtable_set (options_cmd2, "arg1", "-c"); + weechat_hashtable_set (options_cmd2, "arg2", "ls -l /tmp | grep something"); + struct t_hook *my_process_hook = weechat_hook_process_hashtable ("sh", + options_cmd2, + 20000, + &my_process_cb, NULL); + weechat_hashtable_free (options_cmd2); +} ---------------------------------------- Script (Python): @@ -7156,9 +7207,24 @@ def my_process_cb(data, command, return_code, out, err): weechat.prnt("", "stderr: %s" % err) return weechat.WEECHAT_RC_OK -hook = weechat.hook_process_hashtable("url:http://www.weechat.org/", - { "file_out": "/tmp/weechat.org.html" }, - 20000, "my_process_cb", "") +# example 1: download URL +hook1 = weechat.hook_process_hashtable("url:http://www.weechat.org/", + { "file_out": "/tmp/weechat.org.html" }, + 20000, "my_process_cb", "") + +# example 2: execute a notify program with a message from someone +hook2 = weechat.hook_process_hashtable("my-notify-command", + { "arg1": "-from", + "arg2": nick, + "arg3": "-msg", + "arg4": message }, # untrusted argument + 20000, "my_process_cb", "") + +# example 3: call shell to execute a command (command must be SAFE) +hook3 = weechat.hook_process_hashtable("sh", + { "arg1": "-c", + "arg2": "ls -l /tmp | grep something" }, + 20000, "my_process_cb", "") ---------------------------------------- weechat_hook_connect diff --git a/doc/fr/weechat_plugin_api.fr.txt b/doc/fr/weechat_plugin_api.fr.txt index d6348e0b4..e8ea31958 100644 --- a/doc/fr/weechat_plugin_api.fr.txt +++ b/doc/fr/weechat_plugin_api.fr.txt @@ -7014,6 +7014,15 @@ weechat_hook_process Accroche un processus (lancé par un fork), et intercepter sa sortie. +[NOTE] +Depuis la version 0.3.9.2, le shell n'est plus utilisé pour exécuter la +commande. WeeChat effectue un découpage automatique de la commande et de ses +paramètres (comme le fait le shell). + +Si le découpage n'est pas correct (selon les guillemets utilisés dans votre +commande), ou si vous souhaitez utiliser le shell, vous pouvez utiliser la +fonction <<_weechat_hook_process_hashtable,weechat_hook_process_hashtable>> avec +les paramètres dans la hashtable 'options' (_nouveau dans la version 0.4.0_). + Prototype : [source,C] @@ -7169,9 +7178,14 @@ Les paramètres sont les mêmes que ceux de la fonction <<_weechat_hook_process,weechat_hook_process>>, avec un paramètre supplémentaire : -* 'options' : options pour la commande exécutée (voir le tableau ci-dessous); - la hashtable est dupliquée dans la fonction, donc il est possible de la - supprimer après cet appel +* 'options' : options pour la commande exécutée; la hashtable est dupliquée dans + la fonction, donc il est possible de la supprimer après cet appel + +Pour une commande standard (ne commençant pas par "url:"), la hashtable +'options' peut contenir les paramètres pour la commande (et donc 'command' doit +être seulement la commande sans les paramètres) +(_nouveau dans la version 0.4.0_). + +Les clés dans la hashtable doivent être: 'arg1', 'arg2', ... Pour la commande "url:...", les options suivantes sont disponibles (voir `man curl_easy_setopt` pour une description de chaque option) : @@ -7226,6 +7240,7 @@ my_process_cb (void *data, const char *command, int return_code, return WEECHAT_RC_OK; } +/* exemple 1: téléchargement d'une URL */ struct t_hashtable *options = weechat_hashtable_new (8, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING, @@ -7240,6 +7255,42 @@ if (options) &my_process_cb, NULL); weechat_hashtable_free (options); } + +/* exemple 2: exécution d'un programme de notification avec le message de quelqu'un */ +struct t_hashtable *options_cmd1 = weechat_hashtable_new (8, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); +if (options_cmd1) +{ + weechat_hashtable_set (options_cmd1, "arg1", "-from"); + weechat_hashtable_set (options_cmd1, "arg2", nick); + weechat_hashtable_set (options_cmd1, "arg3", "-msg"); + weechat_hashtable_set (options_cmd1, "arg4", message); /* paramètre non sûr */ + struct t_hook *my_process_hook = weechat_hook_process_hashtable ("my-notify-command", + options_cmd1, + 20000, + &my_process_cb, NULL); + weechat_hashtable_free (options_cmd1); +} + +/* exemple 3: appeler le shell pour exécuter la commande (la commande doit être SURE) */ +struct t_hashtable *options_cmd2 = weechat_hashtable_new (8, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); +if (options_cmd2) +{ + weechat_hashtable_set (options_cmd2, "arg1", "-c"); + weechat_hashtable_set (options_cmd2, "arg2", "ls -l /tmp | grep something"); + struct t_hook *my_process_hook = weechat_hook_process_hashtable ("sh", + options_cmd2, + 20000, + &my_process_cb, NULL); + weechat_hashtable_free (options_cmd2); +} ---------------------------------------- Script (Python): @@ -7262,9 +7313,24 @@ def my_process_cb(data, command, return_code, out, err): weechat.prnt("", "stderr: %s" % err) return weechat.WEECHAT_RC_OK -hook = weechat.hook_process_hashtable("url:http://www.weechat.org/", - { "file_out": "/tmp/weechat.org.html" }, - 20000, "my_process_cb", "") +# exemple 1: téléchargement d'une URL +hook1 = weechat.hook_process_hashtable("url:http://www.weechat.org/", + { "file_out": "/tmp/weechat.org.html" }, + 20000, "my_process_cb", "") + +# exemple 2: exécution d'un programme de notification avec le message de quelqu'un +hook2 = weechat.hook_process_hashtable("my-notify-command", + { "arg1": "-from", + "arg2": nick, + "arg3": "-msg", + "arg4": message }, # paramètre non sûr + 20000, "my_process_cb", "") + +# exemple 3: appeler le shell pour exécuter la commande (la commande doit être SURE) +hook3 = weechat.hook_process_hashtable("sh", + { "arg1": "-c", + "arg2": "ls -l /tmp | grep something" }, + 20000, "my_process_cb", "") ---------------------------------------- weechat_hook_connect diff --git a/doc/it/weechat_plugin_api.it.txt b/doc/it/weechat_plugin_api.it.txt index 8f5697b61..cab94113e 100644 --- a/doc/it/weechat_plugin_api.it.txt +++ b/doc/it/weechat_plugin_api.it.txt @@ -6946,6 +6946,15 @@ weechat_hook_process Hook su un processo (lanciato con un fork), e cattura l'output. +[NOTE] +// TRANSLATION MISSING +Since version 0.3.9.2, the shell is not used any more to execute the command. +WeeChat makes an automatic split of command and arguments (like the shell does). +If the split is not correct (according to quotes in your command), or if you +want to use shell, you can use function +<<_weechat_hook_process_hashtable,weechat_hook_process_hashtable>> with +arguments in the hashtable 'options' (_new in version 0.4.0_). + Prototipo: [source,C] @@ -7098,9 +7107,14 @@ struct t_hook *weechat_hook_process_hashtable (const char *command, Gli argomenti sono gli stessi della funzione <<_weechat_hook_process,weechat_hook_process>>, con un argomento aggiuntivo: -* 'options': le opzioni per il comando eseguito (consultare la tabella in - basso); la tabella hash è duplicata nella funzione, per cui è possibile - liberarla dopo questa chiamata +* 'options': le opzioni per il comando eseguito; la tabella hash è duplicata + nella funzione, per cui è possibile liberarla dopo questa chiamata + +// TRANSLATION MISSING +For a standard command (not beginning with "url:"), the hashtable 'options' can +contain arguments for command (and then 'command' must be only the command +without arguments) (_new in version 0.4.0_). + +The keys in hashtable must be: 'arg1', 'arg2', ... Per il comando "url:..." sono disponibili le seguenti opzioni (consultare `man curl_easy_setopt` per la descrizione di ogni opzione): @@ -7155,6 +7169,7 @@ my_process_cb (void *data, const char *command, int return_code, return WEECHAT_RC_OK; } +/* example 1: download URL */ struct t_hashtable *options = weechat_hashtable_new (8, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING, @@ -7169,6 +7184,42 @@ if (options) &my_process_cb, NULL); weechat_hashtable_free (options); } + +/* example 2: execute a notify program with a message from someone */ +struct t_hashtable *options_cmd1 = weechat_hashtable_new (8, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); +if (options_cmd1) +{ + weechat_hashtable_set (options_cmd1, "arg1", "-from"); + weechat_hashtable_set (options_cmd1, "arg2", nick); + weechat_hashtable_set (options_cmd1, "arg3", "-msg"); + weechat_hashtable_set (options_cmd1, "arg4", message); /* unsafe argument */ + struct t_hook *my_process_hook = weechat_hook_process_hashtable ("my-notify-command", + options_cmd1, + 20000, + &my_process_cb, NULL); + weechat_hashtable_free (options_cmd1); +} + +/* example 3: call shell to execute a command (command must be SAFE) */ +struct t_hashtable *options_cmd2 = weechat_hashtable_new (8, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); +if (options_cmd2) +{ + weechat_hashtable_set (options_cmd2, "arg1", "-c"); + weechat_hashtable_set (options_cmd2, "arg2", "ls -l /tmp | grep something"); + struct t_hook *my_process_hook = weechat_hook_process_hashtable ("sh", + options_cmd2, + 20000, + &my_process_cb, NULL); + weechat_hashtable_free (options_cmd2); +} ---------------------------------------- Script (Python): @@ -7191,9 +7242,24 @@ def my_process_cb(data, command, return_code, out, err): weechat.prnt("", "stderr: %s" % err) return weechat.WEECHAT_RC_OK -hook = weechat.hook_process_hashtable("url:http://www.weechat.org/", - { "file_out": "/tmp/weechat.org.html" }, - 20000, "my_process_cb", "") +# example 1: download URL +hook1 = weechat.hook_process_hashtable("url:http://www.weechat.org/", + { "file_out": "/tmp/weechat.org.html" }, + 20000, "my_process_cb", "") + +# example 2: execute a notify program with a message from someone +hook2 = weechat.hook_process_hashtable("my-notify-command", + { "arg1": "-from", + "arg2": nick, + "arg3": "-msg", + "arg4": message }, # unsafe argument + 20000, "my_process_cb", "") + +# example 3: call shell to execute a command (command must be SAFE) +hook3 = weechat.hook_process_hashtable("sh", + { "arg1": "-c", + "arg2": "ls -l /tmp | grep something" }, + 20000, "my_process_cb", "") ---------------------------------------- weechat_hook_connect diff --git a/src/core/wee-hook.c b/src/core/wee-hook.c index 5e708d0d6..40ae1a52f 100644 --- a/src/core/wee-hook.c +++ b/src/core/wee-hook.c @@ -1388,9 +1388,9 @@ hook_process (struct t_weechat_plugin *plugin, void hook_process_child (struct t_hook *hook_process) { - char **exec_args; + char **exec_args, str_arg[64]; const char *ptr_url; - int rc, i; + int rc, i, num_args; /* * close stdin, so that process will fail to read stdin (process reading @@ -1429,7 +1429,50 @@ hook_process_child (struct t_hook *hook_process) else { /* launch command */ - exec_args = string_split_shell (HOOK_PROCESS(hook_process, command)); + num_args = 0; + if (HOOK_PROCESS(hook_process, options)) + { + /* + * count number of arguments given in the hashable options, + * keys are: "arg1", "arg2", ... + */ + while (1) + { + snprintf (str_arg, sizeof (str_arg), "arg%d", num_args + 1); + if (!hashtable_has_key (HOOK_PROCESS(hook_process, options), str_arg)) + break; + num_args++; + } + } + if (num_args > 0) + { + /* + * if at least one argument was found in hashtable option, the + * "command" contains only path to binary (without arguments), and + * the arguments are in hashtable + */ + exec_args = malloc ((num_args + 2) * sizeof (exec_args[0])); + if (exec_args) + { + exec_args[0] = HOOK_PROCESS(hook_process, command); + for (i = 1; i <= num_args; i++) + { + snprintf (str_arg, sizeof (str_arg), "arg%d", i); + exec_args[i] = (char *)hashtable_get (HOOK_PROCESS(hook_process, options), + str_arg); + } + exec_args[num_args + 1] = NULL; + } + } + else + { + /* + * if no arguments were found in hashtable, make an automatic split + * of command, like the shell does + */ + exec_args = string_split_shell (HOOK_PROCESS(hook_process, command)); + } + if (exec_args) { if (weechat_debug_core >= 1)