This is pipe.c in view mode; [Download] [Up]
/* @(#)src/transports/pipe.c 1.3 18 Feb 1991 16:03:16 */ /* * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll * * See the file COPYING, distributed with smail, for restriction * and warranty information. */ /* * pipe.c: * deliver mail over a pipe to a program exec'd in a child * process. Be very careful to keep things secure by setting * the uid and gid in the child process to something appropriate. * * Specifications for the pipe transport driver: * * private attribute data: * cmd (string): a string which will be parsed for vectors * to pass to execv. Some variable expansion will be done, * using the build_cmd_line function. * user (name): the name of the user to setuid to in the child * process. If not specified then the addr structure is * searched for a uid. If none is found there, use the * nobody_uid. * group (name): the name of the group to setgid to in the child * process. The algorithm used for uid is used here as well. * umask (int): umask for child process. * * private attribute flags: * pipe_as_user: if set, become an appropriate user in the * child process. Otherwise, become the nobody user. * ignore_status: if set, ignore the exit status of the * child process. Otherwise complain if it is non-zero. * pipe_as_sender: become the sender, based on the saved * sender's real uid. * log_output: log the stdout and stderr of the child process * to the message log file. This can then be returned back * to the sender of the message. * log_output: log the stdout and stderr of the child process * to the per-message log file. * ignore_write_errors: if set, ignore write errors. Otherwise * complain about them. * defer_child_errors: generally, only child failures from the * signal SIGTERM are retried. If this is set, then retries * are performed if the exit code is non-zero, or if the * write failed on the pipe. */ #include <stdio.h> #include <pwd.h> #include <grp.h> #include <signal.h> #include "defs.h" #include "../smail.h" #include "../smailconf.h" #include "../parse.h" #include "../addr.h" #include "../log.h" #include "../spool.h" #include "../child.h" #include "../transport.h" #include "../exitcodes.h" #include "pipe.h" #ifndef DEPEND # include "../extern.h" # include "../debug.h" # include "../error.h" #endif /* functions local to this file */ static char **get_pipe_env(); static void get_pipe_ugid(); /* * tdp_pipe - pipe transport driver */ void tpd_pipe(addr, succeed, defer, fail) struct addr *addr; /* recipient addresses for transport */ struct addr **succeed; /* successful deliveries */ struct addr **defer; /* defer until a later queue run */ struct addr **fail; /* failed deliveries */ { register struct pipe_private *priv; /* pipe driver's private data */ struct transport *tp = addr->transport; char **argv; /* args to pass to open_pipe */ FILE *child; /* child process' stdin */ int status; /* exit status from child process */ int pid; /* pid of child process */ char **pipe_env; /* environment to give to children */ int uid; /* uid for child process */ int gid; /* gid for child process */ int errfd; /* child process output file */ char *error; /* error from build_cmd_line() */ struct error *write_error = NULL; /* pipe write error message */ int save_umask; /* saved value from umask() */ DEBUG1(DBG_DRIVER_HI, "tpd_pipe called: addr = %s\n", addr->in_addr); priv = (struct pipe_private *)tp->private; if (priv->cmd == NULL) { /* * ERR_137 - no cmd attribute for pipe * * DESCRIPTION * No cmd attribute was specified for a pipe transport. This * attribute is required. * * ACTIONS * The message is deferred with a configuration error. * * RESOLUTION * Correct the entry in the transport file. */ register struct error *er; er = note_error(ERR_CONFERR|ERR_137, xprintf("transport %s: no cmd attribute for pipe", tp->name)); insert_addr_list(addr, defer, er); return; } /* get the argument vectors for the execv call */ argv = build_cmd_line(priv->cmd, addr, "", &error); if (argv == NULL) { /* * ERR_138 - error in cmd attribute * * DESCRIPTION * build_cmd_line() encountered an error while parsing the cmd * attribute. The specific error was returned in `error'. * * ACTIONS * Defer the message with a configuration error. * * RESOLUTION * Correct the entry in the transport file to have a valid, * expandable cmd attribute. */ struct error *er; er = note_error(ERR_CONFERR|ERR_138, xprintf("transport %s: error in cmd attribute: %s", tp->name, error)); insert_addr_list(addr, defer, er); return; } if (argv[0][0] != '/') { /* * ERR_139 - absolute path for cmd required * * DESCRIPTION * The first vector from the cmd attribute must be an absolute * pathname. Search paths are not used and relative paths do * not make sense in smail. * * ACTIONS * Defer the message with a configuration error. * * RESOLUTION * Correct the entry in the transport file to ensure that the * first part of the cmd attribute represents an abolute * pathname. */ register struct error *er; er = note_error(ERR_CONFERR|ERR_139, xprintf("transport %s: absolute path for cmd required", tp->name)); insert_addr_list(addr, defer, er); return; } /* get the environment for the child process */ pipe_env = get_pipe_env(tp, addr); /* get the user id and group id */ get_pipe_ugid(tp, addr, &uid, &gid); /* * if we are logging output, tie the output to the per-message log file */ if (tp->flags & PIPE_LOG_OUTPUT) { if (! msg_logfile) { open_msg_log(); } fflush(msg_logfile); errfd = fileno(msg_logfile); } else { errfd = -1; } if (dont_deliver) { /* succeed everything */ insert_addr_list(addr, succeed, (struct error *)NULL); return; } /* * create the child process with the set environment. * allow writes to process' stdin, redirect stdout/stderr to errfd, * or to DEV_NULL. */ save_umask = umask(priv->umask); pid = open_child(argv, pipe_env, &child, (FILE **)NULL, errfd, CHILD_DEVNULL, uid, gid); (void) umask(save_umask); if (pid < 0) { /* * ERR_140 - could not create process * * DESCRIPTION * open_child() failed to create a child process. This is most * likely a result of fork() failing due to a lack of available * process slots, or pipe() failing due to a lack of available * file descriptors. The specific error should be available in * errno. * * ACTIONS * It will probably be possible for fork() or pipe() to succeed * sometime in the future, unless a configuration error has * caused too many file descriptors to remain open, which will * require a configuration change. Thus, defer the input * addresses. * * RESOLUTION * Hopefully delivery will exceed on a later attempt. */ register struct error *er; er = note_error(ERR_140, xprintf("transport %s: could not create process: %s", tp->name, strerrno())); insert_addr_list(addr, defer, er); return; } /* write out the message */ if (write_message(child, addr->transport, addr) || fflush(child) == EOF) { if (tp->flags & PIPE_IGNORE_WRERRS) { /* * this is a warning only, but log for each input address. * Generally, ignore_write_errors is only turned on for delivery * to shell-command addresses, so this will ususally only * generate one entry in the system log. */ struct addr *cur; for (cur = addr; cur; cur = cur->succ) { write_log(LOG_SYS, "note: %s ... transport %s: write on pipe failed: %s", addr->in_addr, tp->name, strerrno()); } } else { /* * ERR_141 - write on pipe failed * * DESCRIPTION * A write to a process created with open_child() failed, * and ignore_write_errors is not set in the transport. * This is generally caused by a process exiting before * reading up to an EOF on standard input. Sometimes shell * commands run directly will have side effects but will * not actually read its stdin. For example, a forward * file containing: * * "|mailx -s \"gone fishing\" $SENDER < $HOME/.fishing" * * will send a message to the originator of a message * stating that the recipient is on vacation. However, as * it does not actually read the message, a write error * will occur on the pipe. * * The above problem can be dealt with by setting the * ignore_write_errors attribute for the "pipe" transport, * or be changing the forward file to: * * "|cat>/dev/null; * mailx -s \"gone fishing\" $SENDER < $HOME/.fishing" * * which will read and ignore the standard input. * * The last possibility is that a command failed before * reading all of stdin, or the exec() failed in * open_child(). * * ACTIONS * Fail the input addresses and send an error to the * address owner or to the postmaster. However, if * close_child() returns a non-zero exit status, this error * has precedence over write-errors, as it represents a * more important error. The exception is that if * ignore_status is set, then the write-error message is * always returned. * * If defer_child_errors is set, then defer rather than * fail. * * RESOLUTION * If the problem is a shell command, either scold the user * who had the offending forward file, or set * ignore_write_errors in the transport file entry. */ write_error = note_error(ERR_NPOWNER|ERR_141, xprintf("transport %s: write error on pipe: %s", tp->name, strerrno())); } } status = close_child(child, (FILE *)NULL, pid); if (status == EOF && (tp->flags & PIPE_IGNORE_STATUS) == 0) { /* * ERR_142 - failed to reap child process * * DESCRIPTION * For some reason, close_pipe() failed to find the child * process. This should never happen. * * ACTIONS * Fail the input addresses and Notify the postmaster of the * error. Note: write errors have precedence over this error. * * RESOLUTION * This is most likely a bug in either smail or the UNIX * kernel. */ if (write_error == NULL) { register struct error *er; er = note_error(ERR_NPOSTMAST|ERR_142, xprintf("transport %s: failed to reap child process: %s", tp->name, strerrno())); insert_addr_list(addr, fail, er); return; } } else if (status != 0) { if (status & 0xff) { /* * ERR_143 - process killed by signal * * DESCRIPTION * The exit status returned by close_child() revealed that * the child process was killed by a signal. This could be * due to the machine being brought down, if the signal is * SIGTERM. Other signals may represent genuine problems. * * ACTIONS * If the child was killed with SIGTERM, defer the input * addresses, otherwise fail the addresses and notify the * postmaster. This is probably not a problem that address * owners should have to deal with. If ignore_status is * set, then the problem is ignored (except for SIGTERM). * * RESOLUTION * Time to use your skill in tracking down unusual * problems, exept in the case of SIGTERM. */ char *error = xprintf("transport %s: process killed by signal %d", tp->name, status & 0x7f); if ((status & 0x7f) == SIGTERM) { insert_addr_list(addr, defer, note_error(ERR_143, error)); return; } if (tp->flags & PIPE_IGNORE_STATUS) { /* * log errors in the system log file, but don't do anything * about them, if ignore_status is set */ struct addr *cur; for (cur = addr; cur; cur = cur->succ) { write_log(LOG_SYS, "note: %s ... %s", cur->in_addr, error); } } else { insert_addr_list(addr, (tp->flags&PIPE_DEFER_ERRORS)? defer: fail, note_error(ERR_NPOSTMAST|ERR_143, error)); return; } } else { /* * ERR_144 - process returned non-zero status * * DESCRIPTION * close_child() reaped a non-zero exit status from the * child process. This could be okay or it could be bad, * it is difficult to tell. * * ACTIONS * If the ignore_status attribute is set, then this * situation is ignored, except that it is logged in the * system file. Otherwise, the addresses are failed and a * message is returned to the address owner or the * postmaster. * * RESOLUTION * The program run by the transport should be checked to * determine what the various exit codes mean, and if the * status is significant. If the exit status is not * significant, then ignore_status should be set for the * transport. */ char *error = xprintf("transport %s: child returned status %s (%d)", tp->name, strsysexit(status>>8), status>>8); if (tp->flags & PIPE_IGNORE_STATUS) { /* * log errors in the system log file, but don't do anything * about them, if ignore_status is set */ struct addr *cur; for (cur = addr; cur; cur = cur->succ) { write_log(LOG_SYS, "note: %s ... %s", cur->in_addr, error); } } else { insert_addr_list(addr, (tp->flags&PIPE_DEFER_ERRORS)? defer: fail, note_error(ERR_NPOWNER|ERR_144, error)); return; } } } if (write_error) { /* write_error, but no high-precedence error, was found, fail */ insert_addr_list(addr, (tp->flags&PIPE_DEFER_ERRORS)? defer: fail, write_error); } else { /* everything went okay, link into the succeed list */ insert_addr_list(addr, succeed, (struct error *)NULL); } return; } /* * tpb_pipe - read the configuration file attributes */ char * tpb_pipe(tp, attrs) struct transport *tp; /* transport entry being defined */ struct attribute *attrs; /* list of per-driver attributes */ { char *error; static struct attr_table pipe_attributes[] = { { "cmd", t_string, NULL, NULL, OFFSET(pipe_private, cmd) }, { "user", t_string, NULL, NULL, OFFSET(pipe_private, user) }, { "group", t_string, NULL, NULL, OFFSET(pipe_private, group) }, { "umask", t_int, NULL, NULL, OFFSET(pipe_private, umask) }, { "pipe_as_user", t_boolean, NULL, NULL, PIPE_AS_USER }, { "pipe_as_sender", t_boolean, NULL, NULL, PIPE_AS_SENDER }, { "ignore_status", t_boolean, NULL, NULL, PIPE_IGNORE_STATUS }, { "log_output", t_boolean, NULL, NULL, PIPE_LOG_OUTPUT }, { "parent_env", t_boolean, NULL, NULL, PIPE_PARENT_ENV }, { "ignore_write_errors", t_boolean, NULL, NULL, PIPE_IGNORE_WRERRS }, { "defer_child_errors", t_boolean, NULL, NULL, PIPE_DEFER_ERRORS }, }; static struct attr_table *end_pipe_attributes = ENDTABLE(pipe_attributes); static struct pipe_private pipe_template = { NULL, /* cmd */ NULL, /* user */ NULL, /* group */ 0, /* umask */ }; struct pipe_private *priv; /* new pipe_private structure */ /* copy the template private data */ priv = (struct pipe_private *)xmalloc(sizeof(*priv)); (void) memcpy((char *)priv, (char *)&pipe_template, sizeof(*priv)); /* set default flags */ tp->flags |= PIPE_AS_USER; tp->private = (char *)priv; /* fill in the attributes of the private data */ error = fill_attributes((char *)priv, attrs, &tp->flags, pipe_attributes, end_pipe_attributes); if (error) { return error; } return NULL; } /* * get_pipe_env - return an environment suitable for the child process */ static char ** get_pipe_env(tp, addr) struct transport *tp; /* transport being used */ struct addr *addr; /* addrs being delivered to */ { static char *pipe_env[50] = { /* lots of space for variables */ "SHELL=/bin/sh", /* force shell */ }; static int inited = FALSE; /* true if pipe_env is set up */ char **next_env = pipe_env + 1; /* start of available pipe_env slots */ static char **per_address_env; /* start of per-address environment */ register char *p; char *p2; struct addr *cur; /* temp */ if (! inited) { extern char *getenv(); inited = TRUE; /* load environment variables which varry per-message, not per addr */ #ifdef SECURE_PATH *next_env++ = p = xmalloc(sizeof(SECURE_PATH) + sizeof("PATH=") - 1); (void) sprintf(p, "PATH=%s", SECURE_PATH); #else *next_env++ = "PATH=/bin:/usr/bin"; #endif *next_env++ = p = xmalloc(strlen(sender) + sizeof("SENDER=")); (void) sprintf(p, "SENDER=%s", sender); p2 = getenv("TZ"); if (p2) { *next_env++ = p = xmalloc(strlen(p2) + sizeof("TZ=")); (void) sprintf(p, "TZ=%s", p2); } *next_env++ = p = xmalloc(strlen(message_id) + sizeof("MESSAGE_ID=")); (void) sprintf(p, "MESSAGE_ID=%s", message_id); *next_env++ = p = xmalloc(sizeof("GRADE=C")); (void) sprintf(p, "GRADE=%c", msg_grade); *next_env++ = p = xmalloc(strlen(uucp_name) + sizeof("UUCP_NAME=")); (void) sprintf(p, "UUCP_NAME=%s", uucp_name); *next_env++ = p = xmalloc(strlen(primary_name) + sizeof("PRIMARY_NAME=")); (void) sprintf(p, "PRIMARY_NAME=%s", primary_name); *next_env++ = p = xmalloc(strlen(visible_name) + sizeof("VISIBLE_NAME=")); (void) sprintf(p, "VISIBLE_NAME=%s", visible_name); *next_env++ = p = xmalloc(strlen(spool_fn) + sizeof("BASENAME=")); (void) sprintf(p, "BASENAME=%s", spool_fn); *next_env++ = p = xmalloc(strlen(spool_dir) + strlen(input_spool_fn) + sizeof("SPOOL_FILE=/")); (void) sprintf(p, "SPOOL_FILE=%s/%s", spool_dir, input_spool_fn); per_address_env = next_env; } else { /* free up per-invocation storage used previously */ char **pp; for (pp = per_address_env; *pp; pp++) { xfree(*pp); *pp = NULL; } } /* load environment variables which change per addr, get from first addr */ next_env = per_address_env; if ((tp->flags & PIPE_PARENT_ENV) && addr->parent) { /* use parent addr structure for information stuffed in environment */ cur = addr->parent; if (cur->remainder) { p = xmalloc(strlen(cur->remainder) + sizeof("ADDR=")); *next_env++ = p; (void) sprintf(p, "ADDR=%s", cur->remainder); } if (cur->target && cur->target[0]) { p = xmalloc(strlen(cur->target) + sizeof("HOST=")); *next_env++ = p; (void) sprintf(p, "HOST=%s", cur->target); } p2 = cur->home? cur->home: "/"; *next_env++ = p = xmalloc(strlen(p2) + sizeof("HOME=")); (void) sprintf(p, "HOME=%s", p2); } else { if (addr->next_addr) { p = xmalloc(strlen(addr->next_addr) + sizeof("ADDR=")); *next_env++ = p; (void) sprintf(p, "ADDR=%s", addr->next_addr); } if (addr->next_host) { p = xmalloc(strlen(addr->next_host) + sizeof("HOST=")); *next_env++ = p; (void) sprintf(p, "HOST=%s", addr->next_host); } p2 = addr->home? addr->home: "/"; *next_env++ = p = xmalloc(strlen(p2) + sizeof("HOME=")); (void) sprintf(p, "HOME=%s", p2); } /* is there a suitable username to go into the environment */ for (cur = addr; cur && ! (cur->flags & ADDR_ISUSER); cur = cur->parent) ; if (cur) { /* yes */ register char *user = cur->remainder; register int len_user = strlen(user); *next_env++ = p = xmalloc(len_user + sizeof("USER=")); (void) sprintf(p, "USER=%s", user); *next_env++ = p = xmalloc(len_user + sizeof("LOGNAME=")); (void) sprintf(p, "LOGNAME=%s", user); } /* end of environment */ *next_env = NULL; return pipe_env; } /* * get_pipe_ugid - return the uid and gid to use for the child process */ static void get_pipe_ugid(tp, addr, uid, gid) struct transport *tp; /* associated transport structure */ struct addr *addr; /* associated addr structures */ int *uid; /* store uid here */ int *gid; /* store gid here */ { struct pipe_private *priv = (struct pipe_private *)tp->private; /* * determine the uid to use for delivery */ if (priv->user == NULL) { if ((tp->flags & PIPE_AS_USER) && addr->uid != BOGUS_USER) { *uid = addr->uid; } else if ((tp->flags & PIPE_AS_SENDER)) { *uid = real_uid; } else { *uid = nobody_uid; } } else { struct passwd *pw = getpwbyname(priv->user); if (pw == NULL) { write_log(LOG_PANIC, "transport %s: warning: user %s unknown, using nobody", tp->name, priv->user); priv->user = NULL; *uid = nobody_uid; } else { *uid = pw->pw_uid; *gid = pw->pw_gid; } } /* determine the gid for use for delivery */ if (priv->group) { struct group *gr = getgrbyname(priv->group); if (gr == NULL) { write_log(LOG_PANIC, "transport %s: warning: group %s unknown, ignored", tp->name, priv->group); priv->group = NULL; } else { *gid = gr->gr_gid; } } if (priv->group == NULL) { if (priv->user == NULL) { if ((tp->flags & PIPE_AS_USER) && addr->gid != BOGUS_GROUP) { *gid = addr->gid; } else { *gid = nobody_gid; } } } }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.