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