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