root/usr/src/cmd/lp/lib/printers/putprinter.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/* EMACS_MODES: !fill, lnumb, !overwrite, !nodelete, !picture */

#include "sys/types.h"
#include "sys/stat.h"
#include "stdio.h"
#include "string.h"
#include "errno.h"
#include "stdlib.h"

#include "lp.h"
#include "printers.h"

#include <unistd.h>
#include <sys/wait.h>

#define SHELL "/bin/sh"
#define PPDZIP ".gz"

extern struct {
        char                    *v;
        short                   len,
                                okremote;
}                       prtrheadings[];

#if     defined(__STDC__)

static void             print_sdn (int, char *, SCALED);
static void             print_l (int, char *, char **);
static void             print_str (int, char *, char *);

#ifdef LP_USE_PAPI_ATTR
static int addPrintersPPD(char *name, PRINTER *prbufp);
static int copyPPDFile(char *ppd, char *printersPPD);
static int unzipPPDFile(char *ppd, char *printersPPD);
#endif

#else

static void             print_sdn(),
                        print_l(),
                        print_str();

#ifdef LP_USE_PAPI_ATTR
static int addPrintersPPD();
static int copyPPDFile();
static int unzipPPDFile();
#endif

#endif

unsigned long           ignprinter      = 0;
int                     ppdopt          = 0;

/**
 ** putprinter() - WRITE PRINTER STRUCTURE TO DISK FILES
 **/

