/* $Cambridge: hermes/src/prayer/accountd/process.c,v 1.3 2004/10/01 13:38:29 dpc22 Exp $ */
/************************************************
* Prayer - a Webmail Interface *
************************************************/
/* Copyright (c) University of Cambridge 2000 - 2002 */
/* See the file NOTICE for conditions of use and distribution. */
#include "accountd.h"
/* Class for communicating with subsiduary process using Pseudo Terminal */
/* process_clear() *******************************************************
*
* Clear process state
************************************************************************/
void process_clear(struct process *process)
{
process->childpid = 0;
process->stream = NIL;
}
/* ====================================================================== */
/* process_makeenv() *****************************************************
*
* Utility routine for setting up environment in child process.
************************************************************************/
static
char *process_makeenv(char *key, char *value)
{
char *result;
if ((result = malloc(strlen(key) + strlen(value) + 2)) == NIL)
log_fatal("Out of memory");
sprintf(result, "%s=%s", key, value);
return (result);
}
/* process_exec() ********************************************************
*
* exec new process.
************************************************************************/
BOOL process_exec(char *cmdline)
{
struct passwd *pwd;
char *path, *s;
int argc;
char **argv;
char *env[6];
/* Set up environment array for child process */
if ((pwd = getpwuid(getuid())) == NIL)
log_fatal("getpwuid() failed");
env[0] = process_makeenv("HOME", pwd->pw_dir);
env[1] = process_makeenv("LOGNAME", pwd->pw_name);
env[2] = process_makeenv("USER", pwd->pw_name);
env[3] = process_makeenv("PATH", "/bin:/usr/bin:/opt/local/bin");
env[4] = process_makeenv("SHELL", "/bin/false");
env[5] = NIL;
/* Work out correct size for the argv array */
argc = 0;
s = cmdline;
while (*s) {
if ((*s == ' ') || (*s == '\t')) {
argc++;
while ((*s == ' ') || (*s == '\t'))
s++;
} else
s++;
}
/* Allocate the argv array */
if ((argv = malloc((argc + 1) * sizeof(char *))) == NIL) {
log_fatal("process_exec(): Out of memory");
/* NOTREACHED */
exit(1);
}
/* Set up argv array */
argv[0] = path = strdup(cmdline);
argc = 0;
s = path;
while (*s) {
if ((*s == ' ') || (*s == '\t')) {
*s++ = '\0';
while ((*s == ' ') || (*s == '\t'))
s++;
if (*s)
argv[++argc] = s;
} else
s++;
}
argv[argc + 1] = NIL;
/* Caculate argv[0] if full pathname was provided */
if (strrchr(argv[0], '/'))
argv[0] = strrchr(path, '/') + 1;
execve(path, argv, env);
log_fatal("Failed to execve() passwd program");
/* NOTREACHED */
exit(1);
}
/* ====================================================================== */
/* process_start() *******************************************************
*
* Start up subsiduary process for communication
* process: Wrapper structure for state
* cmdline: Program that we are about to run
* use_pty: Run program inside a Pseudo terminal
* argv: argv array to pass to child.
*
* Returns: T if subprocess started successfully. NIL otherwise.
************************************************************************/
BOOL
process_start(struct process *process, char *cmdline, BOOL use_pty,
unsigned long timeout)
{
int childpid;
int masterfd;
if (use_pty) {
if (!os_run_pty(cmdline, &masterfd, &childpid)) {
log_misc("Failed to open pseudoterminal");
return (NIL);
}
} else {
if (!os_run(cmdline, &masterfd, &childpid)) {
log_misc("Failed to open pseudoterminal");
return (NIL);
}
}
process->use_pty = use_pty;
process->childpid = childpid;
if (!(process->stream = iostream_create(NIL, masterfd, 0)))
return (NIL);
iostream_set_timeout(process->stream, timeout);
return (T);
}
/* ====================================================================== */
/* process_stop() *********************************************************
*
* Stop a running process: shuts down iostream connection and waits for
* process to finish.
*************************************************************************/
BOOL process_stop(struct process * process)
{
int status;
if (process->childpid == 0)
return (NIL);
if (process->stream)
iostream_close(process->stream);
waitpid(process->childpid, &status, 0);
return (T);
}
/* ====================================================================== */
/* process_expect_string() ***********************************************
*
* Match string on following line of output from child process.
*
* Returns:
* string matched sucessfully => NIL
* Otherwise => Line from child as error condition
************************************************************************/
static char *process_expect_string(struct process *process, char *string,
char *warning)
{
unsigned long len = strlen(string);
unsigned long count = 0;
struct iostream *stream = process->stream;
char c;
static char buffer[MAXLENGTH];
buffer[0] = '\0';
while ((c = iogetc(stream)) != EOF) {
if (c == '\r')
continue;
if (count == MAXLENGTH)
return ("Line from child process too long");
buffer[count++] = c;
if ((count >= len)
&& !strncmp(buffer + (count - len), string, len))
return (NIL);
if (c == '\n') {
if (warning && !strncmp(buffer, warning, strlen(warning))) {
count = 0; /* Ignore warning lines */
buffer[0] = '\0';
} else {
buffer[count - 1] = '\0'; /* Return line from child as error */
return (buffer);
}
}
}
return ("End of file or timeout waiting for input");
}
/* process_read_line() ***************************************************
*
* Fetch a line from child process
* process:
* buf: Target buffer (if defined)
* blen: Length of target buffer (if defined)
*
* Returns: T => got line. NIL => EOF.
************************************************************************/
BOOL
process_read_line(struct process * process, char *buf, unsigned long blen)
{
struct iostream *stream = process->stream;
char c;
unsigned long i = 0;
if (blen > 0)
blen--; /* Leave space for trailing '\0' */
while ((c = iogetc(stream)) != EOF) {
if (c == '\r')
continue;
if (c == '\n') {
if (buf && (i < blen))
buf[i] = '\0';
return (T);
}
if (buf && (i < blen))
buf[i++] = c;
}
return (NIL);
}
/* process_get_token() **************************************************
*
* Isolate next token in string
* sp: Ptr to current string location
* (updated to point to following token)
*
* Returns: Ptr to next token, NIL if none.
***********************************************************************/
static char *process_get_token(char **sp)
{
char *s = *sp, *result;
if (!(s = string_next_token(sp)))
return (NIL);
if (*s == '"') {
/* Deal with quoted strings */
result = s + 1;
s += 2;
while (*s && (*s != '"'))
s++;
if (*s == '\0')
return (NIL);
*s++ = '\0';
*sp = s;
return (result);
}
/* Record position of this token */
result = s;
/* Find next whitespace character or end of string */
while ((*s) && (*s != ' ') && (*s != '\t'))
s++;
/* Tie off the string unless \0 already reached */
if (*s) {
*s++ = '\0';
while ((*s == ' ') || (*s == '\t'))
s++;
}
/* Record position of first non-whitespace character for next caller */
*sp = s;
return (result);
}
/* ====================================================================== */
/* process_run_script() **************************************************
*
* Run script against process
*
* Returns:
* NIL => script matched successfully
* "" => unexpected EOF
* Other => Last line of output from child as an error condition
************************************************************************/
char *process_run_script(struct process *process,
struct pool *pool, struct assoc *h,
char *script, char *result,
unsigned long result_length)
{
char *type, *value, *ret;
char *warning = NIL;
struct iostream *stream = process->stream;
while (*script) {
if (!(type = string_get_token(&script)))
return (NIL);
if (!strcasecmp(type, "readline")) {
if (!process_read_line(process, NIL, 0L))
return ("");
continue;
}
if (!strcasecmp(type, "result")) {
if (!process_read_line(process, result, result_length))
return ("");
return (NIL);
}
if (!(value = process_get_token(&script)))
return (NIL);
value = string_expand(pool, h, value);
if (!strcasecmp(type, "warning")) {
warning = value;
continue;
}
if (!strcasecmp(type, "expect")) {
if ((ret = process_expect_string(process, value, warning)))
return (ret);
continue;
}
if (!strcasecmp(type, "sendline")) {
ioprintf(stream, "%s\n", value);
ioflush(stream);
if (process->use_pty && !process_read_line(process, NIL, 0L))
return ("");
continue;
}
if (!strcasecmp(type, "send")) {
ioputs(stream, value);
ioflush(stream);
continue;
}
log_panic("Unexpected command in script: %s %s", type, value);
return ("Internal server error");
}
/* Script finished sucessfully */
return (NIL);
}
syntax highlighted by Code2HTML, v. 0.9.1