root/usr/src/cmd/lp/cmd/lpsched/exec.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 * Copyright 2026 OmniOS Community Edition (OmniOSce) Association.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

#include <pwd.h>
#include <zone.h>
#include <dial.h>

#include <stdlib.h>
#include "limits.h"
#include "stdarg.h"
#include "wait.h"
#include "dial.h"
#include "lpsched.h"
#include <syslog.h>
#include "tsol/label.h"

#define Done(EC,ERRNO)  done(((EC) << 8),ERRNO)

#define STRLCAT(dst, src, size) \
        if (strlcat((dst), (src), (size)) >= (size)) { \
                errno = EINVAL; \
                return (-1); \
        }

static MESG *           ChildMd;

static int              ChildPid;
static int              WaitedChildPid;
static int              do_undial;

static char             argbuf[ARG_MAX];

static long             key;

static void             sigtrap ( int );
static void             done ( int , int );
static void             cool_heels ( void );
static void             addenv (char ***envp, char * , char * );
static void             trap_fault_signals ( void );
static void             ignore_fault_signals ( void );
static void             child_mallocfail ( void );
static void             Fork2 ( void );

static int              Fork1 ( EXEC * );

static void
relock(void)
{
        struct flock            l;

        l.l_type = F_WRLCK;
        l.l_whence = 1;
        l.l_start = 0;
        l.l_len = 0;
        (void)Fcntl (lock_fd, F_SETLK, &l);
        return;
}

static char *_exec_name(int type)
{
        static char *_names[] = {
        "", "EX_INTERF", "EX_SLOWF", "EX_ALERT", "EX_FALERT", "EX_PALERT",
        "EX_NOTIFY", "EX_FAULT_MESSAGE", "EX_FORM_MESSAGE", NULL };

        if ((type < 0) || (type > EX_FORM_MESSAGE))
                return ("BAD_EXEC_TYPE");
        else
                return (_names[type]);
}

/*
 * This function replaces characters in a string that might be used
 * to exploit a security hole.  Replace command seperators (`, &, ;, |, ^),
 * output redirection (>, |), variable expansion ($), and character
 * escape (\).
 *
 * Bugid 4141687
 * Add ( ) < * ? [
 * Bugid 4139071
 * Remove \
 */
void clean_string(char *ptr)
{
        char *cp;
        wchar_t wc;
        size_t len;

        for (cp = ptr; *cp != '\0'; ) {
                if ((len = mbtowc(&wc, cp, MB_CUR_MAX)) == -1) {
                        cp++;
                        continue;
                }

                if (len == 1 &&
                    ((wc == L'`') || (wc == L'&') || (wc == L';') ||
                    (wc == L'|') || (wc == L'>') || (wc == L'^') ||
                    (wc == L'$') || (wc == L'(') || (wc == L')') ||
                    (wc == L'<') || (wc == L'*') || (wc == L'?') ||
                    (wc == L'[')))
                        *cp = '_';
                cp += len;
        }
}

enum trust {TRUSTED, UNTRUSTED};

static char *arg_string(enum trust type, char *fmt, ...) __PRINTFLIKE(2);

/* PRINTFLIKE2 */
static char *
arg_string(enum trust type, char *fmt, ...)
{
        char buf[BUFSIZ];
        va_list args;

        va_start(args, fmt);
        (void) vsnprintf(buf, sizeof(buf), fmt, args);
        va_end(args);

        /*
         * If the string contains data from an untrusted origin (user supplied),
         * clean it up in case one of our progeny is a shell script and isn't
         * careful about checking its input.
         */
        if (type == UNTRUSTED)
                clean_string(buf);

        return (strdup(buf));
}

static char time_buf[50];

/**
 ** exec() - FORK AND EXEC CHILD PROCESS
 **/

