#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 #include #include #include #include #include #include #include #include #include #include #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 ", "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 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