All pastes #70934 Raw Edit

Anonymous

public text v1 · immutable
#70934 ·published 2006-06-25 00:36 UTC
rendered paste body
#ifndef HAVE_CONFIG_H
	#ifndef GAIM_PLUGINS
		#define GAIM_PLUGINS
	#endif
#endif

#include "debug.h"
#include "signals.h"
#include "util.h"
#include "version.h"

#include "gtkgaim.h"
#include "gtkplugin.h"
#include "gtkutils.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

#define COMMAND_PLUGIN_ID "bangexec_command_insertion"
#define SHORTDESC "Bangexec Command Interpreter"
#define BANGEXEC_VERSION "1.3.0.2"

//User configurable
static gchar	BANGEXEC_LOCAL_MAXLEN[7],
		BANGEXEC_REMOTE_MAXLEN[7],
		BANGEXEC_TIMEOUT[4];
static gboolean	BANGEXEC_PADSTART,
		BANGEXEC_PADEND;

static GtkWidget	*lenentry,
			*llenentry,
			*timeoutentry,
			*padendcb,
			*padstartcb;


/*
 * EXPORTED FUNCTIONS
 */

static gboolean plugin_load (GaimPlugin * plugin);
static void init_plugin (GaimPlugin * plugin);
static GtkWidget* get_config_frame(GaimPlugin *plugin);

/*
 * PRIVATE FUNCTIONS
 */
static gboolean substitute (GaimAccount * account, GaimConversation * conv,
			    char **message, void *data);
static int handle_exec_command (const gchar *command, GString *result, unsigned long maxlen);
static GString* strip_markup(const gchar *text);
static int	lookup_ampcode(gchar *ampcode);
static int read_nonblock(int fd,unsigned long len,unsigned long timeout_usec,GString *inp);
static gboolean update_conf(GtkWidget *caller,gpointer *data);
static gboolean is_numeric(const gchar *s, int offset, int len);
static gboolean load_conf();
static gboolean save_conf();
static int buf_get_line(char *ibuf, char **buf, int *position, int len);


static gboolean
plugin_load (GaimPlugin * plugin)
{
  void *conv_handle = gaim_conversations_get_handle ();
	
	load_conf();

  gaim_signal_connect (conv_handle, "writing-im-msg",
		       plugin, GAIM_CALLBACK (substitute), NULL);
  gaim_signal_connect (conv_handle, "writing-chat-msg",
		       plugin, GAIM_CALLBACK (substitute), NULL);

  return TRUE;
}

static GtkWidget *
get_config_frame(GaimPlugin *plugin)
{
	GtkWidget	*ret, *vbox;
	GtkWidget	*maxlenl=NULL,
			*lmaxlenl=NULL,
				*timeoutl=NULL;

	ret = gtk_vbox_new(FALSE, 18);
	gtk_container_set_border_width (GTK_CONTAINER (ret), 12);

	vbox = gaim_gtk_make_frame(ret, _(SHORTDESC));
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
	gtk_box_set_child_packing(GTK_BOX(ret),vbox,TRUE,TRUE,0,GTK_PACK_START);
	//gtk_widget_set_size_request(vbox, 300, -1);
	gtk_widget_show (vbox);

	maxlenl=gtk_label_new("Maximum public output length (in characters):");
	gtk_misc_set_alignment(GTK_MISC(maxlenl),0,0);
	gtk_box_pack_start(GTK_BOX(vbox),maxlenl,TRUE,TRUE,1);
	lenentry=gtk_entry_new_with_max_length(6);
	gtk_entry_set_text(GTK_ENTRY(lenentry),BANGEXEC_REMOTE_MAXLEN);
	gtk_box_pack_start(GTK_BOX(vbox),lenentry,TRUE,TRUE,1);
	
	lmaxlenl=gtk_label_new("Maximum private output length (in characters):");
	gtk_misc_set_alignment(GTK_MISC(lmaxlenl),0,0);
	gtk_box_pack_start(GTK_BOX(vbox),lmaxlenl,TRUE,TRUE,1);
	llenentry=gtk_entry_new_with_max_length(6);
	gtk_entry_set_text(GTK_ENTRY(llenentry),BANGEXEC_LOCAL_MAXLEN);
	gtk_box_pack_start(GTK_BOX(vbox),llenentry,TRUE,TRUE,1);
	
	timeoutl=gtk_label_new("Command timeout interval (in seconds):");
	gtk_misc_set_alignment(GTK_MISC(timeoutl),0,0);
	//gtk_container_add(GTK_CONTAINER(vbox),timeoutl);
	gtk_box_pack_start(GTK_BOX(vbox),timeoutl,TRUE,TRUE,1);
	timeoutentry=gtk_entry_new_with_max_length(3);
	gtk_entry_set_text(GTK_ENTRY(timeoutentry),BANGEXEC_TIMEOUT);
	//gtk_container_add(GTK_CONTAINER(vbox),timeoutentry);
	gtk_box_pack_start(GTK_BOX(vbox),timeoutentry,TRUE,TRUE,1);

	padstartcb=gtk_check_button_new_with_label("Pad start of output with a newline");
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(padstartcb),BANGEXEC_PADSTART);
	gtk_misc_set_alignment(GTK_MISC(padstartcb),0,0);
	//gtk_container_add(GTK_CONTAINER(vbox),padstartcb);
	gtk_box_pack_start(GTK_BOX(vbox),padstartcb,TRUE,TRUE,1);
	
	padendcb=gtk_check_button_new_with_label("Pad end of output with a newline");
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(padendcb),BANGEXEC_PADEND);
	gtk_misc_set_alignment(GTK_MISC(padendcb),0,0);
	//gtk_container_add(GTK_CONTAINER(vbox),padendcb);
	gtk_box_pack_start(GTK_BOX(vbox),padendcb,TRUE,TRUE,1);
	

	g_signal_connect(GTK_WIDGET(ret), "destroy", G_CALLBACK(update_conf),NULL);


	gtk_widget_show_all(ret);
	return ret;
}



