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