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