root/usr/src/cmd/lp/cmd/lpfilter.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 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 <errno.h>
#include <string.h>
#include <locale.h>

#include "lp.h"
#include "access.h"
#include "filters.h"
#include "msgs.h"

#define WHO_AM_I        I_AM_LPFILTER
#include "oam.h"

#define OPT_LIST        "f:F:ixl"

int                     add_filter(),
                        reload_filter(),
                        delete_filter(),
                        list_filter();

static void             alert_spooler(),
                        same_complaints();

static char             *opt();

/*
 * Unfortunately, the LP requirements show the listing of a filter
 * to be in a different order than the stored filter table. We can't
 * change the stored version because it's the same as UNISON uses.
 * So, we can't reuse the "FL_..." #defines found in "filters.h".
 * But the following have similar use.
 */
#define FL_MAX_P        FL_MAX
# define FL_IGN_P       8
# define FL_PTYPS_P     2
# define FL_PRTRS_P     3
# define FL_ITYPS_P     0
# define FL_NAME_P      7
# define FL_OTYPS_P     1
# define FL_TYPE_P      4
# define FL_CMD_P       5
# define FL_TMPS_P      6

#define TABLE           0
#define TABLE_I         1

static struct headings {
        char                    *v;
        short                   len;
}               headings[FL_MAX_P] = {

#define ENTRY(X)        X, sizeof(X)-1
        ENTRY("Input types:"),
        ENTRY("Output types:"),
        ENTRY("Printer types:"),
        ENTRY("Printers:"),
        ENTRY("Filter type:"),
        ENTRY("Command:"),
        ENTRY("Options:"),
        ENTRY(""),
        ENTRY("")
#undef  ENTRY

};

/**
 ** usage()
 **/

void                    usage ()
{
        (void) printf (gettext(
"usage:\n"
"\n"
"  (add or change filter)\n"
"    lpfilter -f filter-name {-F path-name | -}\n"
"\n"
"  (restore delivered filter)\n"
"    lpfilter -f filter-name -i\n"
"\n"
"  (list a filter)\n"
"    lpfilter -f filter-name -l\n"
"\n"
"  (list all filters)\n"
"    lpfilter -f \"all\" -l\n"
"\n"
"  (delete filter)\n"
"    lpfilter -f filter-name -x\n"));

        return;
}

/**
 ** main()
 **/

