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

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)lpfilter.c	1.6	98/03/12 SMI"	/* SVr4.0 1.14	*/

#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"

extern char		*strerror(),
			*malloc();

extern int		sys_nerr;

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;


	(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);
		}
		(p = "-X")[1] = optopt;
		if (strchr(OPT_LIST, optopt))
			LP_ERRMSG1 (ERROR, E_LP_OPTARG, p);
		else
			LP_ERRMSG1 (ERROR, E_LP_OPTION, p);
		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),
			(errno < sys_nerr? strerror(errno): "unknown")
		);
		break;
	}
	return;
}