/*VARARGS1*/
int
exec(int type, ...)
{
        va_list                 args;

        int                     i;
        int                     procuid;
        int                     procgid;
        int                     ret;
        int                     fr_flg;

        char                    *cp;
        char                    *infile;
        char                    *outfile;
        char                    *errfile;
        char                    *sep;

        char                    **listp;
        char                    **file_list;
        char                    *printerName;
        char                    *printerNameToShow;
        static char             nameBuf[100];
        char                    keyBuf[sizeof (BIGGEST_NUMBER_S)];
        char                    *clean_title;

        PSTATUS                 *printer;

        RSTATUS                 *request;

        FSTATUS                 *form;

        EXEC                    *ep;

        PWSTATUS                *pwheel;
        time_t                  now;
        struct passwd           *pwp;
#ifdef LP_USE_PAPI_ATTR
        struct stat             tmpBuf;
        char                    tmpName[BUFSIZ];
        char                    *path = NULL;
#endif
        char *av[ARG_MAX];
        char **envp = NULL;
        int ac = 0;
        char    *mail_zonename = NULL;
        char    *slabel = NULL;
        int     setid = 1;
        char    *ridno = NULL, *tmprid = NULL;

        syslog(LOG_DEBUG, "exec(%s)", _exec_name(type));

        memset(av, 0, sizeof (*av));

        va_start (args, type);

        switch (type) {

        case EX_INTERF:
                printer = va_arg(args, PSTATUS *);
                request = printer->request;
                ep = printer->exec;
                break;

        case EX_FAULT_MESSAGE:
                printer = va_arg(args, PSTATUS *);
                request = va_arg(args, RSTATUS *);
                if (! ( printer->status & (PS_FORM_FAULT | PS_SHOW_FAULT))) {
                        return(0);
                }
                ep = printer->fault_exec;
                printerName = (printer->printer && printer->printer->name
                                  ? printer->printer->name : "??");
                        snprintf(nameBuf, sizeof (nameBuf),
                                "%s (on %s)\n", printerName, Local_System);

                printerNameToShow = nameBuf;

                (void) time(&now);
                (void) strftime(time_buf, sizeof (time_buf),
                        NULL, localtime(&now));
                break;

        case EX_SLOWF:
                request = va_arg(args, RSTATUS *);
                ep = request->exec;
                break;

        case EX_NOTIFY:
                request = va_arg(args, RSTATUS *);
                if (request->request->actions & ACT_NOTIFY) {
                        errno = EINVAL;
                        return (-1);
                }
                ep = request->exec;
                break;

        case EX_ALERT:
                printer = va_arg(args, PSTATUS *);
                if (!(printer->printer->fault_alert.shcmd)) {
                        errno = EINVAL;
                        return(-1);
                }
                ep = printer->alert->exec;
                break;

        case EX_PALERT:
                pwheel = va_arg(args, PWSTATUS *);
                ep = pwheel->alert->exec;
                break;

        case EX_FORM_MESSAGE:
                (void) time(&now);
                (void) strftime(time_buf, sizeof (time_buf),
                        NULL, localtime(&now));

                /*FALLTHRU*/
        case EX_FALERT:
                form = va_arg(args, FSTATUS *);
                ep = form->alert->exec;
                break;

        default:
                errno = EINVAL;
                return(-1);

        }
        va_end (args);

        if (!ep || (ep->pid > 0)) {
                errno = EBUSY;
                return(-1);
        }

        ep->flags = 0;

        key = ep->key = getkey();

        switch ((ep->pid = Fork1(ep))) {

        case -1:
                relock ();
                return(-1);

        case 0:
                /*
                 * We want to be able to tell our parent how we died.
                 */
                lp_alloc_fail_handler = child_mallocfail;
                break;

        default:
                switch(type) {

                case EX_INTERF:
                        request->request->outcome |= RS_PRINTING;
                        break;

                case EX_NOTIFY:
                        request->request->outcome |= RS_NOTIFYING;
                        break;

                case EX_SLOWF:
                        request->request->outcome |= RS_FILTERING;
                        request->request->outcome &= ~RS_REFILTER;
                        break;

                }
                return(0);

        }

        for (i = 0; i < NSIG; i++)
                (void)signal (i, SIG_DFL);
        (void)signal (SIGALRM, SIG_IGN);
        (void)signal (SIGTERM, sigtrap);

        closelog();
        for (i = 0; i < OpenMax; i++)
                if (i != ChildMd->writefd)
                        Close (i);
        openlog("lpsched", LOG_PID|LOG_NDELAY|LOG_NOWAIT, LOG_LPR);

        setpgrp();

        /* Set a default path */
        addenv (&envp, "PATH", "/usr/lib/lp/bin:/usr/bin:/bin:/usr/sbin:/sbin");
        /* copy locale related variables */
        addenv (&envp, "TZ", getenv("TZ"));
        addenv (&envp, "LANG", getenv("LANG"));
        addenv (&envp, "LC_ALL", getenv("LC_ALL"));
        addenv (&envp, "LC_COLLATE", getenv("LC_COLLATE"));
        addenv (&envp, "LC_CTYPE", getenv("LC_CTYPE"));
        addenv (&envp, "LC_MESSAGES", getenv("LC_MESSAGES"));
        addenv (&envp, "LC_MONETARY", getenv("LC_MONETARY"));
        addenv (&envp, "LC_NUMERIC", getenv("LC_NUMERIC"));
        addenv (&envp, "LC_TIME", getenv("LC_TIME"));
        sprintf (keyBuf, "%ld", key);
        addenv (&envp, "SPOOLER_KEY", keyBuf);

#if     defined(DEBUG)
        addenv (&envp, "LPDEBUG", (debug? "1" : "0"));
#endif

        /*
         * Open the standard input, standard output, and standard error.
         */
        switch (type) {

        case EX_SLOWF:
        case EX_INTERF:
                /*
                 * stdin:  /dev/null
                 * stdout: /dev/null (EX_SLOWF), printer port (EX_INTERF)
                 * stderr: req#
                 */
                infile = 0;
                outfile = 0;
                errfile = makereqerr(request);
                break;

        case EX_NOTIFY:
                /*
                 * stdin:  req#
                 * stdout: /dev/null
                 * stderr: /dev/null
                 */
                infile = makereqerr(request);
                outfile = 0;
                errfile = 0;

                break;

        case EX_ALERT:
        case EX_FALERT:
        case EX_PALERT:
        case EX_FAULT_MESSAGE:
        case EX_FORM_MESSAGE:
                /*
                 * stdin:  /dev/null
                 * stdout: /dev/null
                 * stderr: /dev/null
                 */
                infile = 0;
                outfile = 0;
                errfile = 0;
                break;

        }

        if (infile) {
                if (Open(infile, O_RDONLY) == -1)
                        Done (EXEC_EXIT_NOPEN, errno);
        } else {
                if (Open("/dev/null", O_RDONLY) == -1)
                        Done (EXEC_EXIT_NOPEN, errno);
        }

        if (outfile) {
                if (Open(outfile, O_CREAT|O_TRUNC|O_WRONLY, 0600) == -1)
                        Done (EXEC_EXIT_NOPEN, errno);
        } else {
                /*
                 * If EX_INTERF, this is still needed to cause the
                 * standard error channel to be #2.
                 */
                if (Open("/dev/null", O_WRONLY) == -1)
                        Done (EXEC_EXIT_NOPEN, errno);
        }

        if (errfile) {
                if (Open(errfile, O_CREAT|O_TRUNC|O_WRONLY, 0600) == -1)
                        Done (EXEC_EXIT_NOPEN, errno);
        } else {
                if (Open("/dev/null", O_WRONLY) == -1)
                        Done (EXEC_EXIT_NOPEN, errno);
        }

        switch (type) {

        case EX_INTERF:
                /*
                 * Opening a ``port'' can be dangerous to our health:
                 *
                 *      - Hangups can occur if the line is dropped.
                 *      - The printer may send an interrupt.
                 *      - A FIFO may be closed, generating SIGPIPE.
                 *
                 * We catch these so we can complain nicely.
                 */
                trap_fault_signals ();

                (void)Close (1);

                procuid = request->secure->uid;
                procgid = request->secure->gid;

                if (printer->printer->dial_info)
                {
                        ret = open_dialup(request->printer_type,
                                printer->printer);
                        if (ret == 0)
                                do_undial = 1;
                }
                else
                {
                        ret = open_direct(request->printer_type,
                                printer->printer);
                        do_undial = 0;
                        /* this is a URI */
                        if (is_printer_uri(printer->printer->device) == 0)
                                addenv(&envp, "DEVICE_URI",
                                         printer->printer->device);
                }
                                addenv(&envp, "DEVICE_URI",
                                         printer->printer->device);
                if (ret != 0)
                        Done (ret, errno);

                if (!(request->request->outcome & RS_FILTERED))
                        file_list = request->request->file_list;

                else {
                        register int            count   = 0;
                        char                    num[sizeof (BIGGEST_REQID_S)];
                        register char *         prefix;

                        prefix = makestr(
                                Lp_Temp,
                                "/F",
                                getreqno(request->secure->req_id),
                                "-",
                                (char *)0
                        );

                        file_list = (char **)Malloc(
                                (lenlist(request->request->file_list) + 1)
                              * sizeof(char *)
                        );

                        for (
                                listp = request->request->file_list;
                                *listp;
                                listp++
                        ) {
                                sprintf (num, "%d", count + 1);
                                file_list[count] = makestr(
                                        prefix,
                                        num,
                                        (char *)0
                                );
                                count++;
                        }
                        file_list[count] = 0;
                }

#ifdef LP_USE_PAPI_ATTR
                /*
                 * Check if the PAPI job attribute file exists, if it does
                 * pass the file's pathname to the printer interface script
                 * in an environment variable. This file is created when
                 * print jobs are submitted via the PAPI interface.
                 */
                snprintf(tmpName, sizeof (tmpName), "%s-%s",
                        getreqno(request->secure->req_id), LP_PAPIATTRNAME);
                path = makepath(Lp_Temp, tmpName, (char *)0);
                if ((path != NULL) && (stat(path, &tmpBuf) == 0))
                {
                        /*
                         * IPP job attribute file exists for this job so
                         * set the environment variable
                         */
                        addenv(&envp, "ATTRPATH", path);
                }
                Free(path);

                /*
                 * now set environment variable for the printer's PostScript
                 * Printer Description (PPD) file, this is used by the filter
                 * when forming the print data for this printer.
                 */
                if ((request->printer != NULL) &&
                    (request->printer->printer != NULL) &&
                    (request->printer->printer->name != NULL))
                {
                        snprintf(tmpName, sizeof (tmpName), "%s.ppd",
                                request->printer->printer->name);
                        path = makepath(ETCDIR, "ppd", tmpName, (char *)0);
                        if ((path != NULL) && (stat(path, &tmpBuf) == 0))
                        {
                                addenv(&envp, "PPD", path);
                        }
                        Free(path);
                }
#endif

                if (request->printer_type)
                        addenv(&envp, "TERM", request->printer_type);

                if (!(printer->printer->daisy)) {
                        register char * chset = 0;
                        register char * csp;

                        if (
                                request->form
                             && request->form->form->chset
                             && request->form->form->mandatory
                             && !STREQU(NAME_ANY, request->form->form->chset)
                        )
                                chset = request->form->form->chset;

                        else if (
                                request->request->charset
                             && !STREQU(NAME_ANY, request->request->charset)
                        )
                                chset = request->request->charset;

                        if (chset) {
                                csp = search_cslist(
                                        chset,
                                        printer->printer->char_sets
                                );

                                /*
                                 * The "strtok()" below wrecks the string
                                 * for future use, but this is a child
                                 * process where it won't be needed again.
                                 */
                                addenv (&envp, "CHARSET",
                                        (csp? strtok(csp, "=") : chset)
                                );
                        }
                }

                if (request->fast)
                        addenv(&envp, "FILTER", request->fast);

                /*
                 * Add the sensitivity label to the environment for
                 * banner page and header/footer processing
                 */

                if (is_system_labeled() && request->secure->slabel != NULL)
                        addenv(&envp, "SLABEL", request->secure->slabel);

                /*
                 * Add the system name to the user name (ala system!user)
                 * unless it is already there. RFS users may have trouble
                 * here, sorry!
                 */
                cp = strchr(request->secure->user, '@');

                allTraysWithForm(printer, request->form);

                /*
                 * Fix for 4137389
                 * Remove double quotes from title string.
                 */
                fr_flg = 1;
                clean_title = strdup(NB(request->request->title));
                if (clean_title == NULL) {
                        /*
                         * strdup failed. We're probably hosed
                         * but try setting clean_title
                         * to original title and continuing.
                         */
                        clean_title = NB(request->request->title);
                        fr_flg = 0;
                } else if (strcmp(clean_title, "") != 0) {
                        char *ct_p;

                        for (ct_p = clean_title; *ct_p != '\0'; ct_p++) {
                                if (*ct_p == '"')
                                        *ct_p = ' ';
                        }
                }

                av[ac++] = arg_string(TRUSTED, "%s/%s", Lp_A_Interfaces,
                                        printer->printer->name);
                /*
                 * Read the options field of the request
                 * In case of remote lpd request
                 * the options field will have
                 * job-id-requested. This is the
                 * id sent by the client
                 */
                if (request->request->options != NULL) {
                        char *options = NULL, *temp = NULL;
                        options = temp = strdup(request->request->options);

                        /*
                         * Search for job-id-requested in
                         * options string
                         */
                        options = strstr(options, "job-id-requested");
                        if (options != NULL) {
                                /*
                                 * Extract the ridno from the string
                                 * job-id-requested=xxx
                                 * In this case ridno = xxx
                                 */
                                if (STRNEQU(options, "job-id-requested=", 17)) {
                                        ridno = strdup(options + 17);
                                        tmprid = strstr(ridno, " ");
                                        if (ridno != NULL) {
                                                /*
                                                 * Read job-id-requested
                                                 * successfully
                                                 */
                                                tmprid = strstr(ridno, " ");
                                                if (tmprid != NULL)
                                                        *tmprid = '\0';

                                                setid = 0;
                                        } else
                                                /*
                                                 * could not read
                                                 * ridno from the string
                                                 * job-id-requested=xxx
                                                 */
                                                setid = 1;
                                } else
                                        /*
                                         * could not read
                                         * ridno from the string
                                         * job-id-requested=xxx
                                         */
                                        setid = 1;
                        } else
                                /*
                                 * No job-id-requested in
                                 * request options
                                 */
                                setid = 1;

                        if (temp != NULL)
                                free(temp);

                } else
                        /*
                         * options field in request structure
                         * not set
                         */
                        setid = 1;


                /*
                 * setid = 1 means the job-id-requested attribute
                 * is not set so read the request->secure->req_id
                 */
                if (setid)
                        av[ac++] = arg_string(TRUSTED, "%s",
                            request->secure->req_id);
                else {
                        /*
                         * From request->secure->req_id extract the
                         * printer-name.
                         * request->secure->req_id = <printer-name>-<req_id>
                         * The final req-id will be
                         * <printer-name>-<ridno>
                         */
                        char *r1 = NULL, *r2 = NULL, *tmp = NULL;
                        r1 = r2 = tmp = strdup(request->secure->req_id);
                        r2 = strrchr(r1, '-');
                        if (r2 != NULL) {
                                char *r3 = NULL;
                                int lr1 = strlen(r1);
                                int lr2 = strlen(r2);
                                r1[lr1 - lr2 + 1] = '\0';

                                /*
                                 * Now r1 = <printer-name>-
                                 */
                                lr1 = strlen(r1);
                                lr2 = strlen(ridno);

                                r3 = (char *)malloc(lr1+lr2+1);
                                if (r3 != NULL) {
                                        strcpy(r3, r1);
                                        strcat(r3, ridno);
                                        /*
                                         * Here r3 = <printer-name>-<ridno>
                                         */
                                        av[ac++] = arg_string(TRUSTED,
                                            "%s", r3);
                                        free(r3);
                                } else
                                        av[ac++] = arg_string(TRUSTED, "%s",
                                            request->secure->req_id);

                        } else
                                av[ac++] = arg_string(TRUSTED, "%s",
                                    request->secure->req_id);

                        if (tmp != NULL)
                                free(tmp);

                        if (ridno != NULL)
                                free(ridno);
                }

                av[ac++] = arg_string(UNTRUSTED, "%s", request->request->user);
                av[ac++] = arg_string(TRUSTED, "%s", clean_title);
                av[ac++] = arg_string(TRUSTED, "%d", request->copies);

                if (fr_flg)
                        free (clean_title);

                sep = "";

                /*
                 * Do the administrator defined key=value pair options
                 */

                argbuf[0] = '\0';

                if (printer->printer->options) {
                        char **tmp = printer->printer->options;
                        while(*tmp != NULL) {
                                STRLCAT(argbuf, sep, sizeof (argbuf));
                                sep = " ";
                                STRLCAT(argbuf, *tmp++, sizeof (argbuf));
                        }
                }

                /*
                 * Do the administrator defined ``stty'' stuff before
                 * the user's -o options, to allow the user to override.
                 */
                if (printer->printer->stty) {
                        STRLCAT (argbuf, sep, sizeof (argbuf));
                        sep = " ";
                        STRLCAT (argbuf, "stty='", sizeof (argbuf));
                        STRLCAT (argbuf, printer->printer->stty,
                            sizeof (argbuf));
                        STRLCAT (argbuf, "'", sizeof (argbuf));
                }

                /*
                 * Do all of the user's options except the cpi/lpi/etc.
                 * stuff, which is done separately.
                 */
                if (request->request->options) {
                        listp = dashos(request->request->options);
                        while (*listp) {
                                if (
                                        !STRNEQU(*listp, "cpi=", 4)
                                     && !STRNEQU(*listp, "lpi=", 4)
                                     && !STRNEQU(*listp, "width=", 6)
                                     && !STRNEQU(*listp, "length=", 7)
                                ) {
                                        STRLCAT (argbuf, sep, sizeof (argbuf));
                                        sep = " ";
                                        STRLCAT (argbuf, *listp,
                                            sizeof (argbuf));
                                }
                                listp++;
                        }
                }

                /*
                 * The "pickfilter()" routine (from "validate()")
                 * stored the cpi/lpi/etc. stuff that should be
                 * used for this request. It chose form over user,
                 * and user over printer.
                 */
                if (request->cpi) {
                        STRLCAT (argbuf, sep, sizeof (argbuf));
                        sep = " ";
                        STRLCAT (argbuf, "cpi=", sizeof (argbuf));
                        STRLCAT (argbuf, request->cpi, sizeof (argbuf));
                }
                if (request->lpi) {
                        STRLCAT (argbuf, sep, sizeof (argbuf));
                        sep = " ";
                        STRLCAT (argbuf, "lpi=", sizeof (argbuf));
                        STRLCAT (argbuf, request->lpi, sizeof (argbuf));
                }
                if (request->pwid) {
                        STRLCAT (argbuf, sep, sizeof (argbuf));
                        sep = " ";
                        STRLCAT (argbuf, "width=", sizeof (argbuf));
                        STRLCAT (argbuf, request->pwid, sizeof (argbuf));
                }
                if (request->plen) {
                        STRLCAT (argbuf, sep, sizeof (argbuf));
                        sep = " ";
                        STRLCAT (argbuf, "length=", sizeof (argbuf));
                        STRLCAT (argbuf, request->plen, sizeof (argbuf));
                }

                /*
                 * Do the ``raw'' bit last, to ensure it gets
                 * done. If the user doesn't want this, then they
                 * can do the correct thing using -o stty=
                 * and leaving out the -r option.
                 */
                if (request->request->actions & ACT_RAW) {
                        STRLCAT (argbuf, sep, sizeof (argbuf));
                        sep = " ";
                        STRLCAT (argbuf, "stty=-opost", sizeof (argbuf));
                }


                /* the "options" */
                av[ac++] = arg_string(UNTRUSTED, "%s", argbuf);

                for (listp = file_list; *listp; listp++)
                        av[ac++] = arg_string(TRUSTED, "%s", *listp);

                (void)chfiles (file_list, procuid, procgid);

                break;


        case EX_SLOWF:
                if (request->slow)
                        addenv(&envp, "FILTER", request->slow);

                procuid = request->secure->uid;
                procgid = request->secure->gid;

                cp = _alloc_files(
                        lenlist(request->request->file_list),
                        getreqno(request->secure->req_id),
                        procuid, procgid);

                av[ac++] = arg_string(TRUSTED, "%s", Lp_Slow_Filter);
                av[ac++] = arg_string(TRUSTED, "%s/%s", Lp_Temp, cp);
                for (listp = request->request->file_list; *listp; listp++)
                        av[ac++] = arg_string(TRUSTED, "%s", *listp);

                (void)chfiles (request->request->file_list, procuid, procgid);

#ifdef LP_USE_PAPI_ATTR
                /*
                 * Check if the PAPI job attribute file exists, if it does
                 * pass the file's pathname to the slow-filters in an
                 * environment variable. Note: this file is created when
                 * print jobs are submitted via the PAPI interface.
                 */
                snprintf(tmpName, sizeof (tmpName), "%s-%s",
                        getreqno(request->secure->req_id), LP_PAPIATTRNAME);
                path = makepath(Lp_Temp, tmpName, (char *)0);
                if ((path != NULL) && (stat(path, &tmpBuf) == 0))
                {
                        /*
                         * IPP job attribute file exists for this job so
                         * set the environment variable
                         */
                        addenv(&envp, "ATTRPATH", path);
                }
                Free(path);


                /*
                 * now set environment variable for the printer's PostScript
                 * Printer Description (PPD) file, this is used by the filter
                 * when forming the print data for this printer.
                 */
                if ((request->printer != NULL) &&
                    (request->printer->printer != NULL) &&
                    (request->printer->printer->name != NULL))
                {
                        snprintf(tmpName, sizeof (tmpName), "%s.ppd",
                                request->printer->printer->name);
                        path = makepath(ETCDIR, "ppd", tmpName, (char *)0);
                        if ((path != NULL) && (stat(path, &tmpBuf) == 0))
                        {
                                addenv(&envp, "PPD", path);
                        }
                        Free(path);
                }
#endif
                break;

        case EX_ALERT:
                procuid = Lp_Uid;
                procgid = Lp_Gid;
                (void)Chown (printer->alert->msgfile, procuid, procgid);

                av[ac++] = arg_string(TRUSTED, "%s/%s/%s", Lp_A_Printers,
                                printer->printer->name, ALERTSHFILE);
                av[ac++] = arg_string(TRUSTED, "%s", printer->alert->msgfile);

                break;

        case EX_PALERT:
                procuid = Lp_Uid;
                procgid = Lp_Gid;
                (void)Chown (pwheel->alert->msgfile, procuid, procgid);

                av[ac++] = arg_string(TRUSTED, "%s/%s/%s", Lp_A_PrintWheels,
                                pwheel->pwheel->name, ALERTSHFILE);
                av[ac++] = arg_string(TRUSTED, "%s", printer->alert->msgfile);

                break;

        case EX_FALERT:
                procuid = Lp_Uid;
                procgid = Lp_Gid;
                (void)Chown (form->alert->msgfile, procuid, procgid);

                av[ac++] = arg_string(TRUSTED, "%s/%s/%s", Lp_A_Forms,
                                form->form->name, ALERTSHFILE);
                av[ac++] = arg_string(TRUSTED, "%s", printer->alert->msgfile);

                break;

        case EX_FORM_MESSAGE:
                procuid = Lp_Uid;
                procgid = Lp_Gid;

                av[ac++] = arg_string(TRUSTED, "%s/form", Lp_A_Faults);
                av[ac++] = arg_string(TRUSTED, "%s", form->form->name);
                av[ac++] = arg_string(TRUSTED, "%s", time_buf);
                av[ac++] = arg_string(TRUSTED, "%s/%s/%s", Lp_A_Forms,
                                form->form->name, FORMMESSAGEFILE);

                break;

        case EX_FAULT_MESSAGE:
                procuid = Lp_Uid;
                procgid = Lp_Gid;

                av[ac++] = arg_string(TRUSTED, "%s/printer", Lp_A_Faults);
                av[ac++] = arg_string(TRUSTED, "%s", printerNameToShow);
                av[ac++] = arg_string(TRUSTED, "%s", time_buf);
                av[ac++] = arg_string(TRUSTED, "%s/%s/%s", Lp_A_Printers,
                                printerName, FAULTMESSAGEFILE);

                break;

        case EX_NOTIFY:
                if (request->request->alert) {
                        procuid = request->secure->uid;
                        procgid = request->secure->gid;

                        av[ac++] = arg_string(TRUSTED, "%s",
                                        request->request->alert);
                } else {
                        char *user = strdup(request->request->user);
                        clean_string(user);
                        slabel = request->secure->slabel;

                        if (request->request->actions & ACT_WRITE) {
                                av[ac++] = arg_string(TRUSTED, "%s", BINWRITE);
                                snprintf(argbuf, sizeof (argbuf),
                                        "%s %s || %s %s",
                                        BINWRITE, user,
                                        BINMAIL, user
                                );
                                av[ac++] = arg_string(TRUSTED, "/bin/sh");
                                av[ac++] = arg_string(TRUSTED, "-c");
                                av[ac++] = arg_string(TRUSTED, "%s", argbuf);
                        } else if ((getzoneid() == GLOBAL_ZONEID) &&
                                   is_system_labeled() && (slabel != NULL)) {
                                /*
                                 * If in the global zone and the system is
                                 * labeled, mail is handled via a local
                                 * labeled zone that is the same label as
                                 * the request.
                                 */
                                if ((mail_zonename =
                                    get_labeled_zonename(slabel)) ==
                                    (char *)-1) {
                                        /*
                                         * Cannot find labeled zone, just
                                         * return 0.
                                         */
                                        return(0);
                                }
                        }
                        if (mail_zonename == NULL) {
                                procuid = Lp_Uid;
                                procgid = Lp_Gid;
                                av[ac++] = arg_string(TRUSTED, "%s", BINMAIL);
                                av[ac++] = arg_string(UNTRUSTED, "%s", user);
                        } else {
                                procuid = getuid();
                                procgid = getgid();
                                av[ac++] = arg_string(TRUSTED, "%s",
                                    "/usr/sbin/zlogin");
                                av[ac++] = arg_string(TRUSTED, "%s",
                                    mail_zonename);
                                av[ac++] = arg_string(TRUSTED, "%s",
                                    BINMAIL);
                                av[ac++] = arg_string(UNTRUSTED, "%s",
                                    user);
                                Free(mail_zonename);
                        }

                        free(user);
                }
                break;
        }

        av[ac++] = NULL;

        Fork2 ();
        /* only the child returns */

        /*
         * Correctly set up the supplemental group list
         * for proper file access (before execl the interface program)
         */

        pwp = getpwuid(procuid);
        if (pwp == NULL) {
                note("getpwuid(%d) call failed\n", procuid);
        } else if (initgroups(pwp->pw_name, procgid) < 0) {
                note("initgroups() call failed %d\n", errno);
        }

        setgid (procgid);
        setuid (procuid);

        /*
         * The shell doesn't allow the "trap" builtin to set a trap
         * for a signal ignored when the shell is started. Thus, don't
         * turn off signals in the last child!
         */

#ifdef DEBUG
        for (i = 0; av[i] != NULL; i++)
                note("exec(%s): av[%d] = %s", _exec_name(type), i, av[i]);
        for (i = 0; envp[i] != NULL; i++)
                note("exec(%s): envp[%d] = %s", _exec_name(type), i, envp[i]);
#endif

        execvpe(av[0], av, envp);
        Done (EXEC_EXIT_NEXEC, errno);
        /*NOTREACHED*/
        return (0);
}

