/*
* 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;
gchar *subject;
guint seq;
guint source;
GIOChannel *channel;
} cmd_cb_t;
static GSList *cmd_channels = NULL;
static gboolean is_room (const gchar *jid)
{
return roster_find (jid, jidsearch, ROSTER_TYPE_ROOM) ? TRUE : FALSE;
}
static gboolean cmd_send_msg (const gchar *to, const gchar *subject, const gchar *body)
{
gboolean room = is_room (to);
gpointer xep184 = NULL;
gint crypted;
xmpp_send_msg (to, body, room ? ROSTER_TYPE_ROOM : ROSTER_TYPE_USER, room ? NULL : subject, FALSE /* ? */, &crypted, LM_MESSAGE_SUB_TYPE_NOT_SET, &xep184);
if (crypted == -1) {
scr_LogPrint (LPRINT_LOGNORM, "cmd: Encryption error. Message not sent.");
return FALSE;
}
if (!room) {
if (subject) {
gchar *hbody = g_strdup_printf ("[%s]\n%s", subject, body);
hk_message_out (to, NULL, 0, hbody, crypted, xep184);
g_free (hbody);
} else
hk_message_out (to, NULL, 0, body, crypted, xep184);
}
return TRUE;
}
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;
GError *error = NULL;
chstat = g_io_channel_read_chars (channel, buf, HBB_BLOCKSIZE, &endpos, &error);
if (error) {
scr_LogPrint (LPRINT_DEBUG, "cmd: Reading error: %s.", error -> message);
g_clear_error (&error);
}
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;
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, &error);
if (error && error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE && bread) {
written = 0;
g_clear_error (&error);
utf8 = g_convert (input->str, bread, LocaleCharSet, "UTF-8", &bread, &written, &error);
}
if (written) {
gsize sent = 0;
while (sent < written) {
gsize len = 0;
gchar *bbuf = NULL;
gchar *subject = NULL;
gboolean ret;
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);
}
cb -> seq += 1;
if (cb -> subject)
subject = g_strdup_printf (cb -> subject, cb -> seq);
if (!cmd_send_msg (cb->jid, subject, len ? bbuf : (utf8 + sent)))
scr_LogPrint (LPRINT_LOGNORM, "cmd: Encryption error. Message not sent.");
if (subject)
g_free (subject);
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", error->message);
g_error_free (error);
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);
if (cb->source)
g_source_remove (cb->source);
if (cb->channel)
g_io_channel_unref (cb->channel);
if (cb -> subject)
g_free (cb -> subject);
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;
}
{
GIOChannel *channel;
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]);
{
GError *error = NULL;
channel = g_io_channel_unix_new (fd[0]);
g_io_channel_set_encoding (channel, NULL, &error);
if (error) {
scr_LogPrint (LPRINT_DEBUG, "cmd: Cannot unset channel encoding: %s.", error -> message);
g_clear_error (&error);
}
g_io_channel_set_buffered (channel, FALSE);
g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, &error);
if (error) {
scr_LogPrint (LPRINT_DEBUG, "cmd: Cannot set nonblocking flag on channel: %s.", error -> message);
g_error_free (error);
}
g_io_channel_set_close_on_unref (channel, TRUE);
}
{
cmd_cb_t *cb = g_new (cmd_cb_t, 1);
if (settings_opt_get_int ("cmd_header")) {
if (settings_opt_get_int ("cmd_header_inline") || is_room (jid)) {
gchar *mesg = g_strdup_printf ("$ %s", arg);
gchar *utf = to_utf8 (mesg);
g_free (mesg);
cmd_send_msg (jid, NULL, utf);
g_free (utf);
cb -> subject = NULL;
} else {
gchar *header = g_strdup_printf ("[%%02d] $ %s", arg);
cb -> subject = to_utf8 (header);
g_free (header);
}
}
cb -> jid = g_strdup (jid);
cb -> input = g_string_new (NULL);
cb -> seq = 0;
cb -> channel = channel;
cb -> source = g_io_add_watch_full (channel, 0, G_IO_IN|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: */