1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1983, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include "lp.cdefs.h"		/* A cross-platform version of <sys/cdefs.h> */
34#include <sys/param.h>
35
36#include <ctype.h>
37#include <dirent.h>
38#include <err.h>
39#include <grp.h>
40#include <setjmp.h>
41#include <signal.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <syslog.h>
45#include <string.h>
46#include <unistd.h>
47#include <histedit.h>
48
49#include "lp.h"
50#include "lpc.h"
51#include "extern.h"
52
53#ifndef LPR_OPER
54#define LPR_OPER	"operator"	/* group name of lpr operators */
55#endif
56
57/*
58 * lpc -- line printer control program
59 */
60
61#define MAX_CMDLINE	200
62#define MAX_MARGV	20
63static int	fromatty;
64
65static char	cmdline[MAX_CMDLINE];
66static int	margc;
67static char	*margv[MAX_MARGV];
68uid_t		uid, euid;
69
70int			 main(int _argc, char *_argv[]);
71static void		 cmdscanner(void);
72static struct cmd	*getcmd(const char *_name);
73static void		 intr(int _signo);
74static void		 makeargv(void);
75static int		 ingroup(const char *_grname);
76
77int
78main(int argc, char *argv[])
79{
80	register struct cmd *c;
81
82	euid = geteuid();
83	uid = getuid();
84	PRIV_END
85	progname = argv[0];
86	openlog("lpd", 0, LOG_LPR);
87
88	if (--argc > 0) {
89		c = getcmd(*++argv);
90		if (c == (struct cmd *)-1) {
91			printf("?Ambiguous command\n");
92			exit(1);
93		}
94		if (c == NULL) {
95			printf("?Invalid command\n");
96			exit(1);
97		}
98		if ((c->c_opts & LPC_PRIVCMD) && getuid() &&
99		    ingroup(LPR_OPER) == 0) {
100			printf("?Privileged command\n");
101			exit(1);
102		}
103		if (c->c_generic != NULL)
104			generic(c->c_generic, c->c_opts, c->c_handler,
105			    argc, argv);
106		else
107			(*c->c_handler)(argc, argv);
108		exit(0);
109	}
110	fromatty = isatty(fileno(stdin));
111	if (!fromatty)
112		signal(SIGINT, intr);
113	for (;;) {
114		cmdscanner();
115	}
116}
117
118static void
119intr(int signo __unused)
120{
121	/* (the '__unused' is just to avoid a compile-time warning) */
122	exit(0);
123}
124
125static const char *
126lpc_prompt(void)
127{
128	return ("lpc> ");
129}
130
131/*
132 * Command parser.
133 */
134static void
135cmdscanner(void)
136{
137	register struct cmd *c;
138	static EditLine *el;
139	static History *hist;
140	HistEvent he;
141	size_t len;
142	int num;
143	const char *bp;
144
145	num = 0;
146	bp = NULL;
147	el = NULL;
148	hist = NULL;
149	for (;;) {
150		if (fromatty) {
151			if (!el) {
152				el = el_init("lpc", stdin, stdout, stderr);
153				hist = history_init();
154				history(hist, &he, H_SETSIZE, 100);
155				el_set(el, EL_HIST, history, hist);
156				el_set(el, EL_EDITOR, "emacs");
157				el_set(el, EL_PROMPT, lpc_prompt);
158				el_set(el, EL_SIGNAL, 1);
159				el_source(el, NULL);
160				/*
161				 * EditLine init may call 'cgetset()' to set a
162				 * capability-db meant for termcap (eg: to set
163				 * terminal type 'xterm').  Reset that now, or
164				 * that same db-information will be used for
165				 * printcap (giving us an "xterm" printer, with
166				 * all kinds of invalid capabilities...).
167				 */
168				cgetset(NULL);
169			}
170			if ((bp = el_gets(el, &num)) == NULL || num == 0)
171				quit(0, NULL);
172
173			len = MIN(MAX_CMDLINE - 1, num);
174			memcpy(cmdline, bp, len);
175			cmdline[len] = 0;
176			history(hist, &he, H_ENTER, bp);
177
178		} else {
179			if (fgets(cmdline, MAX_CMDLINE, stdin) == NULL)
180				quit(0, NULL);
181			if (cmdline[0] == 0 || cmdline[0] == '\n')
182				break;
183		}
184
185		makeargv();
186		if (margc == 0)
187			continue;
188		if (el != NULL && el_parse(el, margc, (const char **)margv) != -1)
189			continue;
190
191		c = getcmd(margv[0]);
192		if (c == (struct cmd *)-1) {
193			printf("?Ambiguous command\n");
194			continue;
195		}
196		if (c == NULL) {
197			printf("?Invalid command\n");
198			continue;
199		}
200		if ((c->c_opts & LPC_PRIVCMD) && getuid() &&
201		    ingroup(LPR_OPER) == 0) {
202			printf("?Privileged command\n");
203			continue;
204		}
205
206		/*
207		 * Two different commands might have the same generic rtn
208		 * (eg: "clean" and "tclean"), and just use different
209		 * handler routines for distinct command-setup.  The handler
210		 * routine might also be set on a generic routine for
211		 * initial parameter processing.
212		 */
213		if (c->c_generic != NULL)
214			generic(c->c_generic, c->c_opts, c->c_handler,
215			    margc, margv);
216		else
217			(*c->c_handler)(margc, margv);
218	}
219}
220
221static struct cmd *
222getcmd(const char *name)
223{
224	register const char *p, *q;
225	register struct cmd *c, *found;
226	register int nmatches, longest;
227
228	longest = 0;
229	nmatches = 0;
230	found = NULL;
231	for (c = cmdtab; (p = c->c_name); c++) {
232		for (q = name; *q == *p++; q++)
233			if (*q == 0)		/* exact match? */
234				return(c);
235		if (!*q) {			/* the name was a prefix */
236			if (q - name > longest) {
237				longest = q - name;
238				nmatches = 1;
239				found = c;
240			} else if (q - name == longest)
241				nmatches++;
242		}
243	}
244	if (nmatches > 1)
245		return((struct cmd *)-1);
246	return(found);
247}
248
249/*
250 * Slice a string up into argc/argv.
251 */
252static void
253makeargv(void)
254{
255	register char *cp;
256	register char **argp = margv;
257	register int n = 0;
258
259	margc = 0;
260	for (cp = cmdline; *cp && (size_t)(cp - cmdline) < sizeof(cmdline) &&
261	    n < MAX_MARGV - 1; n++) {
262		while (isspace(*cp))
263			cp++;
264		if (*cp == '\0')
265			break;
266		*argp++ = cp;
267		margc += 1;
268		while (*cp != '\0' && !isspace(*cp))
269			cp++;
270		if (*cp == '\0')
271			break;
272		*cp++ = '\0';
273	}
274	*argp++ = NULL;
275}
276
277#define HELPINDENT (sizeof ("directory"))
278
279/*
280 * Help command.
281 */
282void
283help(int argc, char *argv[])
284{
285	register struct cmd *c;
286
287	if (argc == 1) {
288		register int i, j, w;
289		int columns, width = 0, lines;
290
291		printf("Commands may be abbreviated.  Commands are:\n\n");
292		for (c = cmdtab; c->c_name; c++) {
293			int len = strlen(c->c_name);
294
295			if (len > width)
296				width = len;
297		}
298		width = (width + 8) &~ 7;
299		columns = 80 / width;
300		if (columns == 0)
301			columns = 1;
302		lines = (NCMDS + columns - 1) / columns;
303		for (i = 0; i < lines; i++) {
304			for (j = 0; j < columns; j++) {
305				c = cmdtab + j * lines + i;
306				if (c->c_name)
307					printf("%s", c->c_name);
308				if (c + lines >= &cmdtab[NCMDS]) {
309					printf("\n");
310					break;
311				}
312				w = strlen(c->c_name);
313				while (w < width) {
314					w = (w + 8) &~ 7;
315					putchar('\t');
316				}
317			}
318		}
319		return;
320	}
321	while (--argc > 0) {
322		register char *arg;
323		arg = *++argv;
324		c = getcmd(arg);
325		if (c == (struct cmd *)-1)
326			printf("?Ambiguous help command %s\n", arg);
327		else if (c == (struct cmd *)0)
328			printf("?Invalid help command %s\n", arg);
329		else
330			printf("%-*s\t%s\n", (int) HELPINDENT,
331				c->c_name, c->c_help);
332	}
333}
334
335/*
336 * return non-zero if the user is a member of the given group
337 */
338static int
339ingroup(const char *grname)
340{
341	static struct group *gptr=NULL;
342	static int ngroups = 0;
343	static long ngroups_max;
344	static gid_t *groups;
345	register gid_t gid;
346	register int i;
347
348	if (gptr == NULL) {
349		if ((gptr = getgrnam(grname)) == NULL) {
350			warnx("warning: unknown group '%s'", grname);
351			return(0);
352		}
353		ngroups_max = sysconf(_SC_NGROUPS_MAX);
354		if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
355			err(1, "malloc");
356		ngroups = getgroups(ngroups_max, groups);
357		if (ngroups < 0)
358			err(1, "getgroups");
359	}
360	gid = gptr->gr_gid;
361	for (i = 0; i < ngroups; i++)
362		if (gid == groups[i])
363			return(1);
364	return(0);
365}
366
367/*
368 * Routine to get the information for a single printer (which will be
369 * called by the routines which implement individual commands).
370 * Note: This is for commands operating on a *single* printer.
371 */
372struct printer *
373setup_myprinter(char *pwanted, struct printer *pp, int sump_opts)
374{
375	int cdres, cmdstatus;
376
377	init_printer(pp);
378	cmdstatus = getprintcap(pwanted, pp);
379	switch (cmdstatus) {
380	default:
381		fatal(pp, "%s", pcaperr(cmdstatus));
382		/* NOTREACHED */
383	case PCAPERR_NOTFOUND:
384		printf("unknown printer %s\n", pwanted);
385		return (NULL);
386	case PCAPERR_TCOPEN:
387		printf("warning: %s: unresolved tc= reference(s)", pwanted);
388		break;
389	case PCAPERR_SUCCESS:
390		break;
391	}
392	if ((sump_opts & SUMP_NOHEADER) == 0)
393		printf("%s:\n", pp->printer);
394
395	if (sump_opts & SUMP_CHDIR_SD) {
396		PRIV_START
397		cdres = chdir(pp->spool_dir);
398		PRIV_END
399		if (cdres < 0) {
400			printf("\tcannot chdir to %s\n", pp->spool_dir);
401			free_printer(pp);
402			return (NULL);
403		}
404	}
405
406	return (pp);
407}
408