/**
 ** addenv() - ADD A VARIABLE TO THE ENVIRONMENT
 **/

static void
addenv(char ***envp, char *name, char *value)
{
        register char *         cp;

        if ((name == NULL) || (value == NULL))
                return;

        if ((cp = makestr(name, "=", value, (char *)0)))
                addlist(envp, cp);
        return;
}

/**
 ** Fork1() - FORK FIRST CHILD, SET UP CONNECTION TO IT
 **/

static int
Fork1(EXEC *ep)
{
        int                     pid;
        int                     fds[2];

        if (pipe(fds) == -1) {
                note("Failed to create pipe for child process (%s).\n", PERROR);
                errno = EAGAIN ;
                return(-1);
        }

        ep->md = mconnect((char *)0, fds[0], fds[1]);

        switch (pid = fork()) {

        case -1:
                mdisconnect(ep->md);
                close(fds[0]);
                close(fds[1]);
                ep->md = 0;
                return (-1);

        case 0:
                ChildMd = mconnect(NULL, fds[1], fds[1]);
                return (0);

        default:
                mlistenadd(ep->md, POLLIN);
                return (pid);
        }
}

/**
 ** Fork2() - FORK SECOND CHILD AND WAIT FOR IT
 **/

static void
Fork2(void)
{
        switch ((ChildPid = fork())) {

        case -1:
                Done (EXEC_EXIT_NFORK, errno);
                /*NOTREACHED*/

        case 0:
                return;

        default:
                /*
                 * Delay calling "ignore_fault_signals()" as long
                 * as possible, to give the child a chance to exec
                 * the interface program and turn on traps.
                 */

                cool_heels ();
                /*NOTREACHED*/

        }
}


