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