diff -r 000000000000 -r 72ffcc3c584e cmd.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd.c Sun Nov 08 21:09:37 2009 +0200 @@ -0,0 +1,266 @@ +/* + * cmd.c -- Send shell command output as messages + * + * Copyrigth (C) 2009 Myhailo Danylenko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include +#include +#include + +#include "commands.h" +#include "logprint.h" +#include "utils.h" +#include "settings.h" +#include "hbuf.h" +#include "xmpp.h" +#include "roster.h" + +typedef struct { + gchar *jid; + GString *input; + guint source; + GIOChannel *channel; +} cmd_cb_t; + +static GSList *cmd_channels = NULL; + +static gboolean cmd_reader (GIOChannel *channel, GIOCondition condition, gpointer data) +{ + cmd_cb_t *cb = (cmd_cb_t *) data; + + if (condition & (G_IO_IN|G_IO_PRI)) { + GIOStatus chstat; + static gchar buf[HBB_BLOCKSIZE]; + gsize endpos; + + chstat = g_io_channel_read_chars (channel, buf, HBB_BLOCKSIZE, &endpos, NULL); + + if (chstat == G_IO_STATUS_ERROR || chstat == G_IO_STATUS_EOF) { + cb->source = 0; + return FALSE; // XXX + } + + if (endpos) { + GString *input = cb->input; + gsize bread = 0; + gsize written = 0; + GError *err = NULL; + gchar *utf8 = NULL; + + g_string_append_len (input, buf, endpos); + + if (!lm_connection_is_authenticated (lconnection)) { + scr_LogPrint (LPRINT_LOGNORM, "cmd: Connection is not ready, delaying data"); + return TRUE; + } + + // usual g_locale_to_utf8 seem to be unable to detect locale charset + // maybe, proper solution will be to call setlocale on module loading, + // but mcabber already does this, and I do not want to mess with it + utf8 = g_convert (input->str, input->len, LocaleCharSet, "UTF-8", &bread, &written, &err); + + if (err && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE && bread) { + err = NULL; + written = 0; + utf8 = g_convert (input->str, bread, LocaleCharSet, "UTF-8", &bread, &written, &err); + } + + if (written) { + gsize sent = 0; + + while (sent < written) { + gint crypted; + gpointer xep184 = NULL; + gsize len = 0; + gchar *bbuf = NULL; + + if (written - sent > HBB_BLOCKSIZE) { + gchar *c = utf8 + sent + HBB_BLOCKSIZE; + c = g_utf8_find_prev_char (utf8 + sent, c); + + if (!c) { + scr_LogPrint (LPRINT_LOGNORM, "cmd: Cannot determine utf8 character end! End of data chunk will be discarded!"); + break; + } + + len = c - utf8 - sent; + bbuf = g_strndup (utf8 + sent, len); + } + + // XXX add command/sequence number as title? + xmpp_send_msg (cb->jid, len ? bbuf : (utf8 + sent), ROSTER_TYPE_USER, NULL, FALSE /* ? */, &crypted, 0, &xep184); + + if (crypted == -1) { + + scr_LogPrint (LPRINT_LOGNORM, "cmd: Encryption error. Message not sent."); + + if (!len) + break; + + g_free (bbuf); + + continue; + } + + hk_message_out (cb->jid, NULL, 0, len ? bbuf : (utf8 + sent), crypted, xep184); + + if (!len) + break; + + g_free (bbuf); + + sent += len; + } + + g_free (utf8); + + g_string_erase (input, 0, bread); + + } else { + + scr_LogPrint (LPRINT_LOGNORM, "cmd: Character conversion error: %s", err->message); + cb->source = 0; + return FALSE; + } + } + + } else if (condition & (G_IO_ERR|G_IO_NVAL|G_IO_HUP)) { + cb->source = 0; + return FALSE; // XXX + } + + return TRUE; +} + +static void cmd_destroy_data (gpointer data) +{ + cmd_cb_t *cb = (cmd_cb_t *) data; + + cmd_channels = g_slist_remove (cmd_channels, data); + + // May conflict - will be called during source removal? +// if (cb->source) +// g_source_remove (cb->source); + if (cb->channel) + g_io_channel_unref (cb->channel); + g_free (cb->jid); + g_free (cb); +} + +static void do_cmd (char *arg) +{ + int fd[2]; + const char *jid = CURRENT_JID; + + if (!*arg) + return; + + if (!jid) { + scr_LogPrint (LPRINT_LOGNORM, "Unsuitable buddy selected"); + return; + } + + if (pipe (fd)) { + scr_LogPrint (LPRINT_LOGNORM, "Cannot create pipe: %s", strerror (errno)); + return; + } + + { + int res = fork (); + + if (!res) { + + close (fd[0]); + dup2 (fd[1], STDOUT_FILENO); + if (settings_opt_get_int ("cmd_redirect_stderr")) + dup2 (fd[1], STDERR_FILENO); + else + close (STDERR_FILENO); + close (STDIN_FILENO); + close (fd[1]); + + { + const char *shell = settings_opt_get ("cmd_shell"); + if (!shell) + shell = getenv ("SHELL"); + if (!shell) + shell = "sh"; + execl (shell, shell, "-c", arg, NULL); + } + } + + if (res == -1) { + scr_LogPrint (LPRINT_NORMAL, "Cannot fork child: %s", strerror (errno)); + close (fd[0]); + close (fd[1]); + return; + } + + close (fd[1]); + + GIOChannel *channel = g_io_channel_unix_new (fd[0]); + + g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_encoding (channel, NULL, NULL); + g_io_channel_set_close_on_unref (channel, TRUE); + g_io_channel_set_buffered (channel, FALSE); + + { + cmd_cb_t *cb = g_new (cmd_cb_t, 1); + + cb->jid = g_strdup (jid); + cb->input = g_string_new (NULL); + cb->channel = channel; + cb->source = g_io_add_watch_full (channel, 0, G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL, + cmd_reader, (gpointer) cb, cmd_destroy_data); + + cmd_channels = g_slist_append (cmd_channels, cb); + } + } +} + +const gchar *g_module_check_init(GModule *module) +{ + cmd_add ("cmd", "", 0, 0, do_cmd, NULL); + + return NULL; +} + +void g_module_unload(GModule *module) +{ + GSList *sel; + + cmd_del ("cmd"); + + for (sel = cmd_channels; sel; sel = sel->next) { + cmd_cb_t *cb = (cmd_cb_t *) sel->data; + if (cb->source) + g_source_remove (cb->source); + if (cb->channel) + g_io_channel_unref (cb->channel); + g_free (cb->jid); + g_free (cb); + } + + g_slist_free (cmd_channels); +} + +/* vim: se ts=4 sw=4: */