int
putprinter(char *name, PRINTER *prbufp)
{
        register char *         path;
        register char *         stty;
        register char *         speed;

        int fdin, fdout;

        int                     fld;

        char                    buf[BUFSIZ];

        struct stat             statbuf1,
                                statbuf2;


        badprinter = 0;

        if (!name || !*name) {
                errno = EINVAL;
                return (-1);
        }

        if (STREQU(NAME_ALL, name)) {
                errno = EINVAL;
                return (-1);
        }

        /*
         * First go through the structure and see if we have
         * anything strange.
         */
        if (!okprinter(name, prbufp, 1)) {
                errno = EINVAL;
                return (-1);
        }

        if (!Lp_A_Printers || !Lp_A_Interfaces) {
                getadminpaths (LPUSER);
                if (!Lp_A_Printers || !Lp_A_Interfaces)
                        return (0);
        }

        /*
         * Create the parent directory for this printer
         * if it doesn't yet exist.
         */
        if (!(path = getprinterfile(name, (char *)0)))
                return (-1);
        if (Stat(path, &statbuf1) == 0) {
                if (!S_ISDIR(statbuf1.st_mode)) {
                        Free (path);
                        errno = ENOTDIR;
                        return (-1);
                }
        } else if (errno != ENOENT || mkdir_lpdir(path, MODE_DIR) == -1) {
                Free (path);
                return (-1);
        }
        Free (path);

        /*
         * Create the copy of the interface program, unless
         * that would be silly or not desired.
         * Conversely, make sure the interface program doesn't
         * exist for a remote printer.
         */
        if (prbufp->remote) {
                if (!(path = makepath(Lp_A_Interfaces, name, (char *)0)))
                        return (-1);
                (void)rmfile (path);
                Free (path);
        }
        if (prbufp->interface && (ignprinter & BAD_INTERFACE) == 0) {
                if (Stat(prbufp->interface, &statbuf1) == -1)
                        return (-1);
                if (!(path = makepath(Lp_A_Interfaces, name, (char *)0)))
                        return (-1);
                if (
                        Stat(path, &statbuf2) == -1
                     || statbuf1.st_dev != statbuf2.st_dev
                     || statbuf1.st_ino != statbuf2.st_ino
                ) {
                        register int            n;

                        if ((fdin = open_locked(prbufp->interface, "r", 0)) < 0) {
                                Free (path);
                                return (-1);
                        }
                        if ((fdout = open_locked(path, "w", MODE_EXEC)) < 0) {
                                Free (path);
                                close(fdin);
                                return (-1);
                        }
                        while ((n = read(fdin, buf, BUFSIZ)) > 0)
                                write (fdout, buf,  n);
                        close(fdout);
                        close(fdin);
                }
                Free (path);
        }

#ifdef LP_USE_PAPI_ATTR
        /*
         * Handle PPD (Postscript Printer Definition) file for printer
         * if this printer has been configured with one
         */
        if ((prbufp->ppd != NULL) && (ppdopt))
        {
                if (addPrintersPPD(name, prbufp) != 0)
                {
                        /* failed to added the printers PPD file */
                        return (-1);
                }
        }
#endif

        /*
         * If this printer is dialed up, remove any baud rates
         * from the stty option list and move the last one to
         * the ".speed" member if the ".speed" member isn't already
         * set. Conversely, if this printer is directly connected,
         * move any value from the ".speed" member to the stty list.
         */

        stty = (prbufp->stty? Strdup(prbufp->stty) : 0);
        if (prbufp->speed)
                speed = Strdup(prbufp->speed);
        else
                speed = 0;

        if (prbufp->dial_info && stty) {
                register char           *newstty,
                                        *p,
                                        *q;

                register int            len;

                if (!(q = newstty = Malloc(strlen(stty) + 1))) {
                        Free (stty);
                        errno = ENOMEM;
                        return (-1);
                }
                newstty[0] = 0; /* start with empty copy */

                for (
                        p = strtok(stty, " ");
                        p;
                        p = strtok((char *)0, " ")
                ) {
                        len = strlen(p);
                        if (strspn(p, "0123456789") == len) {
                                /*
                                 * If "prbufp->speed" isn't set, then
                                 * use the speed we just found. Don't
                                 * check "speed", because if more than
                                 * one speed was given in the list, we
                                 * want the last one.
                                 */
                                if (!prbufp->speed) {
                                        if (speed)
                                                Free (speed);
                                        speed = Strdup(p);
                                }

                        } else {
                                /*
                                 * Not a speed, so copy it to the
                                 * new stty string.
                                 */
                                if (q != newstty)
                                        *q++ = ' ';
                                strcpy (q, p);
                                q += len;
                        }
                }

                Free (stty);
                stty = newstty;

        } else if (!prbufp->dial_info && speed) {
                register char           *newstty;

                newstty = Malloc(strlen(stty) + 1 + strlen(speed) + 1);
                if (!newstty) {
                        if (stty)
                                Free (stty);
                        errno = ENOMEM;
                        return (-1);
                }

                if (stty) {
                        strcpy (newstty, stty);
                        strcat (newstty, " ");
                        strcat (newstty, speed);
                        Free (stty);
                } else
                        strcpy (newstty, speed);
                Free (speed);
                speed = 0;

                stty = newstty;

        }

        /*
         * Open the configuration file and write out the printer
         * configuration.
         */

        if (!(path = getprinterfile(name, CONFIGFILE))) {
                if (stty)
                        Free (stty);
                if (speed)
                        Free (speed);
                return (-1);
        }
        if ((fdout = open_locked(path, "w", MODE_READ)) < 0) {
                Free (path);
                if (stty)
                        Free (stty);
                if (speed)
                        Free (speed);
                return (-1);
        }
        Free (path);

        errno = 0;
        for (fld = 0; fld < PR_MAX; fld++) {
                if (prbufp->remote && !prtrheadings[fld].okremote)
                        continue;

                switch (fld) {

#define HEAD    prtrheadings[fld].v

                case PR_BAN:
                        {
                                char *ptr = NAME_ON;

                                switch (prbufp->banner) {
                                case BAN_ALWAYS:
                                        ptr = NAME_ON;
                                        break;
                                case BAN_NEVER:
                                        ptr = NAME_OFF;
                                        break;
                                case BAN_OPTIONAL:
                                        ptr = NAME_OPTIONAL;
                                        break;
                                }
                                (void)fdprintf(fdout, "%s %s\n", HEAD, ptr);
                        }
                        break;

                case PR_CPI:
                        print_sdn(fdout, HEAD, prbufp->cpi);
                        break;

                case PR_CS:
                        if (!emptylist(prbufp->char_sets))
                                print_l(fdout, HEAD, prbufp->char_sets);
                        break;

                case PR_ITYPES:
                        /*
                         * Put out the header even if the list is empty,
                         * to distinguish no input types from the default.
                         */
                        print_l(fdout, HEAD, prbufp->input_types);
                        break;

                case PR_DEV:
                        print_str(fdout, HEAD, prbufp->device);
                        break;

                case PR_DIAL:
                        print_str(fdout, HEAD, prbufp->dial_info);
                        break;

                case PR_RECOV:
                        print_str(fdout, HEAD, prbufp->fault_rec);
                        break;

                case PR_INTFC:
                        print_str(fdout, HEAD, prbufp->interface);
                        break;

                case PR_LPI:
                        print_sdn(fdout, HEAD, prbufp->lpi);
                        break;

                case PR_LEN:
                        print_sdn(fdout, HEAD, prbufp->plen);
                        break;

                case PR_LOGIN:
                        if (prbufp->login & LOG_IN)
                                (void)fdprintf(fdout, "%s\n", HEAD);
                        break;

                case PR_PTYPE:
                {
                        char                    **printer_types;

                        /*
                         * For backward compatibility for those who
                         * use only "->printer_type", we have to play
                         * some games here.
                         */
                        if (prbufp->printer_type && !prbufp->printer_types)
                                printer_types = getlist(
                                        prbufp->printer_type,
                                        LP_WS,
                                        LP_SEP
                                );
                        else
                                printer_types = prbufp->printer_types;

                        if (!printer_types || !*printer_types)
                                print_str(fdout, HEAD, NAME_UNKNOWN);
                        else
                                print_l(fdout, HEAD, printer_types);

                        if (printer_types != prbufp->printer_types)
                                freelist (printer_types);
                        break;
                }

                case PR_REMOTE:
                        print_str(fdout, HEAD, prbufp->remote);
                        break;

                case PR_SPEED:
                        print_str(fdout, HEAD, speed);
                        break;

                case PR_STTY:
                        print_str(fdout, HEAD, stty);
                        break;

                case PR_WIDTH:
                        print_sdn(fdout, HEAD, prbufp->pwid);
                        break;

#if     defined(CAN_DO_MODULES)
                case PR_MODULES:
                        /*
                         * Put out the header even if the list is empty,
                         * to distinguish no modules from the default.
                         */
                        print_l(fdout, HEAD, prbufp->modules);
                        break;
#endif

                case PR_OPTIONS:
                        print_l(fdout, HEAD, prbufp->options);
                        break;

                case PR_PPD:
                {
                        print_str(fdout, HEAD, prbufp->ppd);
                        break;
                }
                }

        }
        if (stty)
                Free (stty);
        if (speed)
                Free (speed);
        if (errno != 0) {
                close(fdout);
                return (-1);
        }
        close(fdout);

        /*
         * If we have a description of the printer,
         * write it out to a separate file.
         */
        if (prbufp->description) {

                if (!(path = getprinterfile(name, COMMENTFILE)))
                        return (-1);

                if (dumpstring(path, prbufp->description) == -1) {
                        Free (path);
                        return (-1);
                }
                Free (path);

        }

        /*
         * Now write out the alert condition.
         */
        if (
                prbufp->fault_alert.shcmd
             && putalert(Lp_A_Printers, name, &(prbufp->fault_alert)) == -1
        )
                return (-1);

        return (0);
}

