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;
}