lpd.c revision 1.14 1 /* $NetBSD: lpd.c,v 1.14 1997/10/09 07:58:41 mrg 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.7 (Berkeley) 5/10/95";
47 #else
48 __RCSID("$NetBSD: lpd.c,v 1.14 1997/10/09 07:58:41 mrg 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 <sys/file.h>
88 #include <netinet/in.h>
89
90 #include <netdb.h>
91 #include <unistd.h>
92 #include <syslog.h>
93 #include <signal.h>
94 #include <errno.h>
95 #include <fcntl.h>
96 #include <dirent.h>
97 #include <stdio.h>
98 #include <stdlib.h>
99 #include <string.h>
100 #include <ctype.h>
101 #include <arpa/inet.h>
102
103 #include "lp.h"
104 #include "lp.local.h"
105 #include "pathnames.h"
106 #include "extern.h"
107
108 int lflag; /* log requests flag */
109 int sflag; /* secure (no inet) flag */
110 int from_remote; /* from remote socket */
111
112 int main __P((int, char **));
113 static void reapchild __P((int));
114 static void mcleanup __P((int));
115 static void doit __P((void));
116 static void startup __P((void));
117 static void chkhost __P((struct sockaddr_in *));
118 static int ckqueue __P((char *));
119 static void usage __P((void));
120
121 uid_t uid, euid;
122
123 int
124 main(argc, argv)
125 int argc;
126 char **argv;
127 {
128 int f, funix, finet, options, fromlen;
129 fd_set defreadfds;
130 struct sockaddr_un un, fromunix;
131 struct sockaddr_in sin, frominet;
132 int omask, lfd, errs, i;
133
134 euid = geteuid(); /* these shouldn't be different */
135 uid = getuid();
136 options = 0;
137 gethostname(host, sizeof(host));
138 name = argv[0];
139
140 errs = 0;
141 while ((i = getopt(argc, argv, "dls")) != -1)
142 switch (i) {
143 case 'd':
144 options |= SO_DEBUG;
145 break;
146 case 'l':
147 lflag++;
148 break;
149 case 's':
150 sflag++;
151 break;
152 default:
153 errs++;
154 }
155 argc -= optind;
156 argv += optind;
157 if (errs || argc != 0)
158 usage();
159
160 #ifndef DEBUG
161 /*
162 * Set up standard environment by detaching from the parent.
163 */
164 daemon(0, 0);
165 #endif
166
167 openlog("lpd", LOG_PID, LOG_LPR);
168 syslog(LOG_INFO, "restarted");
169 (void)umask(0);
170 lfd = open(_PATH_MASTERLOCK, O_WRONLY|O_CREAT, 0644);
171 if (lfd < 0) {
172 syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
173 exit(1);
174 }
175 if (flock(lfd, LOCK_EX|LOCK_NB) < 0) {
176 if (errno == EWOULDBLOCK) /* active deamon present */
177 exit(0);
178 syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
179 exit(1);
180 }
181 ftruncate(lfd, 0);
182 /*
183 * write process id for others to know
184 */
185 (void)snprintf(line, sizeof(line), "%u\n", getpid());
186 f = strlen(line);
187 if (write(lfd, line, f) != f) {
188 syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
189 exit(1);
190 }
191 signal(SIGCHLD, reapchild);
192 /*
193 * Restart all the printers.
194 */
195 startup();
196 (void)unlink(_PATH_SOCKETNAME);
197 funix = socket(AF_UNIX, SOCK_STREAM, 0);
198 if (funix < 0) {
199 syslog(LOG_ERR, "socket: %m");
200 exit(1);
201 }
202 #define mask(s) (1 << ((s) - 1))
203 omask = sigblock(mask(SIGHUP)|mask(SIGINT)|mask(SIGQUIT)|mask(SIGTERM));
204 signal(SIGHUP, mcleanup);
205 signal(SIGINT, mcleanup);
206 signal(SIGQUIT, mcleanup);
207 signal(SIGTERM, mcleanup);
208 memset(&un, 0, sizeof(un));
209 un.sun_family = AF_UNIX;
210 strncpy(un.sun_path, _PATH_SOCKETNAME, sizeof(un.sun_path) - 1);
211 #ifndef SUN_LEN
212 #define SUN_LEN(unp) (strlen((unp)->sun_path) + 2)
213 #endif
214 if (bind(funix, (struct sockaddr *)&un, SUN_LEN(&un)) < 0) {
215 syslog(LOG_ERR, "ubind: %m");
216 exit(1);
217 }
218 sigsetmask(omask);
219 FD_ZERO(&defreadfds);
220 FD_SET(funix, &defreadfds);
221 listen(funix, 5);
222 if (!sflag)
223 finet = socket(AF_INET, SOCK_STREAM, 0);
224 else
225 finet = -1; /* pretend we couldn't open TCP socket. */
226 if (finet >= 0) {
227 struct servent *sp;
228
229 if (options & SO_DEBUG)
230 if (setsockopt(finet, SOL_SOCKET, SO_DEBUG, 0, 0) < 0) {
231 syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m");
232 mcleanup(0);
233 }
234 sp = getservbyname("printer", "tcp");
235 if (sp == NULL) {
236 syslog(LOG_ERR, "printer/tcp: unknown service");
237 mcleanup(0);
238 }
239 memset(&sin, 0, sizeof(sin));
240 sin.sin_family = AF_INET;
241 sin.sin_port = sp->s_port;
242 if (bind(finet, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
243 syslog(LOG_ERR, "bind: %m");
244 mcleanup(0);
245 }
246 FD_SET(finet, &defreadfds);
247 listen(finet, 5);
248 }
249 /*
250 * Main loop: accept, do a request, continue.
251 */
252 memset(&frominet, 0, sizeof(frominet));
253 memset(&fromunix, 0, sizeof(fromunix));
254 for (;;) {
255 int domain, nfds, s;
256 fd_set readfds;
257
258 FD_COPY(&defreadfds, &readfds);
259 nfds = select(20, &readfds, 0, 0, 0);
260 if (nfds <= 0) {
261 if (nfds < 0 && errno != EINTR)
262 syslog(LOG_WARNING, "select: %m");
263 continue;
264 }
265 if (FD_ISSET(funix, &readfds)) {
266 domain = AF_UNIX, fromlen = sizeof(fromunix);
267 s = accept(funix,
268 (struct sockaddr *)&fromunix, &fromlen);
269 } else /* if (FD_ISSET(finet, &readfds)) */ {
270 domain = AF_INET, fromlen = sizeof(frominet);
271 s = accept(finet,
272 (struct sockaddr *)&frominet, &fromlen);
273 }
274 if (s < 0) {
275 if (errno != EINTR)
276 syslog(LOG_WARNING, "accept: %m");
277 continue;
278 }
279 if (fork() == 0) {
280 signal(SIGCHLD, SIG_IGN);
281 signal(SIGHUP, SIG_IGN);
282 signal(SIGINT, SIG_IGN);
283 signal(SIGQUIT, SIG_IGN);
284 signal(SIGTERM, SIG_IGN);
285 (void)close(funix);
286 if (!sflag)
287 (void)close(finet);
288 dup2(s, 1);
289 (void)close(s);
290 if (domain == AF_INET) {
291 from_remote = 1;
292 chkhost(&frominet);
293 } else
294 from_remote = 0;
295 doit();
296 exit(0);
297 }
298 (void)close(s);
299 }
300 }
301
302 static void
303 reapchild(signo)
304 int signo;
305 {
306 union wait status;
307
308 while (wait3((int *)&status, WNOHANG, 0) > 0)
309 ;
310 }
311
312 static void
313 mcleanup(signo)
314 int signo;
315 {
316 if (lflag)
317 syslog(LOG_INFO, "exiting");
318 unlink(_PATH_SOCKETNAME);
319 exit(0);
320 }
321
322 /*
323 * Stuff for handling job specifications
324 */
325 char *user[MAXUSERS]; /* users to process */
326 int users; /* # of users in user array */
327 int requ[MAXREQUESTS]; /* job number of spool entries */
328 int requests; /* # of spool requests */
329 char *person; /* name of person doing lprm */
330
331 char fromb[MAXHOSTNAMELEN]; /* buffer for client's machine name */
332 char cbuf[BUFSIZ]; /* command line buffer */
333 char *cmdnames[] = {
334 "null",
335 "printjob",
336 "recvjob",
337 "displayq short",
338 "displayq long",
339 "rmjob"
340 };
341
342 static void
343 doit()
344 {
345 char *cp;
346 int n;
347
348 for (;;) {
349 cp = cbuf;
350 do {
351 if (cp >= &cbuf[sizeof(cbuf) - 1])
352 fatal("Command line too long");
353 if ((n = read(1, cp, 1)) != 1) {
354 if (n < 0)
355 fatal("Lost connection");
356 return;
357 }
358 } while (*cp++ != '\n');
359 *--cp = '\0';
360 cp = cbuf;
361 if (lflag) {
362 if (*cp >= '\1' && *cp <= '\5')
363 syslog(LOG_INFO, "%s requests %s %s",
364 from, cmdnames[(int)*cp], cp+1);
365 else
366 syslog(LOG_INFO, "bad request (%d) from %s",
367 *cp, from);
368 }
369 switch (*cp++) {
370 case '\1': /* check the queue and print any jobs there */
371 printer = cp;
372 printjob();
373 break;
374 case '\2': /* receive files to be queued */
375 if (!from_remote) {
376 syslog(LOG_INFO, "illegal request (%d)", *cp);
377 exit(1);
378 }
379 printer = cp;
380 recvjob();
381 break;
382 case '\3': /* display the queue (short form) */
383 case '\4': /* display the queue (long form) */
384 printer = cp;
385 while (*cp) {
386 if (*cp != ' ') {
387 cp++;
388 continue;
389 }
390 *cp++ = '\0';
391 while (isspace(*cp))
392 cp++;
393 if (*cp == '\0')
394 break;
395 if (isdigit(*cp)) {
396 if (requests >= MAXREQUESTS)
397 fatal("Too many requests");
398 requ[requests++] = atoi(cp);
399 } else {
400 if (users >= MAXUSERS)
401 fatal("Too many users");
402 user[users++] = cp;
403 }
404 }
405 displayq(cbuf[0] - '\3');
406 exit(0);
407 case '\5': /* remove a job from the queue */
408 if (!from_remote) {
409 syslog(LOG_INFO, "illegal request (%d)", *cp);
410 exit(1);
411 }
412 printer = cp;
413 while (*cp && *cp != ' ')
414 cp++;
415 if (!*cp)
416 break;
417 *cp++ = '\0';
418 person = cp;
419 while (*cp) {
420 if (*cp != ' ') {
421 cp++;
422 continue;
423 }
424 *cp++ = '\0';
425 while (isspace(*cp))
426 cp++;
427 if (*cp == '\0')
428 break;
429 if (isdigit(*cp)) {
430 if (requests >= MAXREQUESTS)
431 fatal("Too many requests");
432 requ[requests++] = atoi(cp);
433 } else {
434 if (users >= MAXUSERS)
435 fatal("Too many users");
436 user[users++] = cp;
437 }
438 }
439 rmjob();
440 break;
441 }
442 fatal("Illegal service request");
443 }
444 }
445
446 /*
447 * Make a pass through the printcap database and start printing any
448 * files left from the last time the machine went down.
449 */
450 static void
451 startup()
452 {
453 char *buf;
454 char *cp;
455 int pid;
456
457 /*
458 * Restart the daemons.
459 */
460 while (cgetnext(&buf, printcapdb) > 0) {
461 if (ckqueue(buf) <= 0) {
462 free(buf);
463 continue; /* no work to do for this printer */
464 }
465 for (cp = buf; *cp; cp++)
466 if (*cp == '|' || *cp == ':') {
467 *cp = '\0';
468 break;
469 }
470 if (lflag)
471 syslog(LOG_INFO, "work for %s", buf);
472 if ((pid = fork()) < 0) {
473 syslog(LOG_WARNING, "startup: cannot fork");
474 mcleanup(0);
475 }
476 if (!pid) {
477 printer = buf;
478 cgetclose();
479 printjob();
480 /* NOTREACHED */
481 }
482 else free(buf);
483 }
484 }
485
486 /*
487 * Make sure there's some work to do before forking off a child
488 */
489 static int
490 ckqueue(cap)
491 char *cap;
492 {
493 struct dirent *d;
494 DIR *dirp;
495 char *spooldir;
496
497 if (cgetstr(cap, "sd", &spooldir) == -1)
498 spooldir = _PATH_DEFSPOOL;
499 if ((dirp = opendir(spooldir)) == NULL)
500 return (-1);
501 while ((d = readdir(dirp)) != NULL) {
502 if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
503 continue; /* daemon control files only */
504 closedir(dirp);
505 return (1); /* found something */
506 }
507 closedir(dirp);
508 return (0);
509 }
510
511 #define DUMMY ":nobody::"
512
513 /*
514 * Check to see if the from host has access to the line printer.
515 */
516 static void
517 chkhost(f)
518 struct sockaddr_in *f;
519 {
520 struct hostent *hp;
521 FILE *hostf;
522 int first = 1, good = 0;
523
524 f->sin_port = ntohs(f->sin_port);
525 if (f->sin_family != AF_INET || f->sin_port >= IPPORT_RESERVED)
526 fatal("Malformed from address");
527
528 /* Need real hostname for temporary filenames */
529 hp = gethostbyaddr((char *)&f->sin_addr,
530 sizeof(struct in_addr), f->sin_family);
531 if (hp == NULL)
532 fatal("Host name for your address (%s) unknown",
533 inet_ntoa(f->sin_addr));
534
535 (void)strncpy(fromb, hp->h_name, sizeof(fromb) - 1);
536 from[sizeof(fromb) - 1] = '\0';
537 from = fromb;
538
539 /* Check for spoof, ala rlogind */
540 hp = gethostbyname(fromb);
541 if (!hp)
542 fatal("hostname for your address (%s) unknown",
543 inet_ntoa(f->sin_addr));
544 for (; good == 0 && hp->h_addr_list[0] != NULL; hp->h_addr_list++) {
545 if (!bcmp(hp->h_addr_list[0], (caddr_t)&f->sin_addr,
546 sizeof(f->sin_addr)))
547 good = 1;
548 }
549 if (good == 0)
550 fatal("address for your hostname (%s) not matched",
551 inet_ntoa(f->sin_addr));
552 hostf = fopen(_PATH_HOSTSEQUIV, "r");
553 again:
554 if (hostf) {
555 if (__ivaliduser(hostf, f->sin_addr.s_addr,
556 DUMMY, DUMMY) == 0) {
557 (void)fclose(hostf);
558 return;
559 }
560 (void)fclose(hostf);
561 }
562 if (first == 1) {
563 first = 0;
564 hostf = fopen(_PATH_HOSTSLPD, "r");
565 goto again;
566 }
567 fatal("Your host does not have line printer access");
568 /*NOTREACHED*/
569 }
570
571 static void
572 usage()
573 {
574 extern char *__progname; /* XXX */
575
576 fprintf(stderr, "usage: %s [-d] [-l]\n", __progname);
577 exit(1);
578 }
579