1 /* $NetBSD: lpr.c,v 1.44 2011/01/20 15:48:11 ginsbach Exp $ */ 2 3 /* 4 * Copyright (c) 1983, 1989, 1993 5 * The Regents of the University of California. All rights reserved. 6 * (c) UNIX System Laboratories, Inc. 7 * All or some portions of this file are derived from material licensed 8 * to the University of California by American Telephone and Telegraph 9 * Co. or Unix System Laboratories, Inc. and are reproduced herein with 10 * the permission of UNIX System Laboratories, Inc. 11 * 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. 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 #include <sys/cdefs.h> 39 #ifndef lint 40 __COPYRIGHT("@(#) Copyright (c) 1983, 1989, 1993\ 41 The Regents of the University of California. All rights reserved."); 42 #if 0 43 static char sccsid[] = "@(#)lpr.c 8.4 (Berkeley) 4/28/95"; 44 #else 45 __RCSID("$NetBSD: lpr.c,v 1.44 2011/01/20 15:48:11 ginsbach Exp $"); 46 #endif 47 #endif /* not lint */ 48 49 /* 50 * lpr -- off line print 51 * 52 * Allows multiple printers and printers on remote machines by 53 * using information from a printer data base. 54 */ 55 56 #include <sys/param.h> 57 #include <sys/stat.h> 58 #include <sys/file.h> 59 60 #include <dirent.h> 61 #include <fcntl.h> 62 #include <signal.h> 63 #include <syslog.h> 64 #include <pwd.h> 65 #include <grp.h> 66 #include <unistd.h> 67 #include <stdlib.h> 68 #include <stdio.h> 69 #include <ctype.h> 70 #include <string.h> 71 #include <errno.h> 72 #include <err.h> 73 74 #include "lp.h" 75 #include "lp.local.h" 76 #include "pathnames.h" 77 78 static char *cfname; /* daemon control files, linked from tf's */ 79 static char *class = host; /* class title on header page */ 80 static char *dfname; /* data files */ 81 static char *fonts[4]; /* troff font names */ 82 static char format = 'f'; /* format char for printing files */ 83 static int hdr = 1; /* print header or not (default is yes) */ 84 static int iflag; /* indentation wanted */ 85 static int inchar; /* location to increment char in file names */ 86 static int indent; /* amount to indent */ 87 static const char *jobname; /* job name on header page */ 88 static int mailflg; /* send mail */ 89 static int nact; /* number of jobs to act on */ 90 static int ncopies = 1; /* # of copies to make */ 91 static const char *person; /* user name */ 92 static int qflag; /* q job, but don't exec daemon */ 93 static int reqid; /* request id */ 94 static int rflag; /* remove files upon completion */ 95 static int Rflag; /* print request id - like POSIX lp */ 96 static int sflag; /* symbolic link flag */ 97 static int tfd; /* control file descriptor */ 98 static char *tfname; /* tmp copy of cf before linking */ 99 static char *title; /* pr'ing title */ 100 static int userid; /* user id */ 101 static char *width; /* width for versatec printing */ 102 103 static struct stat statb; 104 105 static void card(int, const char *); 106 static void chkprinter(const char *); 107 static void cleanup(int); 108 static void copy(int, const char *); 109 static char *itoa(int); 110 static const char *linked(const char *); 111 static char *lmktemp(const char *, int, int); 112 static void mktemps(void); 113 static int nfile(char *); 114 static int test(const char *); 115 static void usage(void) __dead; 116 117 uid_t uid, euid; 118 119 int 120 main(int argc, char *argv[]) 121 { 122 struct passwd *pw; 123 struct group *gptr; 124 char *arg; 125 const char *cp; 126 char buf[MAXPATHLEN]; 127 int i, f, errs, c; 128 struct stat stb; 129 int oerrno; 130 131 euid = geteuid(); 132 uid = getuid(); 133 seteuid(uid); 134 135 if (signal(SIGHUP, SIG_IGN) != SIG_IGN) 136 signal(SIGHUP, cleanup); 137 if (signal(SIGINT, SIG_IGN) != SIG_IGN) 138 signal(SIGINT, cleanup); 139 if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) 140 signal(SIGQUIT, cleanup); 141 if (signal(SIGTERM, SIG_IGN) != SIG_IGN) 142 signal(SIGTERM, cleanup); 143 144 setprogname(*argv); 145 gethostname(host, sizeof (host)); 146 host[sizeof(host) - 1] = '\0'; 147 openlog("lpd", 0, LOG_LPR); 148 149 errs = 0; 150 while ((c = getopt(argc, argv, 151 ":#:1:2:3:4:C:J:P:RT:U:cdfghi:lmnopqrstvw:")) != -1) { 152 switch (c) { 153 154 case '#': /* n copies */ 155 if (isdigit((unsigned char)*optarg)) { 156 i = atoi(optarg); 157 if (i > 0) 158 ncopies = i; 159 } 160 break; 161 162 case '4': /* troff fonts */ 163 case '3': 164 case '2': 165 case '1': 166 fonts[optopt - '1'] = optarg; 167 break; 168 169 case 'C': /* classification spec */ 170 hdr++; 171 class = optarg; 172 break; 173 174 case 'J': /* job name */ 175 hdr++; 176 jobname = optarg; 177 break; 178 179 case 'P': /* specifiy printer name */ 180 printer = optarg; 181 break; 182 183 case 'R': /* print request id */ 184 Rflag++; 185 break; 186 187 case 'T': /* pr's title line */ 188 title = optarg; 189 break; 190 191 case 'U': /* user name */ 192 hdr++; 193 person = optarg; 194 break; 195 196 case 'c': /* print cifplot output */ 197 case 'd': /* print tex output (dvi files) */ 198 case 'g': /* print graph(1G) output */ 199 case 'l': /* literal output */ 200 case 'o': /* print postscript output */ 201 case 'n': /* print ditroff output */ 202 case 'p': /* print using ``pr'' */ 203 case 't': /* print troff output (cat files) */ 204 case 'v': /* print vplot output */ 205 format = optopt; 206 break; 207 208 case 'f': /* print fortran output */ 209 format = 'r'; 210 break; 211 212 case 'h': /* toggle want of header page */ 213 hdr = !hdr; 214 break; 215 216 case 'i': /* indent output */ 217 iflag++; 218 indent = atoi(optarg); 219 if (indent < 0) 220 indent = 8; 221 break; 222 223 case 'm': /* send mail when done */ 224 mailflg++; 225 break; 226 227 case 'q': /* just q job */ 228 qflag++; 229 break; 230 231 case 'r': /* remove file when done */ 232 rflag++; 233 break; 234 235 case 's': /* try to link files */ 236 sflag++; 237 break; 238 239 case 'w': /* versatec page width */ 240 width = optarg; 241 break; 242 243 case ':': /* catch "missing argument" error */ 244 if (optopt == 'i') { 245 iflag++; /* -i without args is valid */ 246 indent = 8; 247 } else 248 errs++; 249 break; 250 251 default: 252 errs++; 253 } 254 } 255 argc -= optind; 256 argv += optind; 257 if (errs) 258 usage(); 259 if (printer == NULL && (printer = getenv("PRINTER")) == NULL) 260 printer = DEFLP; 261 chkprinter(printer); 262 if (SC && ncopies > 1) 263 errx(EXIT_FAILURE, "multiple copies are not allowed"); 264 if (MC > 0 && ncopies > MC) 265 errx(EXIT_FAILURE, "only %ld copies are allowed", MC); 266 /* 267 * Get the identity of the person doing the lpr using the same 268 * algorithm as lprm. 269 */ 270 userid = getuid(); 271 if (userid != DU || person == 0) { 272 if ((pw = getpwuid(userid)) == NULL) 273 errx(EXIT_FAILURE, "Who are you?"); 274 person = pw->pw_name; 275 } 276 /* 277 * Check for restricted group access. 278 */ 279 if (RG != NULL && userid != DU) { 280 if ((gptr = getgrnam(RG)) == NULL) 281 errx(EXIT_FAILURE, 282 "Restricted group specified incorrectly"); 283 if (gptr->gr_gid != getgid()) { 284 while (*gptr->gr_mem != NULL) { 285 if ((strcmp(person, *gptr->gr_mem)) == 0) 286 break; 287 gptr->gr_mem++; 288 } 289 if (*gptr->gr_mem == NULL) 290 errx(EXIT_FAILURE, 291 "Not a member of the restricted group"); 292 } 293 } 294 /* 295 * Check to make sure queuing is enabled if userid is not root. 296 */ 297 (void)snprintf(buf, sizeof buf, "%s/%s", SD, LO); 298 if (userid && stat(buf, &stb) == 0 && (stb.st_mode & S_IXGRP)) 299 errx(EXIT_FAILURE, "Printer queue is disabled"); 300 /* 301 * Initialize the control file. 302 */ 303 mktemps(); 304 tfd = nfile(tfname); 305 seteuid(euid); 306 (void)fchown(tfd, DU, -1); /* owned by daemon for protection */ 307 seteuid(uid); 308 card('H', host); 309 card('P', person); 310 if (hdr && !SH) { 311 if (jobname == NULL) { 312 if (argc == 0) 313 jobname = "stdin"; 314 else 315 jobname = (arg = strrchr(argv[0], '/')) ? 316 arg+1 : argv[0]; 317 } 318 card('J', jobname); 319 card('C', class); 320 card('L', person); 321 } 322 if (iflag) 323 card('I', itoa(indent)); 324 if (mailflg) 325 card('M', person); 326 if (format == 't' || format == 'n' || format == 'd') 327 for (i = 0; i < 4; i++) 328 if (fonts[i] != NULL) 329 card('1'+i, fonts[i]); 330 if (width != NULL) 331 card('W', width); 332 333 /* 334 * Read the files and spool them. 335 */ 336 if (argc == 0) 337 copy(0, " "); 338 else while (argc--) { 339 if (argv[0][0] == '-' && argv[0][1] == '\0') { 340 /* use stdin */ 341 copy(0, " "); 342 argv++; 343 continue; 344 } 345 if ((f = test(arg = *argv++)) < 0) 346 continue; /* file unreasonable */ 347 348 if (sflag && (cp = linked(arg)) != NULL) { 349 (void)snprintf(buf, sizeof buf, 350 "%llu %llu", 351 (unsigned long long)statb.st_dev, 352 (unsigned long long)statb.st_ino); 353 card('S', buf); 354 if (format == 'p') 355 card('T', title ? title : arg); 356 for (i = 0; i < ncopies; i++) 357 card(format, &dfname[inchar-2]); 358 card('U', &dfname[inchar-2]); 359 if (f) 360 card('U', cp); 361 card('N', arg); 362 dfname[inchar]++; 363 nact++; 364 continue; 365 } 366 if (sflag) 367 warnx("%s: not linked, copying instead", arg); 368 seteuid(uid); 369 if ((i = open(arg, O_RDONLY)) < 0) { 370 oerrno = errno; 371 seteuid(uid); 372 errno = oerrno; 373 warn("cannot open %s", arg); 374 continue; 375 } else { 376 copy(i, arg); 377 (void)close(i); 378 if (f && unlink(arg) < 0) 379 warn("%s: not removed", arg); 380 } 381 seteuid(uid); 382 } 383 384 if (nact) { 385 (void)close(tfd); 386 tfname[inchar]--; 387 /* 388 * Touch the control file to fix position in the queue. 389 */ 390 seteuid(euid); 391 if ((tfd = open(tfname, O_RDWR)) >= 0) { 392 char ch; 393 394 if (read(tfd, &ch, 1) == 1 && 395 lseek(tfd, (off_t)0, 0) == 0 && 396 write(tfd, &ch, 1) != 1) { 397 warn("cannot touch %s", tfname); 398 tfname[inchar]++; 399 cleanup(0); 400 } 401 (void)close(tfd); 402 } 403 if (link(tfname, cfname) < 0) { 404 warn("cannot rename %s", cfname); 405 tfname[inchar]++; 406 cleanup(0); 407 } 408 unlink(tfname); 409 seteuid(uid); 410 if (Rflag) 411 printf("request id is %d\n", reqid); 412 if (qflag) /* just queue things up */ 413 exit(0); 414 if (!startdaemon(printer)) 415 printf("jobs queued, but cannot start daemon.\n"); 416 exit(0); 417 } 418 cleanup(0); 419 #ifdef __GNUC__ 420 return (0); 421 #endif 422 /* NOTREACHED */ 423 } 424 425 /* 426 * Create the file n and copy from file descriptor f. 427 */ 428 static void 429 copy(int f, const char *n) 430 { 431 int fd, i, nr; 432 size_t nc; 433 char buf[BUFSIZ]; 434 435 if (format == 'p') 436 card('T', title ? title : n); 437 for (i = 0; i < ncopies; i++) 438 card(format, &dfname[inchar-2]); 439 card('U', &dfname[inchar-2]); 440 card('N', n); 441 fd = nfile(dfname); 442 nr = nc = 0; 443 while ((i = read(f, buf, sizeof buf)) > 0) { 444 if (write(fd, buf, i) != i) { 445 warn("%s: temp file write error", n); 446 break; 447 } 448 nc += i; 449 if (nc >= sizeof buf) { 450 nc -= sizeof buf; 451 nr++; 452 if (MX > 0 && nr > MX) { 453 warnx("%s: copy file is too large " 454 "(check :mx:?)\n", n); 455 break; 456 } 457 } 458 } 459 (void)close(fd); 460 if (nc == 0 && nr == 0) 461 printf("%s: %s: empty input file\n", getprogname(), 462 f ? n : "stdin"); 463 else 464 nact++; 465 } 466 467 /* 468 * Try and link the file to dfname. Return a pointer to the full 469 * path name if successful. 470 */ 471 static const char * 472 linked(const char *file) 473 { 474 char *cp; 475 static char buf[BUFSIZ]; 476 int ret; 477 478 if (*file != '/') { 479 /* XXX: 2 and file for "/file" */ 480 if (getcwd(buf, sizeof(buf) - 2 - strlen(file)) == NULL) 481 return(NULL); 482 while (file[0] == '.') { 483 switch (file[1]) { 484 case '/': 485 file += 2; 486 continue; 487 case '.': 488 if (file[2] == '/') { 489 if ((cp = strrchr(buf, '/')) != NULL) 490 *cp = '\0'; 491 file += 3; 492 continue; 493 } 494 } 495 break; 496 } 497 strlcat(buf, "/", sizeof(buf)); 498 strlcat(buf, file, sizeof(buf)); 499 file = buf; 500 } 501 seteuid(euid); 502 ret = symlink(file, dfname); 503 seteuid(uid); 504 return(ret ? NULL : file); 505 } 506 507 /* 508 * Put a line into the control file. 509 */ 510 static void 511 card(int c, const char *p2) 512 { 513 char buf[BUFSIZ]; 514 char *p1 = buf; 515 size_t len = 2; 516 517 if (strlen(p2) > BUFSIZ - 2) 518 errx(EXIT_FAILURE, 519 "Internal error: String longer than %d", BUFSIZ); 520 521 *p1++ = c; 522 while ((c = *p2++) != '\0') { 523 *p1++ = (c == '\n') ? ' ' : c; 524 len++; 525 } 526 *p1++ = '\n'; 527 if (write(tfd, buf, len) != (ssize_t)len) 528 warn("Control file write error"); 529 } 530 531 /* 532 * Create a new file in the spool directory. 533 */ 534 static int 535 nfile(char *n) 536 { 537 int f; 538 int oldumask = umask(0); /* should block signals */ 539 int oerrno; 540 541 seteuid(euid); 542 f = open(n, O_WRONLY | O_EXCL | O_CREAT, FILMOD); 543 oerrno = errno; 544 (void)umask(oldumask); 545 if (f < 0) { 546 errno = oerrno; 547 warn("cannot create %s", n); 548 cleanup(0); 549 } 550 if (fchown(f, userid, -1) < 0) { 551 warn("cannot chown %s", n); 552 cleanup(0); /* cleanup does exit */ 553 } 554 seteuid(uid); 555 if (++n[inchar] > 'z') { 556 if (++n[inchar-2] == 't') { 557 printf("too many files - break up the job\n"); 558 cleanup(0); 559 } 560 n[inchar] = 'A'; 561 } else if (n[inchar] == '[') 562 n[inchar] = 'a'; 563 return(f); 564 } 565 566 /* 567 * Cleanup after interrupts and errors. 568 */ 569 static void 570 cleanup(int signo) 571 { 572 int i; 573 574 signal(SIGHUP, SIG_IGN); 575 signal(SIGINT, SIG_IGN); 576 signal(SIGQUIT, SIG_IGN); 577 signal(SIGTERM, SIG_IGN); 578 i = inchar; 579 seteuid(euid); 580 if (tfname) 581 do 582 unlink(tfname); 583 while (tfname[i]-- != 'A'); 584 if (cfname) 585 do 586 unlink(cfname); 587 while (cfname[i]-- != 'A'); 588 if (dfname) 589 do { 590 do 591 unlink(dfname); 592 while (dfname[i]-- != 'A'); 593 dfname[i] = 'z'; 594 } while (dfname[i-2]-- != 'd'); 595 exit(1); 596 } 597 598 /* 599 * Test to see if this is a printable file. 600 * Return -1 if it is not, 0 if its printable, and 1 if 601 * we should remove it after printing. 602 */ 603 static int 604 test(const char *file) 605 { 606 int fd; 607 char *cp; 608 609 seteuid(uid); 610 if (access(file, 4) < 0) { 611 warn("cannot access %s", file); 612 goto bad; 613 } 614 if (stat(file, &statb) < 0) { 615 warn("cannot stat %s", file); 616 goto bad; 617 } 618 if (S_ISDIR(statb.st_mode)) { 619 warnx("%s is a directory", file); 620 goto bad; 621 } 622 if (statb.st_size == 0) { 623 warnx("%s is an empty file", file); 624 goto bad; 625 } 626 if ((fd = open(file, O_RDONLY)) < 0) { 627 warn("cannot open %s", file); 628 goto bad; 629 } 630 (void)close(fd); 631 if (rflag) { 632 if ((cp = strrchr(file, '/')) == NULL) { 633 if (access(".", 2) == 0) 634 return(1); 635 } else { 636 if (cp == file) { 637 fd = access("/", 2); 638 } else { 639 *cp = '\0'; 640 fd = access(file, 2); 641 *cp = '/'; 642 } 643 if (fd == 0) 644 return(1); 645 } 646 warnx("%s: is not removable by you", file); 647 } 648 return(0); 649 bad: 650 seteuid(uid); 651 return(-1); 652 } 653 654 /* 655 * itoa - integer to string conversion 656 */ 657 static char * 658 itoa(int i) 659 { 660 static char b[10] = "########"; 661 char *p; 662 663 p = &b[8]; 664 do 665 *p-- = i%10 + '0'; 666 while (i /= 10); 667 return(++p); 668 } 669 670 /* 671 * Perform lookup for printer name or abbreviation -- 672 */ 673 static void 674 chkprinter(const char *s) 675 { 676 char *cp; 677 678 getprintcap(s); 679 RG = cgetstr(bp, "rg", &cp) == -1 ? NULL : cp; 680 if (cgetnum(bp, "mx", &MX) < 0) 681 MX = DEFMX; 682 if (cgetnum(bp,"mc", &MC) < 0) 683 MC = DEFMAXCOPIES; 684 if (cgetnum(bp, "du", &DU) < 0) 685 DU = DEFUID; 686 SC = (cgetcap(bp, "sc", ':') != NULL); 687 } 688 689 /* 690 * Make the temp files. 691 */ 692 static void 693 mktemps(void) 694 { 695 int len, fd, n; 696 char *cp; 697 char buf[MAXPATHLEN]; 698 699 (void)snprintf(buf, sizeof(buf), "%s/.seq", SD); 700 seteuid(euid); 701 if ((fd = open(buf, O_RDWR|O_CREAT, 0661)) < 0) 702 err(1, "cannot create %s", buf); 703 if (flock(fd, LOCK_EX)) 704 err(1, "cannot lock %s", buf); 705 seteuid(uid); 706 n = 0; 707 if ((len = read(fd, buf, sizeof(buf))) > 0) { 708 for (cp = buf; len--; ) { 709 if (*cp < '0' || *cp > '9') 710 break; 711 n = n * 10 + (*cp++ - '0'); 712 } 713 } 714 reqid = n; 715 len = strlen(SD) + strlen(host) + 8; 716 tfname = lmktemp("tf", n, len); 717 cfname = lmktemp("cf", n, len); 718 dfname = lmktemp("df", n, len); 719 inchar = strlen(SD) + 3; 720 n = (n + 1) % 1000; 721 (void)lseek(fd, (off_t)0, 0); 722 (void)snprintf(buf, sizeof(buf), "%03d\n", n); 723 (void)write(fd, buf, strlen(buf)); 724 (void)close(fd); /* unlocks as well */ 725 } 726 727 /* 728 * Make a temp file name. 729 */ 730 static char * 731 lmktemp(const char *id, int num, int len) 732 { 733 char *s; 734 735 if ((s = malloc(len)) == NULL) 736 err(EXIT_FAILURE, NULL); 737 (void)snprintf(s, len, "%s/%sA%03d%s", SD, id, num, host); 738 return(s); 739 } 740 741 static void 742 usage(void) 743 { 744 745 fprintf(stderr, 746 "Usage: %s [-Pprinter] [-#num] [-C class] [-J job] [-T title] " 747 "[-U user]\n" 748 "%s [-i[numcols]] [-1234 font] [-wnum] [-cdfghlmnopqRrstv] " 749 "[name ...]\n", getprogname(), getprogname()); 750 exit(1); 751 } 752