lpd.c revision 27593
1/*
2 * Copyright (c) 1983, 1993, 1994
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 * 3. All advertising materials mentioning features or use of this software
15 *    must display the following acknowledgement:
16 *	This product includes software developed by the University of
17 *	California, Berkeley and its contributors.
18 * 4. Neither the name of the University nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35#ifndef lint
36static char copyright[] =
37"@(#) Copyright (c) 1983, 1993, 1994\n\
38	The Regents of the University of California.  All rights reserved.\n";
39#endif /* not lint */
40
41#ifndef lint
42static char sccsid[] = "@(#)lpd.c	8.7 (Berkeley) 5/10/95";
43#endif /* not lint */
44
45/*
46 * lpd -- line printer daemon.
47 *
48 * Listen for a connection and perform the requested operation.
49 * Operations are:
50 *	\1printer\n
51 *		check the queue for jobs and print any found.
52 *	\2printer\n
53 *		receive a job from another machine and queue it.
54 *	\3printer [users ...] [jobs ...]\n
55 *		return the current state of the queue (short form).
56 *	\4printer [users ...] [jobs ...]\n
57 *		return the current state of the queue (long form).
58 *	\5printer person [users ...] [jobs ...]\n
59 *		remove jobs from the queue.
60 *
61 * Strategy to maintain protected spooling area:
62 *	1. Spooling area is writable only by daemon and spooling group
63 *	2. lpr runs setuid root and setgrp spooling group; it uses
64 *	   root to access any file it wants (verifying things before
65 *	   with an access call) and group id to know how it should
66 *	   set up ownership of files in the spooling area.
67 *	3. Files in spooling area are owned by root, group spooling
68 *	   group, with mode 660.
69 *	4. lpd, lpq and lprm run setuid daemon and setgrp spooling group to
70 *	   access files and printer.  Users can't get to anything
71 *	   w/o help of lpq and lprm programs.
72 */
73
74#include <sys/param.h>
75#include <sys/wait.h>
76#include <sys/types.h>
77#include <sys/socket.h>
78#include <sys/un.h>
79#include <sys/stat.h>
80#include <sys/file.h>
81#include <netinet/in.h>
82#include <arpa/inet.h>
83
84#include <netdb.h>
85#include <unistd.h>
86#include <syslog.h>
87#include <signal.h>
88#include <errno.h>
89#include <fcntl.h>
90#include <dirent.h>
91#include <stdio.h>
92#include <stdlib.h>
93#include <string.h>
94#include <ctype.h>
95#include "lp.h"
96#include "lp.local.h"
97#include "pathnames.h"
98#include "extern.h"
99
100int	lflag;				/* log requests flag */
101int	from_remote;			/* from remote socket */
102
103static void       reapchild __P((int));
104static void       mcleanup __P((int));
105static void       doit __P((void));
106static void       startup __P((void));
107static void       chkhost __P((struct sockaddr_in *));
108static int	  ckqueue __P((char *));
109
110int
111main(argc, argv)
112	int argc;
113	char **argv;
114{
115	int f, funix, finet, options, fromlen;
116	fd_set defreadfds;
117	struct sockaddr_un un, fromunix;
118	struct sockaddr_in sin, frominet;
119	int omask, lfd;
120
121	options = 0;
122	gethostname(host, sizeof(host));
123	name = argv[0];
124
125	while (--argc > 0) {
126		argv++;
127		if (argv[0][0] == '-')
128			switch (argv[0][1]) {
129			case 'd':
130				options |= SO_DEBUG;
131				break;
132			case 'l':
133				lflag++;
134				break;
135			}
136	}
137
138#ifndef DEBUG
139	/*
140	 * Set up standard environment by detaching from the parent.
141	 */
142	daemon(0, 0);
143#endif
144
145	openlog("lpd", LOG_PID, LOG_LPR);
146	syslog(LOG_INFO, "restarted");
147	(void) umask(0);
148	lfd = open(_PATH_MASTERLOCK, O_WRONLY|O_CREAT, 0644);
149	if (lfd < 0) {
150		syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
151		exit(1);
152	}
153	if (flock(lfd, LOCK_EX|LOCK_NB) < 0) {
154		if (errno == EWOULDBLOCK)	/* active deamon present */
155			exit(0);
156		syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
157		exit(1);
158	}
159	ftruncate(lfd, 0);
160	/*
161	 * write process id for others to know
162	 */
163	sprintf(line, "%u\n", getpid());
164	f = strlen(line);
165	if (write(lfd, line, f) != f) {
166		syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
167		exit(1);
168	}
169	signal(SIGCHLD, reapchild);
170	/*
171	 * Restart all the printers.
172	 */
173	startup();
174	(void) unlink(_PATH_SOCKETNAME);
175	funix = socket(AF_UNIX, SOCK_STREAM, 0);
176	if (funix < 0) {
177		syslog(LOG_ERR, "socket: %m");
178		exit(1);
179	}
180#define	mask(s)	(1 << ((s) - 1))
181	omask = sigblock(mask(SIGHUP)|mask(SIGINT)|mask(SIGQUIT)|mask(SIGTERM));
182	(void) umask(07);
183	signal(SIGHUP, mcleanup);
184	signal(SIGINT, mcleanup);
185	signal(SIGQUIT, mcleanup);
186	signal(SIGTERM, mcleanup);
187	memset(&un, 0, sizeof(un));
188	un.sun_family = AF_UNIX;
189	strcpy(un.sun_path, _PATH_SOCKETNAME);
190#ifndef SUN_LEN
191#define SUN_LEN(unp) (strlen((unp)->sun_path) + 2)
192#endif
193	if (bind(funix, (struct sockaddr *)&un, SUN_LEN(&un)) < 0) {
194		syslog(LOG_ERR, "ubind: %m");
195		exit(1);
196	}
197	(void) umask(0);
198	sigsetmask(omask);
199	FD_ZERO(&defreadfds);
200	FD_SET(funix, &defreadfds);
201	listen(funix, 5);
202	finet = socket(AF_INET, SOCK_STREAM, 0);
203	if (finet >= 0) {
204		struct servent *sp;
205
206		if (options & SO_DEBUG)
207			if (setsockopt(finet, SOL_SOCKET, SO_DEBUG, 0, 0) < 0) {
208				syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m");
209				mcleanup(0);
210			}
211		sp = getservbyname("printer", "tcp");
212		if (sp == NULL) {
213			syslog(LOG_ERR, "printer/tcp: unknown service");
214			mcleanup(0);
215		}
216		memset(&sin, 0, sizeof(sin));
217		sin.sin_family = AF_INET;
218		sin.sin_port = sp->s_port;
219		if (bind(finet, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
220			syslog(LOG_ERR, "bind: %m");
221			mcleanup(0);
222		}
223		FD_SET(finet, &defreadfds);
224		listen(finet, 5);
225	}
226	/*
227	 * Main loop: accept, do a request, continue.
228	 */
229	memset(&frominet, 0, sizeof(frominet));
230	memset(&fromunix, 0, sizeof(fromunix));
231	for (;;) {
232		int domain, nfds, s;
233		fd_set readfds;
234
235		FD_COPY(&defreadfds, &readfds);
236		nfds = select(20, &readfds, 0, 0, 0);
237		if (nfds <= 0) {
238			if (nfds < 0 && errno != EINTR)
239				syslog(LOG_WARNING, "select: %m");
240			continue;
241		}
242		if (FD_ISSET(funix, &readfds)) {
243			domain = AF_UNIX, fromlen = sizeof(fromunix);
244			s = accept(funix,
245			    (struct sockaddr *)&fromunix, &fromlen);
246		} else /* if (FD_ISSET(finet, &readfds)) */  {
247			domain = AF_INET, fromlen = sizeof(frominet);
248			s = accept(finet,
249			    (struct sockaddr *)&frominet, &fromlen);
250			if (frominet.sin_port == htons(20)) {
251				close(s);
252				continue;
253			}
254		}
255		if (s < 0) {
256			if (errno != EINTR)
257				syslog(LOG_WARNING, "accept: %m");
258			continue;
259		}
260		if (fork() == 0) {
261			signal(SIGCHLD, SIG_IGN);
262			signal(SIGHUP, SIG_IGN);
263			signal(SIGINT, SIG_IGN);
264			signal(SIGQUIT, SIG_IGN);
265			signal(SIGTERM, SIG_IGN);
266			(void) close(funix);
267			(void) close(finet);
268			dup2(s, 1);
269			(void) close(s);
270			if (domain == AF_INET) {
271				from_remote = 1;
272				chkhost(&frominet);
273			} else
274				from_remote = 0;
275			doit();
276			exit(0);
277		}
278		(void) close(s);
279	}
280}
281
282static void
283reapchild(signo)
284	int signo;
285{
286	union wait status;
287
288	while (wait3((int *)&status, WNOHANG, 0) > 0)
289		;
290}
291
292static void
293mcleanup(signo)
294	int signo;
295{
296	if (lflag)
297		syslog(LOG_INFO, "exiting");
298	unlink(_PATH_SOCKETNAME);
299	exit(0);
300}
301
302/*
303 * Stuff for handling job specifications
304 */
305char	*user[MAXUSERS];	/* users to process */
306int	users;			/* # of users in user array */
307int	requ[MAXREQUESTS];	/* job number of spool entries */
308int	requests;		/* # of spool requests */
309char	*person;		/* name of person doing lprm */
310
311char	fromb[MAXHOSTNAMELEN];	/* buffer for client's machine name */
312char	cbuf[BUFSIZ];		/* command line buffer */
313char	*cmdnames[] = {
314	"null",
315	"printjob",
316	"recvjob",
317	"displayq short",
318	"displayq long",
319	"rmjob"
320};
321
322static void
323doit()
324{
325	register char *cp;
326	register int n;
327
328	for (;;) {
329		cp = cbuf;
330		do {
331			if (cp >= &cbuf[sizeof(cbuf) - 1])
332				fatal("Command line too long");
333			if ((n = read(1, cp, 1)) != 1) {
334				if (n < 0)
335					fatal("Lost connection");
336				return;
337			}
338		} while (*cp++ != '\n');
339		*--cp = '\0';
340		cp = cbuf;
341		if (lflag) {
342			if (*cp >= '\1' && *cp <= '\5')
343				syslog(LOG_INFO, "%s requests %s %s",
344					from, cmdnames[*cp], cp+1);
345			else
346				syslog(LOG_INFO, "bad request (%d) from %s",
347					*cp, from);
348		}
349		switch (*cp++) {
350		case '\1':	/* check the queue and print any jobs there */
351			printer = cp;
352			printjob();
353			break;
354		case '\2':	/* receive files to be queued */
355			if (!from_remote) {
356				syslog(LOG_INFO, "illegal request (%d)", *cp);
357				exit(1);
358			}
359			printer = cp;
360			recvjob();
361			break;
362		case '\3':	/* display the queue (short form) */
363		case '\4':	/* display the queue (long form) */
364			printer = cp;
365			while (*cp) {
366				if (*cp != ' ') {
367					cp++;
368					continue;
369				}
370				*cp++ = '\0';
371				while (isspace(*cp))
372					cp++;
373				if (*cp == '\0')
374					break;
375				if (isdigit(*cp)) {
376					if (requests >= MAXREQUESTS)
377						fatal("Too many requests");
378					requ[requests++] = atoi(cp);
379				} else {
380					if (users >= MAXUSERS)
381						fatal("Too many users");
382					user[users++] = cp;
383				}
384			}
385			displayq(cbuf[0] - '\3');
386			exit(0);
387		case '\5':	/* remove a job from the queue */
388			if (!from_remote) {
389				syslog(LOG_INFO, "illegal request (%d)", *cp);
390				exit(1);
391			}
392			printer = cp;
393			while (*cp && *cp != ' ')
394				cp++;
395			if (!*cp)
396				break;
397			*cp++ = '\0';
398			person = cp;
399			while (*cp) {
400				if (*cp != ' ') {
401					cp++;
402					continue;
403				}
404				*cp++ = '\0';
405				while (isspace(*cp))
406					cp++;
407				if (*cp == '\0')
408					break;
409				if (isdigit(*cp)) {
410					if (requests >= MAXREQUESTS)
411						fatal("Too many requests");
412					requ[requests++] = atoi(cp);
413				} else {
414					if (users >= MAXUSERS)
415						fatal("Too many users");
416					user[users++] = cp;
417				}
418			}
419			rmjob();
420			break;
421		}
422		fatal("Illegal service request");
423	}
424}
425
426/*
427 * Make a pass through the printcap database and start printing any
428 * files left from the last time the machine went down.
429 */
430static void
431startup()
432{
433	char *buf;
434	register char *cp;
435	int pid;
436	char *spooldirs[16];        /* Which spooldirs are active? */
437	int i;                      /* Printer index presently processed */
438	int j;                      /* Printer index of potential conflict */
439	char *spooldir;             /* Spooldir of present printer */
440	int  canfreespool;          /* Is the spooldir malloc()ed? */
441
442	/*
443	 * Restart the daemons and test for spooldir conflict.
444	 */
445	i = 0;
446	while (cgetnext(&buf, printcapdb) > 0) {
447
448		/* Check for duplicate spooldirs */
449		canfreespool = 1;
450		if (cgetstr(buf, "sd", &spooldir) <= 0) {
451			spooldir = _PATH_DEFSPOOL;
452			canfreespool = 0;
453		}
454		if (i < sizeof(spooldirs)/sizeof(spooldirs[0]))
455			spooldirs[i] = spooldir;
456		for (j = 0;
457			 j < MIN(i,sizeof(spooldirs)/sizeof(spooldirs[0]));
458			 j++) {
459			if (strcmp(spooldir, spooldirs[j]) == 0) {
460				syslog(LOG_ERR,
461					"startup: duplicate spool directories: %s",
462					spooldir);
463				mcleanup(0);
464			}
465		}
466		if (canfreespool && i >= sizeof(spooldirs)/sizeof(spooldirs[0]))
467			free(spooldir);
468		i++;
469		/* Spooldir test done */
470
471		if (ckqueue(buf) <= 0) {
472			free(buf);
473			continue;	/* no work to do for this printer */
474		}
475		for (cp = buf; *cp; cp++)
476			if (*cp == '|' || *cp == ':') {
477				*cp = '\0';
478				break;
479			}
480		if (lflag)
481			syslog(LOG_INFO, "work for %s", buf);
482		if ((pid = fork()) < 0) {
483			syslog(LOG_WARNING, "startup: cannot fork");
484			mcleanup(0);
485		}
486		if (!pid) {
487			printer = buf;
488			cgetclose();
489			printjob();
490			/* NOTREACHED */
491		}
492		else free(buf);
493	}
494}
495
496/*
497 * Make sure there's some work to do before forking off a child
498 */
499static int
500ckqueue(cap)
501	char *cap;
502{
503	register struct dirent *d;
504	DIR *dirp;
505	char *spooldir;
506
507	if (cgetstr(cap, "sd", &spooldir) == -1)
508		spooldir = _PATH_DEFSPOOL;
509	if ((dirp = opendir(spooldir)) == NULL)
510		return (-1);
511	while ((d = readdir(dirp)) != NULL) {
512		if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
513			continue;	/* daemon control files only */
514		closedir(dirp);
515		return (1);		/* found something */
516	}
517	closedir(dirp);
518	return (0);
519}
520
521#define DUMMY ":nobody::"
522
523/*
524 * Check to see if the from host has access to the line printer.
525 */
526static void
527chkhost(f)
528	struct sockaddr_in *f;
529{
530	register struct hostent *hp;
531	register FILE *hostf;
532	int first = 1;
533	int good = 0;
534
535	f->sin_port = ntohs(f->sin_port);
536	if (f->sin_family != AF_INET || f->sin_port >= IPPORT_RESERVED ||
537	    f->sin_port == htons(20))
538		fatal("Malformed from address");
539
540	/* Need real hostname for temporary filenames */
541	hp = gethostbyaddr((char *)&f->sin_addr,
542	    sizeof(struct in_addr), f->sin_family);
543	if (hp == NULL)
544		fatal("Host name for your address (%s) unknown",
545			inet_ntoa(f->sin_addr));
546
547	(void) strncpy(fromb, hp->h_name, sizeof(fromb) - 1);
548	from[sizeof(fromb) - 1] = '\0';
549	from = fromb;
550
551	/* Check for spoof, ala rlogind */
552	hp = gethostbyname(fromb);
553	if (!hp)
554		fatal("hostname for your address (%s) unknown",
555		    inet_ntoa(f->sin_addr));
556	for (; good == 0 && hp->h_addr_list[0] != NULL; hp->h_addr_list++) {
557		if (!bcmp(hp->h_addr_list[0], (caddr_t)&f->sin_addr,
558		    sizeof(f->sin_addr)))
559			good = 1;
560	}
561	if (good == 0)
562		fatal("address for your hostname (%s) not matched",
563		    inet_ntoa(f->sin_addr));
564
565	hostf = fopen(_PATH_HOSTSEQUIV, "r");
566again:
567	if (hostf) {
568		if (__ivaliduser(hostf, f->sin_addr.s_addr,
569		    DUMMY, DUMMY) == 0) {
570			(void) fclose(hostf);
571			return;
572		}
573		(void) fclose(hostf);
574	}
575	if (first == 1) {
576		first = 0;
577		hostf = fopen(_PATH_HOSTSLPD, "r");
578		goto again;
579	}
580	fatal("Your host does not have line printer access");
581	/*NOTREACHED*/
582}
583