/* 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
 *
 */

#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:
 */
