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