/**
 ** print_sdn() - PRINT SCALED DECIMAL NUMBER WITH HEADER
 ** print_l() - PRINT (char **) LIST WITH HEADER
 ** print_str() - PRINT STRING WITH HEADER
 **/

static void
print_sdn(int fd, char *head, SCALED sdn)
{
        if (sdn.val <= 0)
                return;

        (void)fdprintf (fd, "%s ", head);
        fdprintsdn (fd, sdn);

        return;
}

static void
print_l(int fd, char *head, char **list)
{
        (void)fdprintf (fd, "%s ", head);
        printlist_setup (0, 0, LP_SEP, 0);
        fdprintlist (fd, list);
        printlist_unsetup ();

        return;
}

static void
print_str(int fd, char *head, char *str)
{
        if (!str || !*str)
                return;

        (void)fdprintf (fd, "%s %s\n", head, str);

        return;
}


#ifdef LP_USE_PAPI_ATTR
/*
 * Function:     addPrintersPPD()
 *
 * Description:  Handle PPD (Postscript Printer Definition) file for this
 *               printer if it has been configured with one
 *
 */

static int
addPrintersPPD(char *name, PRINTER *prbufp)

{
        int result = 0;
        char *path = NULL;
        char *ppd = NULL;
        char  buf[BUFSIZ];
        struct stat statbuf;

        (void) snprintf(buf, sizeof (buf), "%s.ppd", name);
        if (prbufp->remote)
        {
                /* make sure the PPD file doesn't exist for a remote printer */
                if (!(path = makepath(ETCDIR, "ppd", buf, (char *)0)))
                {
                        result = -1;
                }
                else
                {
                        (void) rmfile(path);
                }
        }

        if ((result == 0) && (prbufp->ppd != NULL))
        {
                ppd = strdup(prbufp->ppd);

                if (ppd == NULL)
                {
                        result = -1;
                }
                else
                {
                        /* Check the PPD file given exists */

                        if (Stat(ppd, &statbuf) == -1)
                        {
                                /*
                                 * The given ppd files does not exist, but
                                 * check if there is a zipped version of the
                                 * file that we can use instead
                                 */
                                if (strstr(ppd, PPDZIP) != NULL)
                                {
                                        /* this is a zipped file so exit */
                                        result = -1;
                                }
                                else
                                {
                                        ppd = Realloc(ppd,
                                                strlen(ppd)+strlen(PPDZIP)+2);
                                        if (ppd != NULL)
                                        {
                                                ppd = strcat(ppd, PPDZIP);
                                                if (Stat(ppd, &statbuf) == -1)
                                                {
                                                        /*
                                                         * this zipped version
                                                         * of the file does not
                                                         * exist either
                                                         */
                                                        result = -1;
                                                }
                                        }
                                        else
                                        {
                                                result = -1;
                                        }
                                }
                        }
                }

                /*
                 * Create the copy of the PPD file for this printer
                 * unless that would be silly or not desired
                 */

                if (result == 0)
                {
                        if (!(path = makepath(ETCDIR, "ppd", buf, (char *)0)))
                        {
                                result = -1;
                        }
                }

                /*
                 * At this point we may have a zipped or unzipped ppd file, if
                 * it's unzipped just copy it otherwise unzip it to the
                 * printer's ppd file (/etc/lp/ppd/<printer>.ppd)
                 */

                if (result == 0)
                {
                        if (strstr(ppd, PPDZIP) == NULL)
                        {
                                result = copyPPDFile(ppd, path);
                        }
                        else
                        {
                                result = unzipPPDFile(ppd, path);
                        }

                        (void) chown_lppath(path);
                        (void) chmod(path, 0644);
                }

                if (ppd != NULL)
                {
                        Free(ppd);
                }
                if (path != NULL)
                {
                        Free(path);
                }
        }

        return (result);
} /* addPrintersPPD() */