/**
 ** cool_heels() - WAIT FOR CHILD TO "DIE"
 **/

static void
cool_heels(void)
{
        int                     status;

        /*
         * At this point our only job is to wait for the child process.
         * If we hang out for a bit longer, that's okay.
         * By delaying before turning off the fault signals,
         * we increase the chance that the child process has completed
         * its exec and has turned on the fault traps. Nonetheless,
         * we can't guarantee a zero chance of missing a fault.
         * (We don't want to keep trapping the signals because the
         * interface program is likely to have a better way to handle
         * them; this process provides only rudimentary handling.)
         *
         * Note that on a very busy system, or with a very fast interface
         * program, the tables could be turned: Our sleep below (coupled
         * with a delay in the kernel scheduling us) may cause us to
         * detect the fault instead of the interface program.
         *
         * What we need is a way to synchronize with the child process.
         */
        sleep (1);
        ignore_fault_signals ();

        WaitedChildPid = 0;
        while ((WaitedChildPid = wait(&status)) != ChildPid)
                ;

        if (
                EXITED(status) > EXEC_EXIT_USER
             && EXITED(status) != EXEC_EXIT_FAULT
        )
                Done (EXEC_EXIT_EXIT, EXITED(status));

        done (status, 0);       /* Don't use Done() */
        /*NOTREACHED*/
}


