|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T p
Length: 21724 (0x54dc)
Types: TextFile
Names: »pipe.c«
└─⟦9ae75bfbd⟧ Bits:30007242 EUUGD3: Starter Kit
└─⟦2fafebccf⟧ »EurOpenD3/mail/smail3.1.19.tar.Z«
└─⟦bcd2bc73f⟧
└─⟦this⟧ »src/transports/pipe.c«
/* @(#)pipe.c 3.30 12/8/88 20:18:05 */
/*
* 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();
\f
/*
* 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;
}
\f
/*
* 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(p) + 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;
}
}
}
}