static GaimGtkPluginUiInfo ui_info =
{
	get_config_frame
};


static GaimPluginInfo info =
{
	GAIM_PLUGIN_MAGIC,
	GAIM_MAJOR_VERSION,
	GAIM_MINOR_VERSION,
	GAIM_PLUGIN_STANDARD,
	GAIM_GTK_PLUGIN_TYPE,
	0,
	NULL,
	GAIM_PRIORITY_DEFAULT,
	COMMAND_PLUGIN_ID,
	N_(SHORTDESC),
  BANGEXEC_VERSION,
  N_("Inserts the output of a command into the conversation."),
  N_("Inserts the output of a command into the conversation."),
  "Levi Bard <tak@bard.sytes.net>",
  "http://bard.sytes.net/bangexec/",
	plugin_load,
	NULL,
	NULL,
	&ui_info,
	NULL,
	NULL,
	NULL
};


static void
init_plugin (GaimPlugin * plugin)
{
		strcpy(BANGEXEC_LOCAL_MAXLEN,"512");
		strcpy(BANGEXEC_REMOTE_MAXLEN,"512");
		strcpy(BANGEXEC_TIMEOUT,"10");
		BANGEXEC_PADSTART=TRUE;
		BANGEXEC_PADEND=TRUE;
}//init_plugin

GAIM_INIT_PLUGIN (command, init_plugin, info)
 
/*
 * Private functions
 */


 /*
  * substitute
  * If *message contains a (possibly marked up) string of the form:
  * ^[!]{1,2}command , command is executed, and the output is:
  * !) Put into the conversation
  * !!) Displayed locally only
  * !!!) quote "!command" as message
  * Param account: Account from which the message is being sent
  * Param conv: Conversation to which the message is being sentst
  * Param message: Pointer to the message string
  * Param data: Don't care
  * Returns: FALSE if further handling required,
  *			TRUE otherwise
  */
