315 lines
7.8 KiB
C
315 lines
7.8 KiB
C
/* Author: Benoît Rouits ( brouits@free.fr ) thanks to Neil Spring.
|
|
from LicqClient by Yong-iL Joh ( tolkien@mizi.com )
|
|
and Jorge García ( Jorge.Garcia@uv.es )
|
|
*
|
|
* generic Shell command support
|
|
*
|
|
* Last Updated : Tue Mar 5 15:23:35 CET 2002
|
|
*
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "Client.h"
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <signal.h>
|
|
#include <assert.h>
|
|
#include <strings.h>
|
|
#include "charutil.h"
|
|
#include "MessageList.h"
|
|
#ifdef USE_DMALLOC
|
|
#include <dmalloc.h>
|
|
#endif
|
|
|
|
#define SH_DM(pc, lvl, args...) DM(pc, lvl, "shell: " args)
|
|
|
|
/* kind_popen bumps off the sigchld handler - we care whether
|
|
a checking program fails. */
|
|
|
|
#ifdef __LCLINT__
|
|
void (*old_signal_handler) (int);
|
|
#else
|
|
RETSIGTYPE(*old_signal_handler) (int);
|
|
#endif
|
|
|
|
/*@null@*/
|
|
FILE *kind_popen(const char *command, const char *type)
|
|
{
|
|
FILE *ret;
|
|
assert(strcmp(type, "r") == 0);
|
|
assert(old_signal_handler == NULL);
|
|
old_signal_handler = signal(SIGCHLD, SIG_DFL);
|
|
ret = popen(command, type);
|
|
if (ret == NULL) {
|
|
DMA(DEBUG_ERROR, "popen: error while reading '%s': %s\n",
|
|
command, strerror(errno));
|
|
(void) signal(SIGCHLD, old_signal_handler);
|
|
old_signal_handler = NULL;
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/* kind_pclose checks the return value from pclose and prints
|
|
some nice error messages about it. ordinarily, this would be
|
|
a good idea, but wmbiff has a sigchld handler that reaps
|
|
children immediately (needed when spawning other child processes),
|
|
so no error checking can be done here until that's disabled */
|
|
|
|
/* returns as a mailcheck function does: -1 on fail, 0 on success */
|
|
static int kind_pclose( /*@only@ */ FILE * F,
|
|
const char *command,
|
|
/*@null@ */ Pop3 pc)
|
|
{
|
|
int exit_status = pclose(F);
|
|
|
|
if (old_signal_handler != NULL) {
|
|
(void) signal(SIGCHLD, old_signal_handler);
|
|
old_signal_handler = NULL;
|
|
}
|
|
|
|
if (exit_status != 0) {
|
|
if (exit_status == -1) {
|
|
/* wmbiff has a sigchld handler already, so wait is likely
|
|
to fail */
|
|
SH_DM(pc, DEBUG_ERROR, "pclose '%s' failed: %s\n",
|
|
command, strerror(errno));
|
|
} else {
|
|
SH_DM(pc, DEBUG_ERROR,
|
|
"'%s' exited with non-zero status %d\n", command,
|
|
exit_status);
|
|
}
|
|
}
|
|
return (exit_status);
|
|
}
|
|
|
|
int grabCommandOutput(Pop3 pc, const char *command, /*@out@ */
|
|
char **output, /*@out@ *//*@null@ */ char **details)
|
|
{
|
|
FILE *F;
|
|
char linebuf[512];
|
|
SH_DM(pc, DEBUG_INFO, "Executing '%s'\n", command);
|
|
*output = NULL;
|
|
if ((F = kind_popen(command, "r")) == NULL) {
|
|
return -1;
|
|
}
|
|
if (fgets(linebuf, 512, F) == NULL) {
|
|
SH_DM(pc, DEBUG_ERROR,
|
|
"fgets: unable to read the output of '%s': %s\n", command,
|
|
strerror(errno));
|
|
} else {
|
|
chomp(linebuf); /* remove trailing newline */
|
|
*output = strdup_ordie(linebuf);
|
|
}
|
|
if (details) {
|
|
static char allbuf[4096];
|
|
allbuf[0] = '\0';
|
|
if (fread(allbuf, 1, 4095, F) == 0 && !feof(F)) {
|
|
SH_DM(pc, DEBUG_ERROR,
|
|
"fread: unable to read the detailed output of '%s': %s\n",
|
|
command, strerror(errno));
|
|
}
|
|
allbuf[4095] = '\0';
|
|
*details = strdup_ordie(allbuf);
|
|
}
|
|
return (kind_pclose(F, command, pc));
|
|
}
|
|
|
|
/* returns null on failure */
|
|
/*@null@*/
|
|
char *backtickExpand(Pop3 pc, const char *path)
|
|
{
|
|
char bigbuffer[1024];
|
|
const char *tickstart;
|
|
const char *tickend;
|
|
bigbuffer[0] = '\0';
|
|
while ((tickstart = strchr(path, '`')) != NULL) {
|
|
char *command;
|
|
char *commandoutput;
|
|
tickend = strchr(tickstart + 1, '`');
|
|
if (tickend == NULL) {
|
|
SH_DM(pc, DEBUG_ERROR, "unbalanced \' in %s\n", path);
|
|
return NULL;
|
|
}
|
|
strncat(bigbuffer, path, tickstart - path);
|
|
command = strdup_ordie(tickstart + 1);
|
|
command[tickend - tickstart - 1] = '\0';
|
|
(void) grabCommandOutput(pc, command, &commandoutput, NULL);
|
|
free(command);
|
|
if (commandoutput != NULL) {
|
|
strcat(bigbuffer, commandoutput);
|
|
free(commandoutput);
|
|
}
|
|
path = tickend + 1;
|
|
}
|
|
/* grab the rest */
|
|
strcat(bigbuffer, path);
|
|
SH_DM(pc, DEBUG_INFO, "expanded to %s\n", bigbuffer);
|
|
return (strdup_ordie(bigbuffer));
|
|
}
|
|
|
|
int shellCmdCheck(Pop3 pc)
|
|
{
|
|
int count_status = 0;
|
|
char *commandOutput;
|
|
|
|
if (pc == NULL)
|
|
return -1;
|
|
SH_DM(pc, DEBUG_INFO, ">Mailbox: '%s'\n", pc->path);
|
|
|
|
/* fetch the first line of input */
|
|
pc->TextStatus[0] = '\0';
|
|
if (pc->u.shell.detail != NULL) {
|
|
free(pc->u.shell.detail);
|
|
pc->u.shell.detail = NULL;
|
|
}
|
|
(void) grabCommandOutput(pc, pc->path, &commandOutput,
|
|
&pc->u.shell.detail);
|
|
if (commandOutput == NULL) {
|
|
return -1;
|
|
}
|
|
SH_DM(pc, DEBUG_INFO, "'%s' returned '%s'\n", pc->path, commandOutput);
|
|
|
|
/* see if it's numeric; the numeric check is somewhat
|
|
useful, as wmbiff renders 4-digit numbers, but not
|
|
4-character strings. */
|
|
if (sscanf(commandOutput, "%d", &(count_status)) == 1) {
|
|
if (strstr(commandOutput, "new")) {
|
|
pc->UnreadMsgs = count_status;
|
|
pc->TotalMsgs = 0;
|
|
} else if (strstr(commandOutput, "old")) {
|
|
pc->UnreadMsgs = 0;
|
|
pc->TotalMsgs = count_status;
|
|
} else {
|
|
/* this default should be configurable. */
|
|
pc->UnreadMsgs = 0;
|
|
pc->TotalMsgs = count_status;
|
|
}
|
|
} else if (strcasestr(commandOutput, "unable")) {
|
|
return -1;
|
|
} else if (sscanf(commandOutput, "%9s\n", pc->TextStatus) == 1) {
|
|
/* validate the string input */
|
|
int i;
|
|
for (i = 0; pc->TextStatus[i] != '\0' && isalnum(pc->TextStatus[i])
|
|
&& i < 10; i++);
|
|
if (pc->TextStatus[i] != '\0') {
|
|
SH_DM(pc, DEBUG_ERROR,
|
|
"wmbiff only supports alphanumeric (isalnum) strings:\n"
|
|
" '%s' is not ok\n", pc->TextStatus);
|
|
/* null terminate it at the first bad char: */
|
|
pc->TextStatus[i] = '\0';
|
|
}
|
|
/* see if we should print as new or not */
|
|
pc->UnreadMsgs = (strstr(commandOutput, "new")) ? 1 : 0;
|
|
pc->TotalMsgs = -1; /* we might alternat numeric /string */
|
|
} else {
|
|
SH_DM(pc, DEBUG_ERROR,
|
|
"'%s' returned something other than an integer message count"
|
|
" or short string.\n", pc->path);
|
|
free(commandOutput);
|
|
return -1;
|
|
}
|
|
|
|
SH_DM(pc, DEBUG_INFO, "from: %s status: %s %d %d\n",
|
|
pc->path, pc->TextStatus, pc->TotalMsgs, pc->UnreadMsgs);
|
|
free(commandOutput);
|
|
return (0);
|
|
}
|
|
|
|
struct msglst *shell_getHeaders( /*@notnull@ */ Pop3 pc)
|
|
{
|
|
struct msglst *message_list = NULL;
|
|
|
|
char *ln = pc->u.shell.detail;
|
|
int i, j;
|
|
if (ln == NULL)
|
|
return NULL;
|
|
|
|
for (j = 0; ln[j] != '\0';) {
|
|
struct msglst *m = malloc(sizeof(struct msglst));
|
|
m->next = message_list;
|
|
m->subj[0] = '\0';
|
|
m->from[0] = '\0';
|
|
|
|
for (i = 0; i < SUBJ_LEN - 1 && ln[j + i] != '\n'; i++) {
|
|
m->subj[i] = ln[j + i];
|
|
}
|
|
m->subj[i] = '\0';
|
|
j += i + 1;
|
|
|
|
message_list = m;
|
|
}
|
|
return message_list;
|
|
}
|
|
|
|
void
|
|
shell_releaseHeaders(Pop3 pc __attribute__ ((unused)), struct msglst *h)
|
|
{
|
|
for (; h != NULL;) {
|
|
struct msglst *n = h->next;
|
|
free(h);
|
|
h = n;
|
|
}
|
|
}
|
|
|
|
int shellCreate( /*@notnull@ */ Pop3 pc, const char *str)
|
|
{
|
|
/* SHELL format: shell:::/path/to/script */
|
|
const char *reserved1, *reserved2, *commandline;
|
|
|
|
pc->TotalMsgs = 0;
|
|
pc->UnreadMsgs = 0;
|
|
pc->OldMsgs = -1;
|
|
pc->OldUnreadMsgs = -1;
|
|
pc->checkMail = shellCmdCheck;
|
|
pc->getHeaders = shell_getHeaders;
|
|
reserved1 = str + 6; /* shell:>:: */
|
|
|
|
assert(strncasecmp("shell:", str, 6) == 0);
|
|
|
|
reserved2 = index(reserved1, ':');
|
|
if (reserved2 == NULL) {
|
|
SH_DM(pc, DEBUG_ERROR, "unable to parse '%s', expecting ':'", str);
|
|
return 0;
|
|
}
|
|
reserved2++; /* shell::>: */
|
|
|
|
commandline = index(reserved2, ':');
|
|
if (commandline == NULL) {
|
|
SH_DM(pc, DEBUG_ERROR,
|
|
"unable to parse '%s', expecting another ':'", str);
|
|
return 0;
|
|
}
|
|
commandline++; /* shell:::> */
|
|
|
|
/* strcpy is not specified to handle overlapping regions */
|
|
SH_DM(pc, DEBUG_INFO, "path= '%s'\n", commandline);
|
|
{
|
|
char *tmp = strdup(commandline);
|
|
if (strlen(tmp) + 1 > BUF_BIG) {
|
|
SH_DM(pc, DEBUG_ERROR, "commandline '%s' is too long.\n",
|
|
commandline);
|
|
memset(pc->path, 0, BUF_BIG);
|
|
} else {
|
|
strcpy(pc->path, tmp);
|
|
}
|
|
free(tmp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* vim:set ts=4: */
|
|
/*
|
|
* Local Variables:
|
|
* tab-width: 4
|
|
* c-indent-level: 4
|
|
* c-basic-offset: 4
|
|
* End:
|
|
*/
|