/*
* 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;
int type = roster_find (cb->jid, jidsearch, ROSTER_TYPE_ROOM) ? ROSTER_TYPE_ROOM : ROSTER_TYPE_USER;
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), type, NULL, FALSE /* ? */, &crypted, LM_MESSAGE_SUB_TYPE_NOT_SET, &xep184);
if (crypted == -1) {
scr_LogPrint (LPRINT_LOGNORM, "cmd: Encryption error. Message not sent.");
if (!len)
break;
g_free (bbuf);
continue;
}
if (type != ROSTER_TYPE_ROOM)
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: */