static gboolean substitute (GaimAccount * account, GaimConversation * conv,
							   char **message, void *data){
    gchar	*cursor = NULL, 
			*tmp = NULL, 
			*tofree = NULL;

	gboolean	rv = FALSE;
	GString		*tmpgs=NULL,
			*result=g_string_new("");

	if (NULL == message){
		return FALSE;
	}else if (NULL == (*message)){
		return FALSE;
	}else if(!strchr(*message,'!')){	//no ! or !!, don't bother stripping markup or checking further
		return FALSE;
	}//if
	
	cursor=g_malloc((strlen(*message)+1)*sizeof(gchar));
	strcpy(cursor,*message);

//	if(strcmp(gaim_account_get_protocol_id(account),"prpl-irc")){	//no markup in irc (other protocols too?)
		tmpgs=strip_markup(cursor);
		strcpy(cursor,tmpgs->str);
		g_string_free(tmpgs,TRUE);
//	}//if

	if ('!' == (*cursor)){
		if ('!' == (*(cursor+1))){			
			if('!' == *(cursor+2)){			//!!!command, quote "!command"
				tmp=g_strdup(cursor+2);
				tofree=(*message);
				(*message)=tmp;
				g_free(tofree);
			}//if
			else if('\0'!=*(cursor+2)){	//!!command, write output to local window only
				handle_exec_command (cursor+2, result,strtoul(BANGEXEC_LOCAL_MAXLEN,NULL,10));
				(**message) = '\0';	//may not need this if returning TRUE
				gaim_conversation_write(conv, NULL, result->str, GAIM_MESSAGE_NO_LOG, time(NULL));
				rv=TRUE;
			}//if
		}else if('\0'!=*(cursor+1)){								//!command, return output to the conversation
			handle_exec_command (cursor+1, result,strtoul(BANGEXEC_REMOTE_MAXLEN,NULL,10));
			tmp = g_malloc ((result->len * sizeof (gchar)) + 1);
		    strcpy (tmp, result->str);
			tofree = (*message);			//replace *message with tmp and free *message
			(*message) = tmp;
			g_free (tofree);
		}//if !/!!
	}//if ^!
	g_string_free(result,TRUE);
	g_free(cursor);
	return rv;
}//substitute


/*
 * handle_exec_command
 * Runs a sh command and stores the output in a GString
 * Param command: The command to be run
 * Param result: A string where the command output will be stored
 * Param maxlen: The maximum acceptable output length
 * Returns: Currently always 0
 */
static int
handle_exec_command (const gchar *command, GString *result,unsigned long maxlen)
{
	GString *inp=g_string_new("");
			
	int p[2], pid, chrs;

	pipe(p);
	g_string_assign (inp, command);
	g_string_append (inp, ":");
	if(BANGEXEC_PADSTART){
		g_string_append (inp, "\n");
	}else{
		g_string_append(inp,"  ");
	}//if
	chrs = inp->len;
	if ((pid = fork ()) == -1){	//Error forking
		g_string_assign (result, command);
		g_string_append (result, ": couldn't fork");
		return -1;
    }else if (pid){		//Parent
		int rv;

		close (p[1]);
		int flags = fcntl (p[0], F_GETFL, 0);
		fcntl (p[0], F_SETFL, flags | O_NONBLOCK);
		
		rv=read_nonblock(p[0],maxlen-chrs,strtoul(BANGEXEC_TIMEOUT,NULL,10),inp);	

	    if (kill (pid, 0) == 0){	//kill child after reading max allowed output
				 kill (pid, SIGKILL);
		}//if
		if (rv<0){
			g_string_append (inp, "[process timed out]");
	    }else if(maxlen==(rv+chrs)){	//Output was truncated
			g_string_append(inp,"...");
	    }else if((!BANGEXEC_PADEND) && ('\n'==(inp->str)[inp->len-1])){
		    g_string_truncate(inp,inp->len-1);
		}//if

		g_string_assign (result, inp->str);
		
		g_string_free(inp,TRUE);
		waitpid (pid, NULL, 0);
	}else{	//Child
	    close (p[0]);
		if (p[1] != STDOUT_FILENO){
			dup2 (p[1], STDOUT_FILENO);
			close (p[1]);
		}//if
		if (STDERR_FILENO != STDOUT_FILENO){
			dup2 (STDOUT_FILENO, STDERR_FILENO);
		}//if
		execlp ("sh", "sh", "-c", command, NULL);
	}//if parent/child
	return 0;
}//handle_exec_command


/*
 * strip_markup
 * Strips simple HTML markup from a string
 * Param text: The marked-up text to be stripped
 * Returns: A stripped GString* (that needs to be freed)
 ** Free the GStrings! **
 */
static GString* strip_markup(const gchar *text){
	GString *tmp=g_string_new(text);
	int i=0,
		starttag=-1,
		startamp=-1,
		offset=0,
		textlen=tmp->len;
		
	gchar	tmpc[textlen+1];
	
	for(i=0;i<textlen;++i){
		switch(text[i]){
			//<tag>
			case '<':
				if(starttag<0){
					starttag=i;
				}
				break;
			case '>':
				if(starttag>=0){
					g_string_erase(tmp,starttag-offset,(i-starttag)+1);
					offset+=(i-starttag)+1;
					starttag=-1;
				}
				break;

			//&specialcharname;
			case '&':
				startamp=i;
				break;
			case ';':	//replace &specialcharname; with specialchar
				strncpy(tmpc,text+(startamp+1),(i-startamp)-1);
				tmpc[(i-startamp)-1]='\0';
				g_string_erase(tmp,startamp-offset,(i-startamp)+1);
				lookup_ampcode(tmpc);
						
				g_string_insert(tmp,startamp-offset,tmpc);
				offset+=(i-startamp)+1;
                offset-=strlen(tmpc);
				break;
		}//switch
	}//for
	
	return tmp;
}//strip_markup


