xref: /freebsd/usr.sbin/lpr/lpd/lpd.c (revision 23f282aa31e9b6fceacd449020e936e98d6f2298)
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
36 static const 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
42 #if 0
43 static char sccsid[] = "@(#)lpd.c	8.7 (Berkeley) 5/10/95";
44 #endif
45 static const char rcsid[] =
46   "$FreeBSD$";
47 #endif /* not lint */
48 
49 /*
50  * lpd -- line printer daemon.
51  *
52  * Listen for a connection and perform the requested operation.
53  * Operations are:
54  *	\1printer\n
55  *		check the queue for jobs and print any found.
56  *	\2printer\n
57  *		receive a job from another machine and queue it.
58  *	\3printer [users ...] [jobs ...]\n
59  *		return the current state of the queue (short form).
60  *	\4printer [users ...] [jobs ...]\n
61  *		return the current state of the queue (long form).
62  *	\5printer person [users ...] [jobs ...]\n
63  *		remove jobs from the queue.
64  *
65  * Strategy to maintain protected spooling area:
66  *	1. Spooling area is writable only by daemon and spooling group
67  *	2. lpr runs setuid root and setgrp spooling group; it uses
68  *	   root to access any file it wants (verifying things before
69  *	   with an access call) and group id to know how it should
70  *	   set up ownership of files in the spooling area.
71  *	3. Files in spooling area are owned by root, group spooling
72  *	   group, with mode 660.
73  *	4. lpd, lpq and lprm run setuid daemon and setgrp spooling group to
74  *	   access files and printer.  Users can't get to anything
75  *	   w/o help of lpq and lprm programs.
76  */
77 
78 #include <sys/param.h>
79 #include <sys/wait.h>
80 #include <sys/types.h>
81 #include <sys/socket.h>
82 #include <sys/un.h>
83 #include <sys/stat.h>
84 #include <sys/file.h>
85 #include <netinet/in.h>
86 #include <arpa/inet.h>
87 
88 #include <netdb.h>
89 #include <unistd.h>
90 #include <syslog.h>
91 #include <signal.h>
92 #include <err.h>
93 #include <errno.h>
94 #include <fcntl.h>
95 #include <dirent.h>
96 #include <stdio.h>
97 #include <stdlib.h>
98 #include <string.h>
99 #include <sysexits.h>
100 #include <ctype.h>
101 #include "lp.h"
102 #include "lp.local.h"
103 #include "pathnames.h"
104 #include "extern.h"
105 
106 int	lflag;				/* log requests flag */
107 int	pflag;				/* no incoming port flag */
108 int	from_remote;			/* from remote socket */
109 
110 int		  main __P((int, char **));
111 static void       reapchild __P((int));
112 static void       mcleanup __P((int));
113 static void       doit __P((void));
114 static void       startup __P((void));
115 static void       chkhost __P((struct sockaddr_in *));
116 static int	  ckqueue __P((struct printer *));
117 static void	  usage __P((void));
118 /* From rcmd.c: */
119 int		  __ivaliduser __P((FILE *, u_long, const char *,
120 				    const char *));
121 
122 uid_t	uid, euid;
123 
124 int
125 main(argc, argv)
126 	int argc;
127 	char **argv;
128 {
129 	int f, funix, finet, options, fromlen, i, errs;
130 	fd_set defreadfds;
131 	struct sockaddr_un un, fromunix;
132 	struct sockaddr_in sin, frominet;
133 	int lfd;
134 	sigset_t omask, nmask;
135 	struct servent *sp, serv;
136 
137 	euid = geteuid();	/* these shouldn't be different */
138 	uid = getuid();
139 	options = 0;
140 	gethostname(host, sizeof(host));
141 
142 	name = "lpd";
143 
144 	if (euid != 0)
145 		errx(EX_NOPERM,"must run as root");
146 
147 	errs = 0;
148 	while ((i = getopt(argc, argv, "dlp")) != -1)
149 		switch (i) {
150 		case 'd':
151 			options |= SO_DEBUG;
152 			break;
153 		case 'l':
154 			lflag++;
155 			break;
156 		case 'p':
157 			pflag++;
158 			break;
159 		default:
160 			errs++;
161 		}
162 	argc -= optind;
163 	argv += optind;
164 	if (errs)
165 		usage();
166 
167 	if (argc == 1) {
168 		if ((i = atoi(argv[0])) == 0)
169 			usage();
170 		if (i < 0 || i > USHRT_MAX)
171 			errx(EX_USAGE, "port # %d is invalid", i);
172 
173 		serv.s_port = htons(i);
174 		sp = &serv;
175 		argc--;
176 	} else {
177 		sp = getservbyname("printer", "tcp");
178 		if (sp == NULL)
179 			errx(EX_OSFILE, "printer/tcp: unknown service");
180 	}
181 
182 	if (argc != 0)
183 		usage();
184 
185 	/*
186 	 * We run chkprintcap right away to catch any errors and blat them
187 	 * to stderr while we still have it open, rather than sending them
188 	 * to syslog and leaving the user wondering why lpd started and
189 	 * then stopped.  There should probably be a command-line flag to
190 	 * ignore errors from chkprintcap.
191 	 */
192 	{
193 		pid_t pid;
194 		int status;
195 		pid = fork();
196 		if (pid < 0) {
197 			err(EX_OSERR, "cannot fork");
198 		} else if (pid == 0) {	/* child */
199 			execl(_PATH_CHKPRINTCAP, _PATH_CHKPRINTCAP, (char *)0);
200 			err(EX_OSERR, "cannot execute %s", _PATH_CHKPRINTCAP);
201 		}
202 		if (waitpid(pid, &status, 0) < 0) {
203 			err(EX_OSERR, "cannot wait");
204 		}
205 		if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
206 			errx(EX_OSFILE, "%d errors in printcap file, exiting",
207 			     WEXITSTATUS(status));
208 	}
209 
210 #ifndef DEBUG
211 	/*
212 	 * Set up standard environment by detaching from the parent.
213 	 */
214 	daemon(0, 0);
215 #endif
216 
217 	openlog("lpd", LOG_PID, LOG_LPR);
218 	syslog(LOG_INFO, "restarted");
219 	(void) umask(0);
220 	/*
221 	 * NB: This depends on O_NONBLOCK semantics doing the right thing;
222 	 * i.e., applying only to the O_EXLOCK and not to the rest of the
223 	 * open/creation.  As of 1997-12-02, this is the case for commonly-
224 	 * used filesystems.  There are other places in this code which
225 	 * make the same assumption.
226 	 */
227 	lfd = open(_PATH_MASTERLOCK, O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK,
228 		   LOCK_FILE_MODE);
229 	if (lfd < 0) {
230 		if (errno == EWOULDBLOCK)	/* active deamon present */
231 			exit(0);
232 		syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
233 		exit(1);
234 	}
235 	fcntl(lfd, F_SETFL, 0);	/* turn off non-blocking mode */
236 	ftruncate(lfd, 0);
237 	/*
238 	 * write process id for others to know
239 	 */
240 	sprintf(line, "%u\n", getpid());
241 	f = strlen(line);
242 	if (write(lfd, line, f) != f) {
243 		syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
244 		exit(1);
245 	}
246 	signal(SIGCHLD, reapchild);
247 	/*
248 	 * Restart all the printers.
249 	 */
250 	startup();
251 	(void) unlink(_PATH_SOCKETNAME);
252 	funix = socket(AF_UNIX, SOCK_STREAM, 0);
253 	if (funix < 0) {
254 		syslog(LOG_ERR, "socket: %m");
255 		exit(1);
256 	}
257 
258 	sigemptyset(&nmask);
259 	sigaddset(&nmask, SIGHUP);
260 	sigaddset(&nmask, SIGINT);
261 	sigaddset(&nmask, SIGQUIT);
262 	sigaddset(&nmask, SIGTERM);
263 	sigprocmask(SIG_BLOCK, &nmask, &omask);
264 
265 	(void) umask(07);
266 	signal(SIGHUP, mcleanup);
267 	signal(SIGINT, mcleanup);
268 	signal(SIGQUIT, mcleanup);
269 	signal(SIGTERM, mcleanup);
270 	memset(&un, 0, sizeof(un));
271 	un.sun_family = AF_UNIX;
272 	strcpy(un.sun_path, _PATH_SOCKETNAME);
273 #ifndef SUN_LEN
274 #define SUN_LEN(unp) (strlen((unp)->sun_path) + 2)
275 #endif
276 	if (bind(funix, (struct sockaddr *)&un, SUN_LEN(&un)) < 0) {
277 		syslog(LOG_ERR, "ubind: %m");
278 		exit(1);
279 	}
280 	(void) umask(0);
281 	sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
282 	FD_ZERO(&defreadfds);
283 	FD_SET(funix, &defreadfds);
284 	listen(funix, 5);
285 	if (pflag == 0) {
286 		finet = socket(AF_INET, SOCK_STREAM, 0);
287 		if (finet >= 0) {
288 			if (options & SO_DEBUG &&
289 			    setsockopt(finet, SOL_SOCKET, SO_DEBUG, 0, 0) < 0) {
290 				syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m");
291 				mcleanup(0);
292 			}
293 			memset(&sin, 0, sizeof(sin));
294 			sin.sin_family = AF_INET;
295 			sin.sin_port = sp->s_port;
296 			if (bind(finet, (struct sockaddr *)&sin,
297 			    sizeof(sin)) < 0) {
298 				syslog(LOG_ERR, "bind: %m");
299 				mcleanup(0);
300 			}
301 			FD_SET(finet, &defreadfds);
302 			listen(finet, 5);
303 		}
304 	}
305 	/*
306 	 * Main loop: accept, do a request, continue.
307 	 */
308 	memset(&frominet, 0, sizeof(frominet));
309 	memset(&fromunix, 0, sizeof(fromunix));
310 	/*
311 	 * XXX - should be redone for multi-protocol
312 	 */
313 	for (;;) {
314 		int domain, nfds, s;
315 		fd_set readfds;
316 
317 		FD_COPY(&defreadfds, &readfds);
318 		nfds = select(20, &readfds, 0, 0, 0);
319 		if (nfds <= 0) {
320 			if (nfds < 0 && errno != EINTR)
321 				syslog(LOG_WARNING, "select: %m");
322 			continue;
323 		}
324 		if (FD_ISSET(funix, &readfds)) {
325 			domain = AF_UNIX, fromlen = sizeof(fromunix);
326 			s = accept(funix,
327 			    (struct sockaddr *)&fromunix, &fromlen);
328 		} else if (pflag == 0) /* if (FD_ISSET(finet, &readfds)) */  {
329 			domain = AF_INET, fromlen = sizeof(frominet);
330 			s = accept(finet,
331 			    (struct sockaddr *)&frominet, &fromlen);
332 			if (frominet.sin_port == htons(20)) {
333 				close(s);
334 				continue;
335 			}
336 		}
337 		if (s < 0) {
338 			if (errno != EINTR)
339 				syslog(LOG_WARNING, "accept: %m");
340 			continue;
341 		}
342 		if (fork() == 0) {
343 			signal(SIGCHLD, SIG_IGN);
344 			signal(SIGHUP, SIG_IGN);
345 			signal(SIGINT, SIG_IGN);
346 			signal(SIGQUIT, SIG_IGN);
347 			signal(SIGTERM, SIG_IGN);
348 			(void) close(funix);
349 			if (pflag == 0) {
350 				(void) close(finet);
351 			}
352 			dup2(s, 1);
353 			(void) close(s);
354 			if (domain == AF_INET) {
355 				from_remote = 1;
356 				chkhost(&frominet);
357 			} else
358 				from_remote = 0;
359 			doit();
360 			exit(0);
361 		}
362 		(void) close(s);
363 	}
364 }
365 
366 static void
367 reapchild(signo)
368 	int signo;
369 {
370 	union wait status;
371 
372 	while (wait3((int *)&status, WNOHANG, 0) > 0)
373 		;
374 }
375 
376 static void
377 mcleanup(signo)
378 	int signo;
379 {
380 	if (lflag)
381 		syslog(LOG_INFO, "exiting");
382 	unlink(_PATH_SOCKETNAME);
383 	exit(0);
384 }
385 
386 /*
387  * Stuff for handling job specifications
388  */
389 char	*user[MAXUSERS];	/* users to process */
390 int	users;			/* # of users in user array */
391 int	requ[MAXREQUESTS];	/* job number of spool entries */
392 int	requests;		/* # of spool requests */
393 char	*person;		/* name of person doing lprm */
394 
395 char	fromb[MAXHOSTNAMELEN];	/* buffer for client's machine name */
396 char	cbuf[BUFSIZ];		/* command line buffer */
397 char	*cmdnames[] = {
398 	"null",
399 	"printjob",
400 	"recvjob",
401 	"displayq short",
402 	"displayq long",
403 	"rmjob"
404 };
405 
406 static void
407 doit()
408 {
409 	char *cp, *printer;
410 	int n;
411 	int status;
412 	struct printer myprinter, *pp = &myprinter;
413 
414 	init_printer(&myprinter);
415 
416 	for (;;) {
417 		cp = cbuf;
418 		do {
419 			if (cp >= &cbuf[sizeof(cbuf) - 1])
420 				fatal(0, "Command line too long");
421 			if ((n = read(1, cp, 1)) != 1) {
422 				if (n < 0)
423 					fatal(0, "Lost connection");
424 				return;
425 			}
426 		} while (*cp++ != '\n');
427 		*--cp = '\0';
428 		cp = cbuf;
429 		if (lflag) {
430 			if (*cp >= '\1' && *cp <= '\5')
431 				syslog(LOG_INFO, "%s requests %s %s",
432 					from, cmdnames[(u_char)*cp], cp+1);
433 			else
434 				syslog(LOG_INFO, "bad request (%d) from %s",
435 					*cp, from);
436 		}
437 		switch (*cp++) {
438 		case CMD_CHECK_QUE: /* check the queue, print any jobs there */
439 			startprinting(cp);
440 			break;
441 		case CMD_TAKE_THIS: /* receive files to be queued */
442 			if (!from_remote) {
443 				syslog(LOG_INFO, "illegal request (%d)", *cp);
444 				exit(1);
445 			}
446 			recvjob(cp);
447 			break;
448 		case CMD_SHOWQ_SHORT: /* display the queue (short form) */
449 		case CMD_SHOWQ_LONG: /* display the queue (long form) */
450 			/* XXX - this all needs to be redone. */
451 			printer = cp;
452 			while (*cp) {
453 				if (*cp != ' ') {
454 					cp++;
455 					continue;
456 				}
457 				*cp++ = '\0';
458 				while (isspace(*cp))
459 					cp++;
460 				if (*cp == '\0')
461 					break;
462 				if (isdigit(*cp)) {
463 					if (requests >= MAXREQUESTS)
464 						fatal(0, "Too many requests");
465 					requ[requests++] = atoi(cp);
466 				} else {
467 					if (users >= MAXUSERS)
468 						fatal(0, "Too many users");
469 					user[users++] = cp;
470 				}
471 			}
472 			status = getprintcap(printer, pp);
473 			if (status < 0)
474 				fatal(pp, pcaperr(status));
475 			displayq(pp, cbuf[0] == CMD_SHOWQ_LONG);
476 			exit(0);
477 		case CMD_RMJOB:	/* remove a job from the queue */
478 			if (!from_remote) {
479 				syslog(LOG_INFO, "illegal request (%d)", *cp);
480 				exit(1);
481 			}
482 			printer = cp;
483 			while (*cp && *cp != ' ')
484 				cp++;
485 			if (!*cp)
486 				break;
487 			*cp++ = '\0';
488 			person = cp;
489 			while (*cp) {
490 				if (*cp != ' ') {
491 					cp++;
492 					continue;
493 				}
494 				*cp++ = '\0';
495 				while (isspace(*cp))
496 					cp++;
497 				if (*cp == '\0')
498 					break;
499 				if (isdigit(*cp)) {
500 					if (requests >= MAXREQUESTS)
501 						fatal(0, "Too many requests");
502 					requ[requests++] = atoi(cp);
503 				} else {
504 					if (users >= MAXUSERS)
505 						fatal(0, "Too many users");
506 					user[users++] = cp;
507 				}
508 			}
509 			rmjob(printer);
510 			break;
511 		}
512 		fatal(0, "Illegal service request");
513 	}
514 }
515 
516 /*
517  * Make a pass through the printcap database and start printing any
518  * files left from the last time the machine went down.
519  */
520 static void
521 startup()
522 {
523 	int pid, status, more;
524 	struct printer myprinter, *pp = &myprinter;
525 
526 	more = firstprinter(pp, &status);
527 	if (status)
528 		goto errloop;
529 	while (more) {
530 		if (ckqueue(pp) <= 0) {
531 			goto next;
532 		}
533 		if (lflag)
534 			syslog(LOG_INFO, "work for %s", pp->printer);
535 		if ((pid = fork()) < 0) {
536 			syslog(LOG_WARNING, "startup: cannot fork");
537 			mcleanup(0);
538 		}
539 		if (pid == 0) {
540 			lastprinter();
541 			printjob(pp);
542 			/* NOTREACHED */
543 		}
544 		do {
545 next:
546 			more = nextprinter(pp, &status);
547 errloop:
548 			if (status)
549 				syslog(LOG_WARNING,
550 				       "printcap for %s has errors, skipping",
551 				       pp->printer ? pp->printer : "<???>");
552 		} while (more && status);
553 	}
554 }
555 
556 /*
557  * Make sure there's some work to do before forking off a child
558  */
559 static int
560 ckqueue(pp)
561 	struct printer *pp;
562 {
563 	register struct dirent *d;
564 	DIR *dirp;
565 	char *spooldir;
566 
567 	spooldir = pp->spool_dir;
568 	if ((dirp = opendir(spooldir)) == NULL)
569 		return (-1);
570 	while ((d = readdir(dirp)) != NULL) {
571 		if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
572 			continue;	/* daemon control files only */
573 		closedir(dirp);
574 		return (1);		/* found something */
575 	}
576 	closedir(dirp);
577 	return (0);
578 }
579 
580 #define DUMMY ":nobody::"
581 
582 /*
583  * Check to see if the from host has access to the line printer.
584  */
585 static void
586 chkhost(f)
587 	struct sockaddr_in *f;
588 {
589 	register struct hostent *hp;
590 	register FILE *hostf;
591 	int first = 1;
592 	int good = 0;
593 
594 	/* Need real hostname for temporary filenames */
595 	hp = gethostbyaddr((char *)&f->sin_addr,
596 	    sizeof(struct in_addr), f->sin_family);
597 	if (hp == NULL)
598 		fatal(0, "Host name for your address (%s) unknown",
599 			inet_ntoa(f->sin_addr));
600 
601 	(void) strncpy(fromb, hp->h_name, sizeof(fromb) - 1);
602 	fromb[sizeof(fromb) - 1] = '\0';
603 	from = fromb;
604 
605 	/* Check for spoof, ala rlogind */
606 	hp = gethostbyname(fromb);
607 	if (!hp)
608 		fatal(0, "hostname for your address (%s) unknown",
609 		    inet_ntoa(f->sin_addr));
610 	for (; good == 0 && hp->h_addr_list[0] != NULL; hp->h_addr_list++) {
611 		if (!bcmp(hp->h_addr_list[0], (caddr_t)&f->sin_addr,
612 		    sizeof(f->sin_addr)))
613 			good = 1;
614 	}
615 	if (good == 0)
616 		fatal(0, "address for your hostname (%s) not matched",
617 		    inet_ntoa(f->sin_addr));
618 
619 	hostf = fopen(_PATH_HOSTSEQUIV, "r");
620 again:
621 	if (hostf) {
622 		if (__ivaliduser(hostf, f->sin_addr.s_addr,
623 		    DUMMY, DUMMY) == 0) {
624 			(void) fclose(hostf);
625 			return;
626 		}
627 		(void) fclose(hostf);
628 	}
629 	if (first == 1) {
630 		first = 0;
631 		hostf = fopen(_PATH_HOSTSLPD, "r");
632 		goto again;
633 	}
634 	fatal(0, "Your host does not have line printer access");
635 	/*NOTREACHED*/
636 }
637 
638 static void
639 usage()
640 {
641 	fprintf(stderr, "usage: lpd [-dlp] [port#]\n");
642 	exit(EX_USAGE);
643 }
644