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