/* 
 * lookup_ampcode
 * Replaces a &specialcharname; HTML sequence with the character
 * that it represents
 * Param ampcode: The sequence to be replaced
 *				The replacement is stored here
 * Returns: Currently always 0
 */
static int	lookup_ampcode(gchar *ampcode){
	if(!strcasecmp(ampcode,"amp")){
		strcpy(ampcode,"&");
	}else if(!strcasecmp(ampcode,"quot")){
		strcpy(ampcode,"\"");
	}else if(!strcasecmp(ampcode,"lt")){
		strcpy(ampcode,"<");
	}else if(!strcasecmp(ampcode,"gt")){
		strcpy(ampcode,">");
	}else if(!strcasecmp(ampcode,"apos")){
		strcpy(ampcode,"'");
	}else{
		strcpy(ampcode,"");
	}//if common ampcodes
	return 0;
}//lookup_ampcode



/*
 * read_nonblock
 * Reads up to a specified number of characters from a nonblocking fd,
 * or up to a specified timeout
 * Param fd: The file descriptor for reading
 * Param len: The maximum number of characters to be read
 * Param timeout: The maximum time to wait, in seconds
 * Param inp: The GString* that will receive the input from the fd
 * Returns: The number of characters read, or -1 on timeout
 */
static int read_nonblock(int fd,unsigned long len,unsigned long timeout,GString *inp){
	int chrs=0,
		timedout=FALSE,
		rv=0;
	unsigned long 	timeout_usec=timeout*1000000,
					tlapsed=0;
	static const int SLEEP_INTERVAL=50000;

	gchar	*ptr=g_malloc((len+1)*sizeof(gchar));

	while (chrs < len){
		if (tlapsed > timeout_usec){
			timedout = TRUE;
			break;
		}//if
		if ((rv = read (fd, ptr, (len - chrs))) < 0){	//Error reading - wait and try again
			rv = errno;
			usleep (SLEEP_INTERVAL);
			tlapsed += SLEEP_INTERVAL;
			continue;
		}else if (rv == 0){	//Done reading
			break;
		}//if
		chrs += rv;
		*(ptr + rv)= 0;
		g_string_append(inp,ptr);
		*ptr=0;
	}//while
	g_free(ptr);
	if(timedout){
		return -1;
	}else{
		return chrs;
	}//if
}//read_nonblock

static gboolean update_conf(GtkWidget *caller,gpointer *data){
			
	const gchar	*newlen=gtk_entry_get_text(GTK_ENTRY(lenentry)),
	      		*lnewlen=gtk_entry_get_text(GTK_ENTRY(llenentry)),
			*newtimeout=gtk_entry_get_text(GTK_ENTRY(timeoutentry));
	gboolean rv=FALSE;
	if(is_numeric(newlen,0,strlen(newlen))){
		strcpy(BANGEXEC_REMOTE_MAXLEN,newlen);
		rv=TRUE;
	}//if
	if(is_numeric(lnewlen,0,strlen(lnewlen))){
		strcpy(BANGEXEC_LOCAL_MAXLEN,lnewlen);
		rv=TRUE;
	}//if
	if(is_numeric(newtimeout,0,strlen(newtimeout))){
		strcpy(BANGEXEC_TIMEOUT,newtimeout);
		rv=TRUE;
	}//if

	BANGEXEC_PADSTART=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(padstartcb));
	BANGEXEC_PADEND=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(padendcb));
	
	save_conf();

	return rv;
}//update_conf

static gboolean is_numeric(const gchar *s, int offset, int len){
	int i;
	gboolean rv=TRUE;
	
	if(0==len){
		return FALSE;
	}//if
	
	for(i=offset;i<len;++i){
		if(!g_ascii_isdigit(s[i])){
			rv=FALSE;
		}//if
	}//for
	
	return rv;
}//is_numeric

