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