root/usr/src/cmd/svr4pkg/pkginstall/reqexec.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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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


#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>      /* creat() declaration */
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <locale.h>
#include <libintl.h>
#include <pkglib.h>
#include "install.h"
#include "libadm.h"
#include "libinst.h"
#include "pkginstall.h"
#include "messages.h"

extern char     tmpdir[], instdir[];
extern int      pkgverbose;

static int      do_exec(int update, char *script, char *output,
                        char *inport, char *alt_user);
static char     path[PATH_MAX];
static char     *resppath = NULL;
static int      fd;
static int      respfile_defined = 0;
static int      respfile_ro = 0;        /* read only resp file */

/*
 * This informs the calling routine if a read-only response file has been
 * provided on the command line.
 */
int
rdonly_respfile(void)
{
        return (respfile_ro);
}

int
is_a_respfile(void)
{
        return (respfile_defined);
}

/*
 * This function creates a working copy of the checkinstall script.
 * This is needed in situations where the packages parent directories modes
 * are set too restrictively, i.e. 700.
 *
 * Returns: A pointer to the location of the copied checkinstall
 * script or NULL
 */

char *
dup_chkinstall(char *script)
{
        char    *dstpath;
        size_t  dstpathLen;
        int     r;
static  char    *tmpname = "checkinstallXXXXXX";

        /* determine length for destination script path */

        dstpathLen = strlen(tmpdir) + strlen(tmpname) + 3;

        /* allocate storage to hold destination script path */

        dstpath = (char *)malloc(dstpathLen);
        if (dstpath == (char *)NULL) {
                return ((char *)NULL);
        }

        /* create destination script path */

        (void) snprintf(dstpath, dstpathLen, "%s/%s", tmpdir, tmpname);

        if (mktemp(dstpath) == NULL) {
                progerr(ERR_TMPFILE_CHK);
                (void) free(dstpath);
                return (NULL);
        }

        /* make copy of script */

        r = copyf(script, dstpath, (time_t)0);
        if (r != 0) {
                progerr(ERR_CANNOT_COPY, script, dstpath);
                return (NULL);
        }

        /* Make the copy of the script readable by all */

        if (chmod(dstpath, 0444) != 0) {
                progerr(ERR_CHMOD_CHK);
                (void) free(dstpath);
                return (NULL);
        }

        return (dstpath);
}

/*
 * This function creates a temporary working copy of a read-only response
 * file. It changes the resppath pointer to point to the working copy.
 */
static int
dup_respfile(void)
{
        char    tpath[PATH_MAX];
        int     r;

        (void) strlcpy(tpath, path, sizeof (tpath));

        (void) snprintf(path, sizeof (path), "%s/respXXXXXX", tmpdir);

        resppath = mktemp(path);
        if (resppath == NULL) {
                progerr(ERR_TMPRESP);
                return (99);
        }

        /* Copy the contents of the user's response file to the working copy. */

        r = copyf(tpath, resppath, (time_t)0);
        if (r != 0) {
                progerr(ERR_NORESPCOPY, tpath, resppath);
                return (99);
        }

        /*
         * Make it writable by the non-privileged installation user-id,
         * but readable by the world.
         */

        if (chmod(resppath, 0644) != 0) {
                progerr(ERR_CHMOD, resppath);
                return (99);
        }

        respfile_ro = 0;

        return (0);
}

/*
 * This function establishes the response file passed on the command line if
 * it's called with a valid string. If called with NULL, it checks to see if
 * there's a response file already. If there isn't, it creates a temporary.
 */
int
set_respfile(char *respfile, char *pkginst, int resp_stat)
{
        if (respfile == NULL && !respfile_defined) {
                /* A temporary response file needs to be constructed. */
                (void) snprintf(path, sizeof (path), "%s/respXXXXXX", tmpdir);
                resppath = mktemp(path);
                if (resppath == NULL) {
                        progerr(ERR_TMPRESP);
                        return (99);
                }
        } else {
                /* OK, we're being passed a response file or directory. */
                if (isdir(respfile) == 0) {
                        (void) snprintf(path, sizeof (path),
                                "%s/%s", respfile, pkginst);
                } else {
                        (void) strlcpy(path, respfile, sizeof (path));
                }

                resppath = path;
                respfile_ro = resp_stat;
        }

        respfile_defined++;

        return (0);
}

/* This exposes the working response file. */
char *
get_respfile(void)
{
        return (resppath);
}

/*
 * Execute the request script if present assuming the response file
 * isn't read only.
 */