/**
 ** trap_fault_signals() - TRAP SIGNALS THAT CAN OCCUR ON PRINTER FAULT
 ** ignore_fault_signals() - IGNORE SAME
 **/

static void
trap_fault_signals(void)
{
        signal (SIGHUP, sigtrap);
        signal (SIGINT, sigtrap);
        signal (SIGQUIT, sigtrap);
        signal (SIGPIPE, sigtrap);
        return;
}

static void
ignore_fault_signals(void)
{
        signal (SIGHUP, SIG_IGN);
        signal (SIGINT, SIG_IGN);
        signal (SIGQUIT, SIG_IGN);
        signal (SIGPIPE, SIG_IGN);
        return;
}

/**
 ** sigtrap() - TRAP VARIOUS SIGNALS
 **/

static void
sigtrap(int sig)
{
        signal (sig, SIG_IGN);
        switch (sig) {

        case SIGHUP:
                Done (EXEC_EXIT_HUP, 0);
                /*NOTREACHED*/

        case SIGQUIT:
        case SIGINT:
                Done (EXEC_EXIT_INTR, 0);
                /*NOTREACHED*/

        case SIGPIPE:
                Done (EXEC_EXIT_PIPE, 0);
                /*NOTREACHED*/

        case SIGTERM:
                /*
                 * If we were killed with SIGTERM, it should have been
                 * via the Spooler who should have killed the entire
                 * process group. We have to wait for the children,
                 * since we're their parent, but WE MAY HAVE WAITED
                 * FOR THEM ALREADY (in cool_heels()).
                 */
                if (ChildPid != WaitedChildPid) {
                        register int            cpid;

                        while (
                                (cpid = wait((int *)0)) != ChildPid
                             && (cpid != -1 || errno != ECHILD)
                        )
                                ;
                }

                /*
                 * We can't rely on getting SIGTERM back in the wait()
                 * above, because, for instance, some shells trap SIGTERM
                 * and exit instead. Thus we force it.
                 */
                done (SIGTERM, 0);      /* Don't use Done() */
                /*NOTREACHED*/
        }
}

/**
 ** done() - TELL SPOOLER THIS CHILD IS DONE
 **/

static void
done(int status, int err)
{
        if (do_undial)
                undial (1);

        mputm (ChildMd, S_CHILD_DONE, key, status, err);
        mdisconnect (ChildMd);

        exit (0);
        /*NOTREACHED*/
}

/**
 ** child_mallocfail()
 **/

static void
child_mallocfail(void)
{
        Done (EXEC_EXIT_NOMEM, ENOMEM);
}