int                     main (argc, argv)
        int                     argc;
        char                    *argv[];
{
        extern int              optind,
                                opterr,
                                optopt,
                                getopt();

        extern char             *optarg;

        int                     c,
                                (*action)(),
                                (*newaction)();

        FILE                    *input;

        char                    *filter,
                                *p;
        char                    stroptsw[] = "-X";


        (void) setlocale (LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
        (void) textdomain(TEXT_DOMAIN);

        if (!is_user_admin()) {
                LP_ERRMSG (ERROR, E_LP_NOTADM);
                exit (1);
        }

        action = 0;
        input = 0;
        filter = 0;

        opterr = 0;

        while ((c = getopt(argc, argv, OPT_LIST)) != -1) switch (c) {

        case 'f':
                if (filter)
                        LP_ERRMSG1 (WARNING, E_LP_2MANY, 'f');
                filter = optarg;
                if (
                        STREQU(NAME_ANY, filter)
                     || STREQU(NAME_NONE, filter)
                ) {
                        LP_ERRMSG (ERROR, E_LP_ANYNONE);
                        exit (1);
                } else if (!syn_name(filter)) {
                        LP_ERRMSG1 (ERROR, E_LP_NOTNAME, filter);
                        exit (1);
                } else if (!*filter)
                        filter = NAME_ALL;
                break;

        case 'F':
                if (input)
                        LP_ERRMSG1 (WARNING, E_LP_2MANY, 'F');
                if (!(input = fopen(optarg, "r"))) {
                        LP_ERRMSG1 (ERROR, E_FL_OPEN, optarg);
                        exit (1);
                }
                newaction = add_filter;
                goto Check;

        case 'i':
                newaction = reload_filter;
                goto Check;

        case 'x':
                newaction = delete_filter;
                goto Check;

        case 'l':
                newaction = list_filter;
Check:          if (action && newaction != action) {
                        LP_ERRMSG2 (
                                ERROR,
                                E_LP_AMBIG,
                                opt(action),
                                opt(newaction)
                        );
                        exit (1);
                }
                action = newaction;
                break;

        default:
                if (optopt == '?') {
                        usage ();
                        exit (0);
                }
                stroptsw[1] = optopt;
                if (strchr(OPT_LIST, optopt))
                        LP_ERRMSG1 (ERROR, E_LP_OPTARG, stroptsw);
                else
                        LP_ERRMSG1 (ERROR, E_LP_OPTION, stroptsw);
                exit (1);

        }

        if (optind < argc && STREQU(argv[optind], "-"))
                if (action) {
                        LP_ERRMSG2 (ERROR, E_LP_AMBIG, opt(action), "-");
                        exit (1);
                } else {
                        action = add_filter;
                        optind++;
                }

        if (!filter) {
                LP_ERRMSG (ERROR, E_FL_NOFILT);
                exit (1);
        }

        if (!action) {
                LP_ERRMSG (ERROR, E_FL_NOACT);
                exit (1);
        }

        if (optind < argc)
                LP_ERRMSG1 (WARNING, E_FL_IGNORE, argv[optind]);

        return ((*action)(filter, input));
}

/**
 ** add_filter()
 **/

int                     add_filter (filter, input)
        char                    *filter;
        FILE                    *input;
{
        register FILTER         *pf,
                                *store,
                                *ps;

        register int            fld;

        register char           *p;

        char                    buf[3 * BUFSIZ],
                                *file;

        int                     line,
                                bad_headings,
                                real_fields[FL_MAX],
                                at_least_one,
                                ret;

        FILTER                  flbuf;


        /*
         * First we read in the input and parse it into a filter,
         * storing it in the filter buffer "flbuf". Keep track of
         * which fields have been given, to avoid overwriting unchanged
         * fields later.
         */

        if (!input)
                input = stdin;

        for (fld = 0; fld < FL_MAX; fld++)
                real_fields[fld] = 0;
        flbuf.templates = 0;

        line = bad_headings = 0;
        while (fgets(buf, sizeof(buf), input) != NULL) {

                buf[strlen(buf) - 1] = 0;

                line++;

                p = buf + strspn(buf, " \t");
                if (!*p || *p == '#')
                        continue;

                for (fld = 0; fld < FL_MAX; fld++)
                        if (
                                headings[fld].v
                             && headings[fld].len
                             && CS_STRNEQU(
                                        p,
                                        headings[fld].v,
                                        headings[fld].len
                                )
                        ) {
                                real_fields[fld] = 1;
                                p += headings[fld].len + 1;
                                break;
                        }

                if (fld >= FL_MAX) {

                        if (bad_headings++ >= 5) {
                                LP_ERRMSG (ERROR, E_FL_GARBAGE);
                                return (1);
                        }
                        LP_ERRMSG1 (WARNING, E_FL_HEADING, line);

                } else switch (fld) {

                        case FL_IGN_P:
                        case FL_NAME_P:
                                break;
                        case FL_CMD_P:
                                flbuf.command = strdup(strip(p));
                                break;
                        case FL_TYPE_P:
                                flbuf.type = s_to_filtertype(strip(p));
                                break;
                        case FL_PTYPS_P:
                                flbuf.printer_types = getlist(p, LP_WS, LP_SEP);
                                break;
                        case FL_ITYPS_P:
                                flbuf.input_types = getlist(p, LP_WS, LP_SEP);
                                break;
                        case FL_OTYPS_P:
                                flbuf.output_types = getlist(p, LP_WS, LP_SEP);
                                break;
                        case FL_PRTRS_P:
                                flbuf.printers = getlist(p, LP_WS, LP_SEP);
                                break;
                        case FL_TMPS_P:
                                if (flbuf.templates) {
                                        char                    **temp;

                                        temp = getlist(p, "", LP_SEP);
                                        mergelist (&(flbuf.templates), temp);
                                        freelist (temp);
                                } else
                                        flbuf.templates = getlist(p, "", LP_SEP);
                                break;

                }

        }
        if (ferror(input)) {
                LP_ERRMSG (ERROR, E_FL_READ);
                return (1);
        }

        /*
         * We have the input stored, now get the current copy of the
         * filter(s). If no filter exists, we create it.
         */

        if (STREQU(NAME_ALL, filter)) {

                /*
                 * Adding ``all'' means changing all filters to reflect
                 * the information in the input. We'll preload the
                 * filters so that we know how many there are.
                 */
                if (
                        !(file = getfilterfile(FILTERTABLE))
                     || loadfilters(file) == -1
                ) {
                        switch (errno) {
                        case ENOENT:
                                LP_ERRMSG (ERROR, E_FL_NOTALL);
                                break;
                        default:
                                same_complaints (FILTERTABLE, TABLE);
                                break;
                        }
                        return (1);
                }

                store = (FILTER *)malloc((nfilters + 1) * sizeof(FILTER));
                if (!store) {
                        LP_ERRMSG (ERROR, E_LP_MALLOC);
                        return (1);
                }

                for (ps = store; (pf = getfilter(filter)); )
                        *ps++ = *pf;
                ps->name = 0;

                switch (errno) {
                case ENOENT:
                        if (ps - store != nfilters) {
                                LP_ERRMSG1 (
                                        ERROR,
                                        E_FL_STRANGE,
                                        getfilterfile(FILTERTABLE)
                                );
                                return (1);
                        }
                        break;
                default:
                        same_complaints (FILTERTABLE, TABLE);
                        return (1);
                }

        } else {

                store = (FILTER *)malloc(2 * sizeof(FILTER));
                if (!store) {
                        LP_ERRMSG (ERROR, E_LP_MALLOC);
                        return (1);
                }

                if ((pf = getfilter(filter))) {
                        store[0] = *pf;
                } else
                        switch (errno) {
                        case ENOENT:
                                /*
                                 * We must be adding a new filter, so
                                 * set up default values. Check that
                                 * we'll have something reasonable to add.
                                 */
                                pf = store;
                                pf->name = strdup(filter);
                                pf->command = 0;
                                pf->type = fl_slow;
                                pf->printer_types = 0;
                                pf->printers = 0;
                                pf->input_types = 0;
                                pf->output_types = 0;
                                pf->templates = 0;
                                if (!flbuf.command) {
                                        LP_ERRMSG (ERROR, E_FL_NOCMD);
                                        return (1);
                                }
                                break;
                        default:
                                same_complaints (FILTERTABLE, TABLE);
                                return (1);
                        }

                store[1].name = 0;

        }

        at_least_one = ret = 0;
        for (ps = store; ps->name; ps++) {

                for (fld = 0; fld < FL_MAX; fld++)
                        if (real_fields[fld]) switch(fld) {
                        case FL_IGN_P:
                        case FL_NAME_P:
                                break;
                        case FL_CMD_P:
                                ps->command = flbuf.command;
                                break;
                        case FL_TYPE_P:
                                ps->type = flbuf.type;
                                break;
                        case FL_PTYPS_P:
                                ps->printer_types = flbuf.printer_types;
                                break;
                        case FL_ITYPS_P:
                                ps->input_types = flbuf.input_types;
                                break;
                        case FL_OTYPS_P:
                                ps->output_types = flbuf.output_types;
                                break;
                        case FL_PRTRS_P:
                                ps->printers = flbuf.printers;
                                break;
                        case FL_TMPS_P:
                                ps->templates = flbuf.templates;
                                break;
                        }

                if (putfilter(ps->name, ps) == -1) {
                        if (errno == EBADF)  switch (lp_errno) {
                        case LP_ETEMPLATE:
                                LP_ERRMSG (ERROR, E_FL_BADTEMPLATE);
                                break;
                        case LP_EKEYWORD:
                                LP_ERRMSG (ERROR, E_FL_BADKEY);
                                break;
                        case LP_EPATTERN:
                                LP_ERRMSG (ERROR, E_FL_BADPATT);
                                break;
                        case LP_EREGEX:
                        {
                                char *                  why;

                                extern int              regerrno;


                                switch (regerrno) {
                                case 11:
                                        why = "range endpoint too large";
                                        break;
                                case 16:
                                        why = "bad number";
                                        break;
                                case 25:
                                        why = "\"\\digit\" out of range";
                                        break;
                                case 36:
                                        why = "illegal or missing delimiter";
                                        break;
                                case 41:
                                        why = "no remembered search string";
                                        break;
                                case 42:
                                        why = "\\(...\\) imbalance";
                                        break;
                                case 43:
                                        why = "too many \\(";
                                        break;
                                case 44:
                                        why = "more than 2 numbers given in \\{...\\}";
                                        break;
                                case 45:
                                        why = "} expected after \\";
                                        break;
                                case 46:
                                        why = "first number exceeds second in \\{...\\}";
                                        break;
                                case 49:
                                        why = "[...] imbalance";
                                        break;
                                case 50:
                                        why = "regular expression overflow";
                                        break;
                                }
                                LP_ERRMSG1 (ERROR, E_FL_BADREGEX, why);
                                break;
                        }
                        case LP_ERESULT:
                                LP_ERRMSG (ERROR, E_FL_BADRESULT);
                                break;
                        case LP_ENOMEM:
                                errno = ENOMEM;
                                same_complaints (FILTERTABLE, TABLE);
                                break;
                        } else
                                same_complaints (FILTERTABLE, TABLE);
                        ret = 1;
                        break;
                } else
                        at_least_one = 1;

        }

        if (at_least_one)
                (void)alert_spooler ();

        return (ret);
}

/**
 ** reload_filter()
 **/

int                     reload_filter (filter)
        char                    *filter;
{
        register FILTER         *pf,
                                *store,
                                *ps;

        char                    *factory_file;

        int                     ret,
                                at_least_one;

        /*
         * ``Manually'' load the archived filters, so that a call
         * to "getfilter()" will read from them instead of the regular
         * table.
         */
        if (
                !(factory_file = getfilterfile(FILTERTABLE_I))
             || loadfilters(factory_file) == -1
        ) {
                switch (errno) {
                case ENOENT:
                        LP_ERRMSG (ERROR, E_FL_NOFACTY);
                        break;
                default:
                        same_complaints (FILTERTABLE_I, TABLE_I);
                        break;
                }
                return (1);
        }

        if (STREQU(NAME_ALL, filter)) {

                store = (FILTER *)malloc((nfilters + 1) * sizeof(FILTER));
                if (!store) {
                        LP_ERRMSG (ERROR, E_LP_MALLOC);
                        return (1);
                }

                for (ps = store; (pf = getfilter(filter)); )
                        *ps++ = *pf;
                ps->name = 0;

                switch (errno) {
                case ENOENT:
                        if (ps - store != nfilters) {
                                LP_ERRMSG1 (
                                        ERROR,
                                        E_FL_STRANGE,
                                        getfilterfile(FILTERTABLE_I)
                                );
                                return (1);
                        }
                        break;
                default:
                        same_complaints (FILTERTABLE_I, TABLE_I);
                        return (1);
                }

        } else {

                store = (FILTER *)malloc(2 * sizeof(FILTER));
                if (!store) {
                        LP_ERRMSG (ERROR, E_LP_MALLOC);
                        return (1);
                }

                if (!(pf = getfilter(filter))) switch (errno) {
                case ENOENT:
                        LP_ERRMSG (ERROR, E_FL_FACTYNM);
                        return (1);
                default:
                        same_complaints (FILTERTABLE_I, TABLE_I);
                        return (1);
                }

                store[0] = *pf;
                store[1].name = 0;

        }

        /*
         * Having stored the archived filter(s) in our own area, clear
         * the currently loaded table so that the subsequent calls to
         * "putfilter()" will read in the regular table.
         */
        trash_filters ();

        at_least_one = ret = 0;
        for (ps = store; ps->name; ps++)
                if (putfilter(ps->name, ps) == -1) {
                        same_complaints (FILTERTABLE, TABLE);
                        ret = 1;
                        break;
                } else
                        at_least_one = 1;

        if (at_least_one)
                (void)alert_spooler ();

        return (ret);
}

/**
 ** delete_filter()
 **/

int                     delete_filter (filter)
        char                    *filter;
{
        if (delfilter(filter) == -1) switch (errno) {
        case ENOENT:
                LP_ERRMSG1 (ERROR, E_FL_UNKFILT, filter);
                return (1);
        default:
                same_complaints (FILTERTABLE, TABLE);
                return (1);
        }

        (void)alert_spooler ();

        return (0);
}

/**
 ** list_filter()
 **/

static void             _list_filter();

int                     list_filter (filter)
        char                    *filter;
{
        register FILTER         *pf;

        char                    *nl;

        if (STREQU(NAME_ALL, filter)) {

                nl = "";
                while ((pf = getfilter(filter))) {
                        printf (gettext("%s(Filter \"%s\")\n"), nl, pf->name);
                        _list_filter (pf);
                        nl = "\n";
                }

                switch (errno) {
                case ENOENT:
                        return (0);
                default:
                        same_complaints (FILTERTABLE, TABLE);
                        return (1);
                }

        } else {

                if ((pf = getfilter(filter))) {
                        _list_filter (pf);
                        return (0);
                }

                switch (errno) {
                case ENOENT:
                        LP_ERRMSG1 (ERROR, E_FL_UNKFILT, filter);
                        return (1);
                default:
                        same_complaints (FILTERTABLE, TABLE);
                        return (1);
                }

        }
}

static void             _list_filter (pf)
        register FILTER         *pf;
{
        register char           **pp,
                                *sep;

        register int            fld;

        char *                  head;


        for (fld = 0; fld < FL_MAX_P; fld++) switch (fld) {
        case FL_IGN_P:
        case FL_NAME_P:
                break;
        case FL_CMD_P:
                printf (
                        "%s %s\n",
                        headings[fld].v,
                        (pf->command? pf->command : "")
                );
                break;
        case FL_TYPE_P:
                printf (
                        "%s %s\n",
                        headings[fld].v,
                        (pf->type == fl_fast? FL_FAST : FL_SLOW)
                );
                break;
        case FL_PTYPS_P:
                pp = pf->printer_types;
                goto Lists;
        case FL_ITYPS_P:
                pp = pf->input_types;
                goto Lists;
        case FL_OTYPS_P:
                pp = pf->output_types;
                goto Lists;
        case FL_PRTRS_P:
                pp = pf->printers;
Lists:          printlist_qsep = 1;
                printlist_setup ("", "", LP_SEP, "");
                printf ("%s ", headings[fld].v);
                printlist (stdout, pp);
                printf ("\n");
                break;
        case FL_TMPS_P:
                head = makestr(headings[fld].v, " ", (char *)0);
                printlist_qsep = 1;
                printlist_setup (head, "", "\n", "\n");
                printlist (stdout, pf->templates);
                break;
        }

        return;
}

/**
 ** opt() - GENERATE OPTION FROM FUNCTION NAME
 **/

static char             *opt (fnc)
        int                     (*fnc)();
{
        if (fnc == add_filter)
                return ("-F");
        else if (fnc == reload_filter)
                return ("-i");
        else if (fnc == list_filter)
                return ("-l");
        else if (fnc == delete_filter)
                return ("-x");
        else
                return ("-?");
}

/**
 ** alert_spooler() - TELL SPOOLER TO LOAD FILTER TABLE
 **/

static void             alert_spooler ()
{
        char                    msgbuf[MSGMAX];

        int                     mtype;

        short                   status;

        /*
         * If the attempt to open a message queue to the
         * Spooler fails, assume it isn't running and just
         * return--don't say anything, `cause the user may
         * know. Any other failure deserves an error message.
         */

        if (mopen() == -1)
                return;

        (void)putmessage (msgbuf, S_LOAD_FILTER_TABLE);

        if (msend(msgbuf) == -1)
                goto Error;
        if (mrecv(msgbuf, MSGMAX) == -1)
                goto Error;

        mtype = getmessage(msgbuf, R_LOAD_FILTER_TABLE, &status);
        if (mtype != R_LOAD_FILTER_TABLE) {
                LP_ERRMSG1 (ERROR, E_LP_BADREPLY, mtype);
                (void)mclose ();
                exit (1);
        }

        if (status == MOK)
                goto NoError;

Error:  LP_ERRMSG (ERROR, E_FL_NOSPLOAD);

NoError:(void)mclose ();
        return;

}

/**
 ** same_complaints() - PRINT COMMON ERROR MESSAGES
 **/

static void             same_complaints (table, type)
        char                    *table;
        int                     type;
{
        switch (errno) {
        case EACCES:
                if (type == TABLE)
                        LP_ERRMSG1 (
                                ERROR,
                                E_FL_ACCESS,
                                getfilterfile(table)
                        );
                else
                        LP_ERRMSG1 (
                                ERROR,
                                E_FL_ACCESSI,
                                getfilterfile(table)
                        );
                break;
        case EAGAIN:
        case EDEADLK:
                LP_ERRMSG1 (ERROR, E_LP_AGAIN, getfilterfile(table));
                break;
        default:
                LP_ERRMSG2 (
                        ERROR,
                        E_FL_UNKNOWN,
                        getfilterfile(table),
                        strerror(errno)
                );
                break;
        }
        return;
}