cmd.c
changeset 0 72ffcc3c584e
child 4 e305d7e562c4
--- /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: */