/*
 * Function:     copyPPDFile()
 *
 * Description:  Copy the given ppd file to the printer's file in /etc/lp/ppd
 *
 */

static int
copyPPDFile(char *ppd, char *printersPPD)

{
        int  result = 0;
        register int n = 0;
        int  fdin  = 0;
        int  fdout = 0;
        char buf[BUFSIZ];

        if ((ppd != NULL) && (printersPPD != NULL))
        {
                if ((fdin = open_locked(ppd, "r", 0)) < 0)
                {
                        result = -1;
                }
                else
                {
                        fdout = open_locked(printersPPD, "w", MODE_EXEC);
                        if (fdout < 0)
                        {
                                close(fdin);
                                result = -1;
                        }
                }

                if (result == 0)
                {
                        while ((n = read(fdin, buf, BUFSIZ)) > 0)
                        {
                                write(fdout, buf,  n);
                        }
                        close(fdout);
                        close(fdin);
                }
        }
        else
        {
                result = -1;
        }

        return (result);
} /* copyPPDFile() */



/*
 * Function:     unzipPPDFile()
 *
 * Description:  Unzip the given ppd file to the printer's file in /etc/lp/ppd.
 *               This is done by forking and running the unzip utility on the
 *               zipped ppd file.
 *
 */

static int
unzipPPDFile(char *ppd, char *printersPPD)

{
        int  result = -1;
        char *cmdLine = NULL;
        pid_t childPID = 0;
        int   stat = 0;
        int   clSize = 0;


        if ((ppd != NULL) && (printersPPD != NULL))
        {
                childPID = fork();

                switch (childPID)
                {
                        case -1:
                        {
                                /* return error */
                                break;
                        }

                        case 0:
                        {
                                /* child process  - so execute something */

                                clSize = strlen("/usr/bin/rm -f ") +
                                                strlen(printersPPD) +
                                                strlen("/usr/bin/gzip -dc ") +
                                                strlen(ppd) +
                                                strlen(printersPPD) + 20;
                                cmdLine = malloc(clSize);
                                if (cmdLine != NULL)
                                {

                                        (void) snprintf(cmdLine, clSize,
                                "/usr/bin/rm -f %s; /usr/bin/gzip -dc %s > %s",
                                                        printersPPD, ppd,
                                                        printersPPD);
                                        result = execl(SHELL, SHELL, "-c",
                                                        cmdLine, NULL);
                                        exit(result);
                                }
                                break;
                        }

                        default:
                        {
                                /* parent process, child pid is in childPID */

                                while (wait(&stat) != childPID);

                                if ((stat & 0xff00) == 0)
                                {
                                        result = 0;
                                }
                                break;
                        }
                }
        }

        return (result);
} /* unzipPPDFile() */
#endif