--- /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 <isbear@ukrpost.net>
+ *
+ * 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 <stdlib.h>
+#include <glib.h>
+#include <gmodule.h>
+#include <unistd.h>
+#include <errno.h>
+
+#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: */