static gboolean load_conf(){
	gchar *buf, *ibuf;
	char len[7];
	char timeout[4];
	int pnt = 0;
	gsize size;

	buf = g_build_filename(gaim_user_dir(), "bangexec", NULL);
	g_file_get_contents(buf, &ibuf, &size, NULL);
	g_free(buf);
	if(!ibuf) {
		return FALSE;
	}//if

	timeout[0] = 0;
	len[0] = 0;

	while(buf_get_line(ibuf, &buf, &pnt, size)) {
		if (*buf != '#') {
			if (!strncasecmp(buf, "BANGEXEC_MAXLEN ", 16)){
				strncpy(len, buf + 16, 6);
				if (is_numeric(len,0,strlen(len))) {
					strcpy(BANGEXEC_REMOTE_MAXLEN,len);
					strcpy(BANGEXEC_LOCAL_MAXLEN,len);
				}//if
			}//if
			if (!strncasecmp(buf, "BANGEXEC_LOCAL_MAXLEN ", 16)){
				strncpy(len, buf + 22, 6);
				if (is_numeric(len,0,strlen(len))) {
					strcpy(BANGEXEC_LOCAL_MAXLEN,len);
				}//if
			}//if
			if (!strncasecmp(buf, "BANGEXEC_REMOTE_MAXLEN ", 16)){
				strncpy(len, buf + 23, 6);
				if (is_numeric(len,0,strlen(len))) {
					strcpy(BANGEXEC_REMOTE_MAXLEN,len);
				}//if
			}//if
			if (!strncasecmp(buf, "BANGEXEC_TIMEOUT ", 17)) {
				strncpy(timeout, buf + 17, 3);
			}//if
			if (!strncasecmp(buf, "BANGEXEC_PADSTART ", 18)) {
				if(is_numeric(buf,18,strlen(buf))){
					BANGEXEC_PADSTART=(gboolean)atoi(buf+18);
				}//if
			}//if
			if (!strncasecmp(buf, "BANGEXEC_PADEND ", 16)) {
				if(is_numeric(buf,16,strlen(buf))){
					BANGEXEC_PADEND=(gboolean)atoi(buf+16);
				}//if
			}//if
		}//if
	}//while
	if(is_numeric(timeout,0,strlen(timeout))){
		strcpy(BANGEXEC_TIMEOUT,timeout);
	}//if
	
	g_free(ibuf);
	return TRUE;
}//load_conf

static gboolean save_conf()
{
	FILE *f;
	char *name;
	char tempfilename[BUF_LONG];
	int fd;

	name = g_build_filename(gaim_user_dir(), "bangexec", NULL);
	strcpy(tempfilename, name);
	strcat(tempfilename,".XXXXXX");
	fd = g_mkstemp(tempfilename);
	if(fd<0) {
		perror(tempfilename);
		g_free(name);
		return FALSE;
	}//if
	if (!(f = fdopen(fd, "w"))) {
		perror("fdopen");
		close(fd);
		g_free(name);
		return FALSE;
	}//if

	fchmod(fd, S_IRUSR | S_IWUSR);

	fprintf(f,"%s %s\n","BANGEXEC_REMOTE_MAXLEN",BANGEXEC_REMOTE_MAXLEN);
	fprintf(f,"%s %s\n","BANGEXEC_LOCAL_MAXLEN",BANGEXEC_LOCAL_MAXLEN);
	fprintf(f,"%s %s\n","BANGEXEC_TIMEOUT",BANGEXEC_TIMEOUT);
	fprintf(f,"%s %d\n","BANGEXEC_PADSTART",BANGEXEC_PADSTART);
	fprintf(f,"%s %d\n","BANGEXEC_PADEND",BANGEXEC_PADEND);
	
	if(fclose(f)) {
		gaim_debug(GAIM_DEBUG_ERROR, "bangexec",
				   "Error writing to %s: %m\n", tempfilename);
		unlink(tempfilename);
		g_free(name);
		return FALSE;
	}//if
	rename(tempfilename, name);
	g_free(name);
	return TRUE;
}//save_conf

static int buf_get_line(char *ibuf, char **buf, int *position, int len) {
	int pos = *position, spos = pos;

	if (pos == len)
		return 0;

	while (ibuf[pos++] != '\n') {
		if (pos == len)
			return 0;
	}
	pos--;
	ibuf[pos] = 0;
	*buf = &ibuf[spos];
	pos++;
	*position = pos;
	return 1;
}