int
reqexec(int update, char *script, int non_abi_scripts,
        boolean_t enable_root_user)
{
        char    *req_user;

        /*
         * determine which alternative user to execute the request script as
         * if the default user "install" is not defined.
         */

        if (enable_root_user == B_TRUE) {
                /* use the root user */
                req_user = CHK_USER_ROOT;
        } else if (non_abi_scripts != 0) {
                /* non-compliant package user */
                req_user = CHK_USER_NON;
        } else {
                /* standard non-privileged user */
                req_user = CHK_USER_ALT;
        }

        /*
         * If we can't get to the the script or the response file, skip this.
         */
        if (access(script, F_OK) != 0 || respfile_ro)
                return (0);

        /* No interact means no interact. */
        if (echoGetFlag() == B_FALSE) {
                ptext(stderr, ERR_INTR);
                return (5);
        }

        /* If there's no response file, create one. */
        if (!respfile_defined)
                if (set_respfile(NULL, NULL, 0))
                        return (99);

        /* Clear out the old response file (if there is one). */
        if ((access(resppath, F_OK) == 0) && unlink(resppath)) {
                progerr(ERR_RMRESP, resppath);
                return (99);
        }

        /*
         * Create a zero length response file which is only writable
         * by the non-privileged installation user-id, but is readable
         * by the world
         */
        if ((fd = open(resppath, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644)) < 0) {
                progerr(ERR_CRERESP, resppath);
                return (99);
        }
        (void) close(fd);

        return (do_exec(update, script, resppath, REQ_STDIN, req_user));
}

int
chkexec(int update, char *script)
{
        /*
         * If we're up against a read-only response file from the command
         * line. Create a working copy.
         */
        if (respfile_ro) {
                if (dup_respfile())

                        return (99);

                /* Make sure we can get to it. */
                if ((access(resppath, F_OK) != 0)) {
                        progerr(ERR_ACCRESP, resppath);
                        return (7);
                }
        }

        /* If there's no response file, create a fresh one. */
        else if (!respfile_defined) {
                if (set_respfile(NULL, NULL, 0))
                        return (99);

                /*
                 * create a zero length response file which is only writable
                 * by the non-priveledged installation user-id, but is readable
                 * by the world
                 */
                fd = open(resppath, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644);
                if (fd < 0) {
                        progerr(ERR_CRERESP, resppath);
                        return (99);
                }
                (void) close(fd);
        }

        return (do_exec(update, script, resppath, CHK_STDIN, CHK_USER_ALT));
}

static int
do_exec(int update, char *script, char *output, char *inport, char *alt_user)
{
        char            *gname;
        char            *tmp_script;
        char            *uname;
        gid_t           instgid;
        int             retcode = 0;
        struct group    *grp;
        struct passwd   *pwp;
        uid_t           instuid;

        /*
         * Determine which user to run the request script as:
         * - if CHK_USER is a valid user, run the script as CHK_USER
         * - otherwise, if alt_user is a valid user, run the script
         * -- as alt_user
         * - otherwise, output an error message and return failure
         */

        if ((pwp = getpwnam(CHK_USER)) != (struct passwd *)NULL) {
                instuid = pwp->pw_uid;
                uname = CHK_USER;
        } else if ((pwp = getpwnam(alt_user)) != (struct passwd *)NULL) {
                instuid = pwp->pw_uid;
                uname = alt_user;
        } else {
                ptext(stderr, ERR_BADUSER, CHK_USER, CHK_USER_ALT);
                return (1);
        }

        /*
         * Determine which group to run the request script as:
         * - If CHK_GRP is a valid group, run the script as CHK_GRP
         * - otherwise, assume group "1" user "other"
         */

        if ((grp = getgrnam(CHK_GRP)) != (struct group *)NULL) {
                instgid = grp->gr_gid;
                gname = CHK_GRP;
        } else {
                instgid = (gid_t)1;     /* "other" group id */
                gname = "other";        /* "other" group name */
        }

        echoDebug(DBG_DO_EXEC_REQUEST_USER, script, output, uname, instuid,
                gname, instgid);

        (void) chown(output, instuid, instgid);

        /*
         * Copy the checkinstall script to tmpdir in case parent directories
         * are restrictive, i.e. 700. Only do this for non updates, i.e.
         * package installs and not patch package installs.
         */
        if (update) {
                tmp_script = strdup(script);
        } else if ((tmp_script = dup_chkinstall(script)) == NULL) {
                /* Use the original checkinstall script */
                tmp_script = strdup(script);
        }

        if (pkgverbose)
                retcode = pkgexecl(inport, CHK_STDOUT, uname, CHK_GRP, SHELL,
                    "-x", tmp_script, output, NULL);
        else
                retcode = pkgexecl(inport, CHK_STDOUT, uname, CHK_GRP, SHELL,
                    tmp_script, output, NULL);

        free(tmp_script);
        return (retcode);
}