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