1331722Seadler/*
21553Srgrimes * Copyright (c) 1983, 1993, 1994
31553Srgrimes *	The Regents of the University of California.  All rights reserved.
41553Srgrimes *
51553Srgrimes *
61553Srgrimes * Redistribution and use in source and binary forms, with or without
71553Srgrimes * modification, are permitted provided that the following conditions
81553Srgrimes * are met:
91553Srgrimes * 1. Redistributions of source code must retain the above copyright
101553Srgrimes *    notice, this list of conditions and the following disclaimer.
111553Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
121553Srgrimes *    notice, this list of conditions and the following disclaimer in the
131553Srgrimes *    documentation and/or other materials provided with the distribution.
141553Srgrimes * 4. Neither the name of the University nor the names of its contributors
151553Srgrimes *    may be used to endorse or promote products derived from this software
161553Srgrimes *    without specific prior written permission.
171553Srgrimes *
181553Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
191553Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
201553Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
211553Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
221553Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
231553Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
241553Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
251553Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
261553Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
271553Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
281553Srgrimes * SUCH DAMAGE.
291553Srgrimes */
301553Srgrimes
311553Srgrimes#ifndef lint
3229780Scharnierstatic const char copyright[] =
331553Srgrimes"@(#) Copyright (c) 1983, 1993, 1994\n\
341553Srgrimes	The Regents of the University of California.  All rights reserved.\n";
351553Srgrimes#endif /* not lint */
361553Srgrimes
37117280Scharnier#if 0
38117587Sgad#ifndef lint
3915637Sjoergstatic char sccsid[] = "@(#)lpd.c	8.7 (Berkeley) 5/10/95";
40117587Sgad#endif /* not lint */
4129780Scharnier#endif
421553Srgrimes
43117554Sgad#include "lp.cdefs.h"		/* A cross-platform version of <sys/cdefs.h> */
44117280Scharnier__FBSDID("$FreeBSD$");
45117280Scharnier
461553Srgrimes/*
471553Srgrimes * lpd -- line printer daemon.
481553Srgrimes *
491553Srgrimes * Listen for a connection and perform the requested operation.
501553Srgrimes * Operations are:
511553Srgrimes *	\1printer\n
521553Srgrimes *		check the queue for jobs and print any found.
531553Srgrimes *	\2printer\n
541553Srgrimes *		receive a job from another machine and queue it.
551553Srgrimes *	\3printer [users ...] [jobs ...]\n
561553Srgrimes *		return the current state of the queue (short form).
571553Srgrimes *	\4printer [users ...] [jobs ...]\n
581553Srgrimes *		return the current state of the queue (long form).
591553Srgrimes *	\5printer person [users ...] [jobs ...]\n
601553Srgrimes *		remove jobs from the queue.
611553Srgrimes *
621553Srgrimes * Strategy to maintain protected spooling area:
631553Srgrimes *	1. Spooling area is writable only by daemon and spooling group
641553Srgrimes *	2. lpr runs setuid root and setgrp spooling group; it uses
651553Srgrimes *	   root to access any file it wants (verifying things before
661553Srgrimes *	   with an access call) and group id to know how it should
671553Srgrimes *	   set up ownership of files in the spooling area.
681553Srgrimes *	3. Files in spooling area are owned by root, group spooling
691553Srgrimes *	   group, with mode 660.
701553Srgrimes *	4. lpd, lpq and lprm run setuid daemon and setgrp spooling group to
711553Srgrimes *	   access files and printer.  Users can't get to anything
721553Srgrimes *	   w/o help of lpq and lprm programs.
731553Srgrimes */
741553Srgrimes
751553Srgrimes#include <sys/param.h>
761553Srgrimes#include <sys/wait.h>
771553Srgrimes#include <sys/types.h>
781553Srgrimes#include <sys/socket.h>
791553Srgrimes#include <sys/un.h>
801553Srgrimes#include <sys/stat.h>
8115637Sjoerg#include <sys/file.h>
821553Srgrimes#include <netinet/in.h>
8315703Sjoerg#include <arpa/inet.h>
841553Srgrimes
851553Srgrimes#include <netdb.h>
861553Srgrimes#include <unistd.h>
871553Srgrimes#include <syslog.h>
881553Srgrimes#include <signal.h>
8928621Sjoerg#include <err.h>
901553Srgrimes#include <errno.h>
911553Srgrimes#include <fcntl.h>
921553Srgrimes#include <dirent.h>
931553Srgrimes#include <stdio.h>
941553Srgrimes#include <stdlib.h>
951553Srgrimes#include <string.h>
9628621Sjoerg#include <sysexits.h>
971553Srgrimes#include <ctype.h>
981553Srgrimes#include "lp.h"
991553Srgrimes#include "lp.local.h"
1001553Srgrimes#include "pathnames.h"
1011553Srgrimes#include "extern.h"
1021553Srgrimes
1031553Srgrimesint	lflag;				/* log requests flag */
10495070Sgadint	sflag;				/* no incoming port flag */
1051553Srgrimesint	from_remote;			/* from remote socket */
1061553Srgrimes
10778146Sgadint		 main(int argc, char **_argv);
10878146Sgadstatic void	 reapchild(int _signo);
10978146Sgadstatic void	 mcleanup(int _signo);
11078146Sgadstatic void	 doit(void);
11178146Sgadstatic void	 startup(void);
11278749Sgadstatic void	 chkhost(struct sockaddr *_f, int _ch_opts);
11378146Sgadstatic int	 ckqueue(struct printer *_pp);
11498776Sgadstatic void	 fhosterr(int _ch_opts, char *_sysmsg, char *_usermsg);
11578302Sgadstatic int	*socksetup(int _af, int _debuglvl);
11678146Sgadstatic void	 usage(void);
1171553Srgrimes
11870098Sume/* XXX from libc/net/rcmd.c */
119173412Skevloextern int __ivaliduser_sa(FILE *, struct sockaddr *, socklen_t,
120173412Skevlo				const char *, const char *);
12170098Sume
12227618Simpuid_t	uid, euid;
12327618Simp
12478749Sgad#define LPD_NOPORTCHK	0001		/* skip reserved-port check */
12578749Sgad#define LPD_LOGCONNERR	0002		/* (sys)log connection errors */
12698776Sgad#define LPD_ADDFROMLINE	0004		/* just used for fhosterr() */
12778749Sgad
1281553Srgrimesint
12978146Sgadmain(int argc, char **argv)
1301553Srgrimes{
13180170Sgad	int ch_options, errs, f, funix, *finet, i, lfd, socket_debug;
1321553Srgrimes	fd_set defreadfds;
1331553Srgrimes	struct sockaddr_un un, fromunix;
13470098Sume	struct sockaddr_storage frominet;
13580170Sgad	socklen_t fromlen;
13631492Swollman	sigset_t omask, nmask;
13728621Sjoerg	struct servent *sp, serv;
13870098Sume	int inet_flag = 0, inet6_flag = 0;
1391553Srgrimes
14027618Simp	euid = geteuid();	/* these shouldn't be different */
14127618Simp	uid = getuid();
14278749Sgad
14378749Sgad	ch_options = 0;
14468742Sgad	socket_debug = 0;
14578300Sgad	gethostname(local_host, sizeof(local_host));
1461553Srgrimes
14778280Sgad	progname = "lpd";
14827618Simp
14928621Sjoerg	if (euid != 0)
15028621Sjoerg		errx(EX_NOPERM,"must run as root");
15127618Simp
15228621Sjoerg	errs = 0;
15395070Sgad	while ((i = getopt(argc, argv, "cdlpswW46")) != -1)
15428621Sjoerg		switch (i) {
15578749Sgad		case 'c':
15678749Sgad			/* log all kinds of connection-errors to syslog */
15778749Sgad			ch_options |= LPD_LOGCONNERR;
15878749Sgad			break;
15928621Sjoerg		case 'd':
16068742Sgad			socket_debug++;
16128621Sjoerg			break;
16228621Sjoerg		case 'l':
16328621Sjoerg			lflag++;
16428621Sjoerg			break;
16595070Sgad		case 'p':		/* letter initially used for -s */
16695070Sgad			/*
16795070Sgad			 * This will probably be removed with 5.0-release.
16895070Sgad			 */
16995070Sgad			/* FALLTHROUGH */
17095070Sgad		case 's':		/* secure (no inet) */
17195070Sgad			sflag++;
17258777Ssheldonh			break;
17380122Sgad		case 'w':		/* netbsd uses -w for maxwait */
17480122Sgad			/*
17580122Sgad			 * This will be removed after the release of 4.4, as
17680122Sgad			 * it conflicts with -w in netbsd's lpd.  For now it
17780122Sgad			 * is just a warning, so we won't suddenly break lpd
17880122Sgad			 * for anyone who is currently using the option.
17980122Sgad			 */
18080122Sgad			syslog(LOG_WARNING,
18180122Sgad			    "NOTE: the -w option has been renamed -W");
18280122Sgad			syslog(LOG_WARNING,
18380122Sgad			    "NOTE: please change your lpd config to use -W");
18480122Sgad			/* FALLTHROUGH */
18580122Sgad		case 'W':
18678749Sgad			/* allow connections coming from a non-reserved port */
18778749Sgad			/* (done by some lpr-implementations for MS-Windows) */
18878749Sgad			ch_options |= LPD_NOPORTCHK;
18978749Sgad			break;
19070098Sume		case '4':
19170098Sume			family = PF_INET;
19270098Sume			inet_flag++;
19370098Sume			break;
19470098Sume		case '6':
19574127Sgad#ifdef INET6
19670098Sume			family = PF_INET6;
19770098Sume			inet6_flag++;
19874127Sgad#else
19974127Sgad			errx(EX_USAGE, "lpd compiled sans INET6 (IPv6 support)");
20074127Sgad#endif
20170098Sume			break;
20280122Sgad		/*
20380122Sgad		 * The following options are not in FreeBSD (yet?), but are
20480122Sgad		 * listed here to "reserve" them, because the option-letters
20580122Sgad		 * are used by either NetBSD or OpenBSD (as of July 2001).
20680122Sgad		 */
20780122Sgad		case 'b':		/* set bind-addr */
20880122Sgad		case 'n':		/* set max num of children */
20980122Sgad		case 'r':		/* allow 'of' for remote ptrs */
21080122Sgad					/* ...[not needed in freebsd] */
21180122Sgad			/* FALLTHROUGH */
21228621Sjoerg		default:
21328621Sjoerg			errs++;
21428621Sjoerg		}
21570098Sume	if (inet_flag && inet6_flag)
21670098Sume		family = PF_UNSPEC;
21728621Sjoerg	argc -= optind;
21828621Sjoerg	argv += optind;
21928621Sjoerg	if (errs)
22028621Sjoerg		usage();
22128621Sjoerg
22228621Sjoerg	if (argc == 1) {
22328621Sjoerg		if ((i = atoi(argv[0])) == 0)
22428621Sjoerg			usage();
22528621Sjoerg		if (i < 0 || i > USHRT_MAX)
22628621Sjoerg			errx(EX_USAGE, "port # %d is invalid", i);
22728621Sjoerg
22828621Sjoerg		serv.s_port = htons(i);
22928621Sjoerg		sp = &serv;
23028621Sjoerg		argc--;
23128621Sjoerg	} else {
23228621Sjoerg		sp = getservbyname("printer", "tcp");
23328621Sjoerg		if (sp == NULL)
23428621Sjoerg			errx(EX_OSFILE, "printer/tcp: unknown service");
2351553Srgrimes	}
2361553Srgrimes
23728621Sjoerg	if (argc != 0)
23828621Sjoerg		usage();
23928621Sjoerg
24031492Swollman	/*
24131492Swollman	 * We run chkprintcap right away to catch any errors and blat them
24231492Swollman	 * to stderr while we still have it open, rather than sending them
24331492Swollman	 * to syslog and leaving the user wondering why lpd started and
24431492Swollman	 * then stopped.  There should probably be a command-line flag to
24531492Swollman	 * ignore errors from chkprintcap.
24631492Swollman	 */
24731492Swollman	{
24831492Swollman		pid_t pid;
24931492Swollman		int status;
25031492Swollman		pid = fork();
25131492Swollman		if (pid < 0) {
25231492Swollman			err(EX_OSERR, "cannot fork");
25331492Swollman		} else if (pid == 0) {	/* child */
25431492Swollman			execl(_PATH_CHKPRINTCAP, _PATH_CHKPRINTCAP, (char *)0);
25531492Swollman			err(EX_OSERR, "cannot execute %s", _PATH_CHKPRINTCAP);
25631492Swollman		}
25731492Swollman		if (waitpid(pid, &status, 0) < 0) {
25831492Swollman			err(EX_OSERR, "cannot wait");
25931492Swollman		}
26031492Swollman		if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
26131492Swollman			errx(EX_OSFILE, "%d errors in printcap file, exiting",
26231492Swollman			     WEXITSTATUS(status));
26331492Swollman	}
26431492Swollman
2651553Srgrimes#ifndef DEBUG
2661553Srgrimes	/*
2671553Srgrimes	 * Set up standard environment by detaching from the parent.
2681553Srgrimes	 */
2691553Srgrimes	daemon(0, 0);
2701553Srgrimes#endif
2711553Srgrimes
2721553Srgrimes	openlog("lpd", LOG_PID, LOG_LPR);
27395070Sgad	syslog(LOG_INFO, "lpd startup: logging=%d%s%s", lflag,
27495070Sgad	    socket_debug ? " dbg" : "", sflag ? " net-secure" : "");
2751553Srgrimes	(void) umask(0);
27631492Swollman	/*
27731492Swollman	 * NB: This depends on O_NONBLOCK semantics doing the right thing;
27831492Swollman	 * i.e., applying only to the O_EXLOCK and not to the rest of the
27931492Swollman	 * open/creation.  As of 1997-12-02, this is the case for commonly-
28031492Swollman	 * used filesystems.  There are other places in this code which
28131492Swollman	 * make the same assumption.
28231492Swollman	 */
28331492Swollman	lfd = open(_PATH_MASTERLOCK, O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK,
28431492Swollman		   LOCK_FILE_MODE);
2851553Srgrimes	if (lfd < 0) {
28695068Sgad		if (errno == EWOULDBLOCK)	/* active daemon present */
2871553Srgrimes			exit(0);
2881553Srgrimes		syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
2891553Srgrimes		exit(1);
2901553Srgrimes	}
29131492Swollman	fcntl(lfd, F_SETFL, 0);	/* turn off non-blocking mode */
2921553Srgrimes	ftruncate(lfd, 0);
2931553Srgrimes	/*
2941553Srgrimes	 * write process id for others to know
2951553Srgrimes	 */
2961553Srgrimes	sprintf(line, "%u\n", getpid());
2971553Srgrimes	f = strlen(line);
2981553Srgrimes	if (write(lfd, line, f) != f) {
2991553Srgrimes		syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
3001553Srgrimes		exit(1);
3011553Srgrimes	}
3021553Srgrimes	signal(SIGCHLD, reapchild);
3031553Srgrimes	/*
3041553Srgrimes	 * Restart all the printers.
3051553Srgrimes	 */
3061553Srgrimes	startup();
3071553Srgrimes	(void) unlink(_PATH_SOCKETNAME);
3081553Srgrimes	funix = socket(AF_UNIX, SOCK_STREAM, 0);
3091553Srgrimes	if (funix < 0) {
3101553Srgrimes		syslog(LOG_ERR, "socket: %m");
3111553Srgrimes		exit(1);
3121553Srgrimes	}
31331492Swollman
31431492Swollman	sigemptyset(&nmask);
31531492Swollman	sigaddset(&nmask, SIGHUP);
31631492Swollman	sigaddset(&nmask, SIGINT);
31731492Swollman	sigaddset(&nmask, SIGQUIT);
31831492Swollman	sigaddset(&nmask, SIGTERM);
31931492Swollman	sigprocmask(SIG_BLOCK, &nmask, &omask);
32031492Swollman
32119202Simp	(void) umask(07);
3221553Srgrimes	signal(SIGHUP, mcleanup);
3231553Srgrimes	signal(SIGINT, mcleanup);
3241553Srgrimes	signal(SIGQUIT, mcleanup);
3251553Srgrimes	signal(SIGTERM, mcleanup);
3261553Srgrimes	memset(&un, 0, sizeof(un));
3271553Srgrimes	un.sun_family = AF_UNIX;
3281553Srgrimes	strcpy(un.sun_path, _PATH_SOCKETNAME);
3291553Srgrimes#ifndef SUN_LEN
3301553Srgrimes#define SUN_LEN(unp) (strlen((unp)->sun_path) + 2)
3311553Srgrimes#endif
3321553Srgrimes	if (bind(funix, (struct sockaddr *)&un, SUN_LEN(&un)) < 0) {
3331553Srgrimes		syslog(LOG_ERR, "ubind: %m");
3341553Srgrimes		exit(1);
3351553Srgrimes	}
33619202Simp	(void) umask(0);
33731492Swollman	sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
3381553Srgrimes	FD_ZERO(&defreadfds);
3391553Srgrimes	FD_SET(funix, &defreadfds);
3401553Srgrimes	listen(funix, 5);
34195070Sgad	if (sflag == 0) {
34278302Sgad		finet = socksetup(family, socket_debug);
34370098Sume	} else
34470098Sume		finet = NULL;	/* pretend we couldn't open TCP socket. */
34570098Sume	if (finet) {
34670098Sume		for (i = 1; i <= *finet; i++) {
34770098Sume			FD_SET(finet[i], &defreadfds);
34870098Sume			listen(finet[i], 5);
3491553Srgrimes		}
3501553Srgrimes	}
3511553Srgrimes	/*
3521553Srgrimes	 * Main loop: accept, do a request, continue.
3531553Srgrimes	 */
3541553Srgrimes	memset(&frominet, 0, sizeof(frominet));
3551553Srgrimes	memset(&fromunix, 0, sizeof(fromunix));
35668742Sgad	if (lflag)
35768742Sgad		syslog(LOG_INFO, "lpd startup: ready to accept requests");
35831492Swollman	/*
35931492Swollman	 * XXX - should be redone for multi-protocol
36031492Swollman	 */
3611553Srgrimes	for (;;) {
36278302Sgad		int domain, nfds, s;
3631553Srgrimes		fd_set readfds;
3641553Srgrimes
3651553Srgrimes		FD_COPY(&defreadfds, &readfds);
3661553Srgrimes		nfds = select(20, &readfds, 0, 0, 0);
3671553Srgrimes		if (nfds <= 0) {
3681553Srgrimes			if (nfds < 0 && errno != EINTR)
3691553Srgrimes				syslog(LOG_WARNING, "select: %m");
3701553Srgrimes			continue;
3711553Srgrimes		}
37278302Sgad		domain = -1;		    /* avoid compile-time warning */
37378302Sgad		s = -1;			    /* avoid compile-time warning */
3741553Srgrimes		if (FD_ISSET(funix, &readfds)) {
3751553Srgrimes			domain = AF_UNIX, fromlen = sizeof(fromunix);
3761553Srgrimes			s = accept(funix,
3771553Srgrimes			    (struct sockaddr *)&fromunix, &fromlen);
37870098Sume 		} else {
37970098Sume                        for (i = 1; i <= *finet; i++)
38070098Sume				if (FD_ISSET(finet[i], &readfds)) {
38170098Sume					domain = AF_INET;
38270098Sume					fromlen = sizeof(frominet);
38370098Sume					s = accept(finet[i],
38470098Sume					    (struct sockaddr *)&frominet,
38570098Sume					    &fromlen);
38670098Sume				}
3871553Srgrimes		}
3881553Srgrimes		if (s < 0) {
3891553Srgrimes			if (errno != EINTR)
3901553Srgrimes				syslog(LOG_WARNING, "accept: %m");
3911553Srgrimes			continue;
3921553Srgrimes		}
3931553Srgrimes		if (fork() == 0) {
39479735Sgad			/*
39579735Sgad			 * Note that printjob() also plays around with
39679735Sgad			 * signal-handling routines, and may need to be
39779735Sgad			 * changed when making changes to signal-handling.
39879735Sgad			 */
39979735Sgad			signal(SIGCHLD, SIG_DFL);
4001553Srgrimes			signal(SIGHUP, SIG_IGN);
4011553Srgrimes			signal(SIGINT, SIG_IGN);
4021553Srgrimes			signal(SIGQUIT, SIG_IGN);
4031553Srgrimes			signal(SIGTERM, SIG_IGN);
4041553Srgrimes			(void) close(funix);
40595070Sgad			if (sflag == 0 && finet) {
40670098Sume                        	for (i = 1; i <= *finet; i++)
40770098Sume					(void)close(finet[i]);
40858777Ssheldonh			}
409118881Sgad			dup2(s, STDOUT_FILENO);
4101553Srgrimes			(void) close(s);
4111553Srgrimes			if (domain == AF_INET) {
41270098Sume				/* for both AF_INET and AF_INET6 */
4131553Srgrimes				from_remote = 1;
41478749Sgad 				chkhost((struct sockaddr *)&frominet,
41578749Sgad				    ch_options);
4161553Srgrimes			} else
4171553Srgrimes				from_remote = 0;
4181553Srgrimes			doit();
4191553Srgrimes			exit(0);
4201553Srgrimes		}
4211553Srgrimes		(void) close(s);
4221553Srgrimes	}
4231553Srgrimes}
4241553Srgrimes
4251553Srgrimesstatic void
42678146Sgadreapchild(int signo __unused)
4271553Srgrimes{
42897781Sgad	int status;
4291553Srgrimes
43097781Sgad	while (wait3(&status, WNOHANG, 0) > 0)
4311553Srgrimes		;
4321553Srgrimes}
4331553Srgrimes
4341553Srgrimesstatic void
43578146Sgadmcleanup(int signo)
4361553Srgrimes{
43766414Swollman	/*
43866414Swollman	 * XXX syslog(3) is not signal-safe.
43966414Swollman	 */
44066414Swollman	if (lflag) {
44166414Swollman		if (signo)
44266414Swollman			syslog(LOG_INFO, "exiting on signal %d", signo);
44366414Swollman		else
44466414Swollman			syslog(LOG_INFO, "exiting");
44566414Swollman	}
4461553Srgrimes	unlink(_PATH_SOCKETNAME);
4471553Srgrimes	exit(0);
4481553Srgrimes}
4491553Srgrimes
4501553Srgrimes/*
4511553Srgrimes * Stuff for handling job specifications
4521553Srgrimes */
4531553Srgrimeschar	*user[MAXUSERS];	/* users to process */
4541553Srgrimesint	users;			/* # of users in user array */
4551553Srgrimesint	requ[MAXREQUESTS];	/* job number of spool entries */
4561553Srgrimesint	requests;		/* # of spool requests */
4571553Srgrimeschar	*person;		/* name of person doing lprm */
4581553Srgrimes
45978300Sgad		 /* buffer to hold the client's machine-name */
46078300Sgadstatic char	 frombuf[MAXHOSTNAMELEN];
4611553Srgrimeschar	cbuf[BUFSIZ];		/* command line buffer */
46278146Sgadconst char	*cmdnames[] = {
4631553Srgrimes	"null",
4641553Srgrimes	"printjob",
4651553Srgrimes	"recvjob",
4661553Srgrimes	"displayq short",
4671553Srgrimes	"displayq long",
4681553Srgrimes	"rmjob"
4691553Srgrimes};
4701553Srgrimes
4711553Srgrimesstatic void
47278146Sgaddoit(void)
4731553Srgrimes{
47431492Swollman	char *cp, *printer;
47531492Swollman	int n;
47631492Swollman	int status;
47731492Swollman	struct printer myprinter, *pp = &myprinter;
4781553Srgrimes
47931492Swollman	init_printer(&myprinter);
48031492Swollman
4811553Srgrimes	for (;;) {
4821553Srgrimes		cp = cbuf;
4831553Srgrimes		do {
4841553Srgrimes			if (cp >= &cbuf[sizeof(cbuf) - 1])
48531492Swollman				fatal(0, "Command line too long");
48680113Sgad			if ((n = read(STDOUT_FILENO, cp, 1)) != 1) {
4871553Srgrimes				if (n < 0)
48831492Swollman					fatal(0, "Lost connection");
4891553Srgrimes				return;
4901553Srgrimes			}
4911553Srgrimes		} while (*cp++ != '\n');
4921553Srgrimes		*--cp = '\0';
4931553Srgrimes		cp = cbuf;
4941553Srgrimes		if (lflag) {
4951553Srgrimes			if (*cp >= '\1' && *cp <= '\5')
4961553Srgrimes				syslog(LOG_INFO, "%s requests %s %s",
49778300Sgad					from_host, cmdnames[(u_char)*cp], cp+1);
4981553Srgrimes			else
4991553Srgrimes				syslog(LOG_INFO, "bad request (%d) from %s",
50078300Sgad					*cp, from_host);
5011553Srgrimes		}
5021553Srgrimes		switch (*cp++) {
50331492Swollman		case CMD_CHECK_QUE: /* check the queue, print any jobs there */
50431492Swollman			startprinting(cp);
5051553Srgrimes			break;
50631492Swollman		case CMD_TAKE_THIS: /* receive files to be queued */
5071553Srgrimes			if (!from_remote) {
5081553Srgrimes				syslog(LOG_INFO, "illegal request (%d)", *cp);
5091553Srgrimes				exit(1);
5101553Srgrimes			}
51131492Swollman			recvjob(cp);
5121553Srgrimes			break;
51331492Swollman		case CMD_SHOWQ_SHORT: /* display the queue (short form) */
51431492Swollman		case CMD_SHOWQ_LONG: /* display the queue (long form) */
51531492Swollman			/* XXX - this all needs to be redone. */
5161553Srgrimes			printer = cp;
5171553Srgrimes			while (*cp) {
5181553Srgrimes				if (*cp != ' ') {
5191553Srgrimes					cp++;
5201553Srgrimes					continue;
5211553Srgrimes				}
5221553Srgrimes				*cp++ = '\0';
5231553Srgrimes				while (isspace(*cp))
5241553Srgrimes					cp++;
5251553Srgrimes				if (*cp == '\0')
5261553Srgrimes					break;
5271553Srgrimes				if (isdigit(*cp)) {
5281553Srgrimes					if (requests >= MAXREQUESTS)
52931492Swollman						fatal(0, "Too many requests");
5301553Srgrimes					requ[requests++] = atoi(cp);
5311553Srgrimes				} else {
5321553Srgrimes					if (users >= MAXUSERS)
53331492Swollman						fatal(0, "Too many users");
5341553Srgrimes					user[users++] = cp;
5351553Srgrimes				}
5361553Srgrimes			}
53731492Swollman			status = getprintcap(printer, pp);
53831492Swollman			if (status < 0)
53979739Sgad				fatal(pp, "%s", pcaperr(status));
54031492Swollman			displayq(pp, cbuf[0] == CMD_SHOWQ_LONG);
5411553Srgrimes			exit(0);
54231492Swollman		case CMD_RMJOB:	/* remove a job from the queue */
5431553Srgrimes			if (!from_remote) {
5441553Srgrimes				syslog(LOG_INFO, "illegal request (%d)", *cp);
5451553Srgrimes				exit(1);
5461553Srgrimes			}
5471553Srgrimes			printer = cp;
5481553Srgrimes			while (*cp && *cp != ' ')
5491553Srgrimes				cp++;
5501553Srgrimes			if (!*cp)
5511553Srgrimes				break;
5521553Srgrimes			*cp++ = '\0';
5531553Srgrimes			person = cp;
5541553Srgrimes			while (*cp) {
5551553Srgrimes				if (*cp != ' ') {
5561553Srgrimes					cp++;
5571553Srgrimes					continue;
5581553Srgrimes				}
5591553Srgrimes				*cp++ = '\0';
5601553Srgrimes				while (isspace(*cp))
5611553Srgrimes					cp++;
5621553Srgrimes				if (*cp == '\0')
5631553Srgrimes					break;
5641553Srgrimes				if (isdigit(*cp)) {
5651553Srgrimes					if (requests >= MAXREQUESTS)
56631492Swollman						fatal(0, "Too many requests");
5671553Srgrimes					requ[requests++] = atoi(cp);
5681553Srgrimes				} else {
5691553Srgrimes					if (users >= MAXUSERS)
57031492Swollman						fatal(0, "Too many users");
5711553Srgrimes					user[users++] = cp;
5721553Srgrimes				}
5731553Srgrimes			}
57431492Swollman			rmjob(printer);
5751553Srgrimes			break;
5761553Srgrimes		}
57731492Swollman		fatal(0, "Illegal service request");
5781553Srgrimes	}
5791553Srgrimes}
5801553Srgrimes
5811553Srgrimes/*
5821553Srgrimes * Make a pass through the printcap database and start printing any
5831553Srgrimes * files left from the last time the machine went down.
5841553Srgrimes */
5851553Srgrimesstatic void
58678146Sgadstartup(void)
5871553Srgrimes{
58831492Swollman	int pid, status, more;
58931492Swollman	struct printer myprinter, *pp = &myprinter;
5901553Srgrimes
59131492Swollman	more = firstprinter(pp, &status);
59231492Swollman	if (status)
59331492Swollman		goto errloop;
59431492Swollman	while (more) {
59531492Swollman		if (ckqueue(pp) <= 0) {
59631492Swollman			goto next;
59725856Sbrian		}
59815637Sjoerg		if (lflag)
59968742Sgad			syslog(LOG_INFO, "lpd startup: work for %s",
60068742Sgad			    pp->printer);
6011553Srgrimes		if ((pid = fork()) < 0) {
60268742Sgad			syslog(LOG_WARNING, "lpd startup: cannot fork for %s",
60368742Sgad			    pp->printer);
6041553Srgrimes			mcleanup(0);
6051553Srgrimes		}
60631492Swollman		if (pid == 0) {
60731492Swollman			lastprinter();
60831492Swollman			printjob(pp);
60915637Sjoerg			/* NOTREACHED */
6101553Srgrimes		}
61131492Swollman		do {
61231492Swollmannext:
61331492Swollman			more = nextprinter(pp, &status);
61431492Swollmanerrloop:
61531492Swollman			if (status)
61631492Swollman				syslog(LOG_WARNING,
61768742Sgad				    "lpd startup: printcap entry for %s has errors, skipping",
61878317Sgad				    pp->printer ? pp->printer : "<noname?>");
61931492Swollman		} while (more && status);
6201553Srgrimes	}
6211553Srgrimes}
6221553Srgrimes
62315637Sjoerg/*
62415637Sjoerg * Make sure there's some work to do before forking off a child
62515637Sjoerg */
62615637Sjoergstatic int
62778146Sgadckqueue(struct printer *pp)
62815637Sjoerg{
62915637Sjoerg	register struct dirent *d;
63015637Sjoerg	DIR *dirp;
63115637Sjoerg	char *spooldir;
63215637Sjoerg
63331492Swollman	spooldir = pp->spool_dir;
63415637Sjoerg	if ((dirp = opendir(spooldir)) == NULL)
63515637Sjoerg		return (-1);
63615637Sjoerg	while ((d = readdir(dirp)) != NULL) {
63715637Sjoerg		if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
63815637Sjoerg			continue;	/* daemon control files only */
63915637Sjoerg		closedir(dirp);
64015637Sjoerg		return (1);		/* found something */
64115637Sjoerg	}
64215637Sjoerg	closedir(dirp);
64315637Sjoerg	return (0);
64415637Sjoerg}
64515637Sjoerg
6461553Srgrimes#define DUMMY ":nobody::"
6471553Srgrimes
6481553Srgrimes/*
64978749Sgad * Check to see if the host connecting to this host has access to any
65078749Sgad * lpd services on this host.
6511553Srgrimes */
6521553Srgrimesstatic void
65378749Sgadchkhost(struct sockaddr *f, int ch_opts)
6541553Srgrimes{
65570098Sume	struct addrinfo hints, *res, *r;
6561553Srgrimes	register FILE *hostf;
65778300Sgad	char hostbuf[NI_MAXHOST], ip[NI_MAXHOST];
65870098Sume	char serv[NI_MAXSERV];
65998776Sgad	char *syserr, *usererr;
660298592Sgad	int error, errsav, fpass, good;
6611553Srgrimes
66278749Sgad	from_host = ".na.";
66378749Sgad
6641553Srgrimes	/* Need real hostname for temporary filenames */
66578300Sgad	error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf), NULL, 0,
66678300Sgad	    NI_NAMEREQD);
66770098Sume	if (error) {
66878749Sgad		errsav = error;
66978300Sgad		error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf),
670146188Sume		    NULL, 0, NI_NUMERICHOST);
67198776Sgad		if (error) {
67298776Sgad			asprintf(&syserr,
67398776Sgad			    "can not determine hostname for remote host (%d,%d)",
67498776Sgad			    errsav, error);
67598776Sgad			asprintf(&usererr,
67698776Sgad			    "Host name for your address is not known");
67798776Sgad			fhosterr(ch_opts, syserr, usererr);
67898776Sgad			/* NOTREACHED */
67998776Sgad		}
68098776Sgad		asprintf(&syserr,
68198776Sgad		    "Host name for remote host (%s) not known (%d)",
68298776Sgad		    hostbuf, errsav);
68398776Sgad		asprintf(&usererr,
68498776Sgad		    "Host name for your address (%s) is not known",
68598776Sgad		    hostbuf);
68698776Sgad		fhosterr(ch_opts, syserr, usererr);
68798776Sgad		/* NOTREACHED */
68870098Sume	}
6891553Srgrimes
69078300Sgad	strlcpy(frombuf, hostbuf, sizeof(frombuf));
69178300Sgad	from_host = frombuf;
69298776Sgad	ch_opts |= LPD_ADDFROMLINE;
69370098Sume
69470098Sume	/* Need address in stringform for comparison (no DNS lookup here) */
69578300Sgad	error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf), NULL, 0,
696146188Sume	    NI_NUMERICHOST);
69798776Sgad	if (error) {
69898776Sgad		asprintf(&syserr, "Cannot print IP address (error %d)",
69998776Sgad		    error);
70098776Sgad		asprintf(&usererr, "Cannot print IP address for your host");
70198776Sgad		fhosterr(ch_opts, syserr, usererr);
70298776Sgad		/* NOTREACHED */
70398776Sgad	}
70478300Sgad	from_ip = strdup(hostbuf);
7051553Srgrimes
70670098Sume	/* Reject numeric addresses */
70770098Sume	memset(&hints, 0, sizeof(hints));
70870098Sume	hints.ai_family = family;
70970098Sume	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
71070098Sume	hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
71178300Sgad	if (getaddrinfo(from_host, NULL, &hints, &res) == 0) {
71270098Sume		freeaddrinfo(res);
71398776Sgad		/* This syslog message already includes from_host */
71498776Sgad		ch_opts &= ~LPD_ADDFROMLINE;
71598776Sgad		asprintf(&syserr, "reverse lookup results in non-FQDN %s",
71678749Sgad		    from_host);
71798776Sgad		/* same message to both syslog and remote user */
71898776Sgad		fhosterr(ch_opts, syserr, syserr);
71998776Sgad		/* NOTREACHED */
72070098Sume	}
72170098Sume
72219202Simp	/* Check for spoof, ala rlogind */
72370098Sume	memset(&hints, 0, sizeof(hints));
72470098Sume	hints.ai_family = family;
72570098Sume	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
72678300Sgad	error = getaddrinfo(from_host, NULL, &hints, &res);
72770098Sume	if (error) {
72898776Sgad		asprintf(&syserr, "dns lookup for address %s failed: %s",
72998776Sgad		    from_ip, gai_strerror(error));
73098776Sgad		asprintf(&usererr, "hostname for your address (%s) unknown: %s",
73198776Sgad		    from_ip, gai_strerror(error));
73298776Sgad		fhosterr(ch_opts, syserr, usererr);
73398776Sgad		/* NOTREACHED */
73470098Sume	}
73570098Sume	good = 0;
73670098Sume	for (r = res; good == 0 && r; r = r->ai_next) {
73770098Sume		error = getnameinfo(r->ai_addr, r->ai_addrlen, ip, sizeof(ip),
738146188Sume		    NULL, 0, NI_NUMERICHOST);
73970098Sume		if (!error && !strcmp(from_ip, ip))
74019202Simp			good = 1;
74119202Simp	}
74270098Sume	if (res)
74370098Sume		freeaddrinfo(res);
74498776Sgad	if (good == 0) {
74598776Sgad		asprintf(&syserr, "address for remote host (%s) not matched",
74698776Sgad		    from_ip);
74798776Sgad		asprintf(&usererr,
74878749Sgad		    "address for your hostname (%s) not matched", from_ip);
74998776Sgad		fhosterr(ch_opts, syserr, usererr);
75098776Sgad		/* NOTREACHED */
75198776Sgad	}
75219202Simp
75378749Sgad	fpass = 1;
7541553Srgrimes	hostf = fopen(_PATH_HOSTSEQUIV, "r");
7551553Srgrimesagain:
7561553Srgrimes	if (hostf) {
75770098Sume		if (__ivaliduser_sa(hostf, f, f->sa_len, DUMMY, DUMMY) == 0) {
7581553Srgrimes			(void) fclose(hostf);
75978749Sgad			goto foundhost;
7601553Srgrimes		}
7611553Srgrimes		(void) fclose(hostf);
7621553Srgrimes	}
76378749Sgad	if (fpass == 1) {
76478749Sgad		fpass = 2;
7651553Srgrimes		hostf = fopen(_PATH_HOSTSLPD, "r");
7661553Srgrimes		goto again;
7671553Srgrimes	}
76898776Sgad	/* This syslog message already includes from_host */
76998776Sgad	ch_opts &= ~LPD_ADDFROMLINE;
77098776Sgad	asprintf(&syserr, "refused connection from %s, sip=%s", from_host,
77178749Sgad	    from_ip);
77298776Sgad	asprintf(&usererr,
77398776Sgad	    "Print-services are not available to your host (%s).", from_host);
77498776Sgad	fhosterr(ch_opts, syserr, usererr);
77598776Sgad	/* NOTREACHED */
77678749Sgad
77778749Sgadfoundhost:
77878749Sgad	if (ch_opts & LPD_NOPORTCHK)
77978749Sgad		return;			/* skip the reserved-port check */
78078749Sgad
78178749Sgad	error = getnameinfo(f, f->sa_len, NULL, 0, serv, sizeof(serv),
78278749Sgad	    NI_NUMERICSERV);
78398776Sgad	if (error) {
78498776Sgad		/* same message to both syslog and remote user */
78598776Sgad		asprintf(&syserr, "malformed from-address (%d)", error);
78698776Sgad		fhosterr(ch_opts, syserr, syserr);
78798776Sgad		/* NOTREACHED */
78898776Sgad	}
78978749Sgad
79098776Sgad	if (atoi(serv) >= IPPORT_RESERVED) {
79198776Sgad		/* same message to both syslog and remote user */
79298776Sgad		asprintf(&syserr, "connected from invalid port (%s)", serv);
79398776Sgad		fhosterr(ch_opts, syserr, syserr);
79498776Sgad		/* NOTREACHED */
79598776Sgad	}
7961553Srgrimes}
79728621Sjoerg
79878749Sgad/*
79998776Sgad * Handle fatal errors in chkhost.  The first message will optionally be
80098776Sgad * sent to syslog, the second one is sent to the connecting host.
80178749Sgad *
80278749Sgad * The idea is that the syslog message is meant for an administrator of a
80378749Sgad * print server (the host receiving connections), while the usermsg is meant
80478749Sgad * for a remote user who may or may not be clueful, and may or may not be
80578749Sgad * doing something nefarious.  Some remote users (eg, MS-Windows...) may not
80678749Sgad * even see whatever message is sent, which is why there's the option to
80778749Sgad * start 'lpd' with the connection-errors also sent to syslog.
80878749Sgad *
80978749Sgad * Given that hostnames can theoretically be fairly long (well, over 250
81078749Sgad * bytes), it would probably be helpful to have the 'from_host' field at
81178749Sgad * the end of any error messages which include that info.
81298776Sgad *
81398776Sgad * These are Fatal host-connection errors, so this routine does not return.
81478749Sgad */
81598776Sgadstatic void
81698776Sgadfhosterr(int ch_opts, char *sysmsg, char *usermsg)
81778749Sgad{
81878749Sgad
81998776Sgad	/*
82098776Sgad	 * If lpd was started up to print connection errors, then write
82198776Sgad	 * the syslog message before the user message.
82298776Sgad	 * And for many of the syslog messages, it is helpful to first
82398776Sgad	 * write the from_host (if it is known) as a separate syslog
82498776Sgad	 * message, since the hostname may be so long.
82598776Sgad	 */
82698776Sgad	if (ch_opts & LPD_LOGCONNERR) {
82798776Sgad		if (ch_opts & LPD_ADDFROMLINE) {
82878749Sgad		    syslog(LOG_WARNING, "for connection from %s:", from_host);
82978749Sgad		}
83098776Sgad		syslog(LOG_WARNING, "%s", sysmsg);
83178749Sgad	}
83278749Sgad
83398776Sgad	/*
83498776Sgad	 * Now send the error message to the remote host which is trying
83598776Sgad	 * to make the connection.
83698776Sgad	 */
83798776Sgad	printf("%s [@%s]: %s\n", progname, local_host, usermsg);
83878749Sgad	fflush(stdout);
83978749Sgad
84078749Sgad	/*
84178749Sgad	 * Add a minimal delay before exiting (and disconnecting from the
84278749Sgad	 * sending-host).  This is just in case that machine responds by
84378749Sgad	 * INSTANTLY retrying (and instantly re-failing...).  This may also
84478749Sgad	 * give the other side more time to read the error message.
84578749Sgad	 */
84678749Sgad	sleep(2);			/* a paranoid throttling measure */
84778749Sgad	exit(1);
84878749Sgad}
84978749Sgad
85070098Sume/* setup server socket for specified address family */
85170098Sume/* if af is PF_UNSPEC more than one socket may be returned */
85270098Sume/* the returned list is dynamically allocated, so caller needs to free it */
85370098Sumestatic int *
85478302Sgadsocksetup(int af, int debuglvl)
85570098Sume{
85670098Sume	struct addrinfo hints, *res, *r;
85770098Sume	int error, maxs, *s, *socks;
85870098Sume	const int on = 1;
85970098Sume
86070098Sume	memset(&hints, 0, sizeof(hints));
86170098Sume	hints.ai_flags = AI_PASSIVE;
86270098Sume	hints.ai_family = af;
86370098Sume	hints.ai_socktype = SOCK_STREAM;
86470098Sume	error = getaddrinfo(NULL, "printer", &hints, &res);
86570098Sume	if (error) {
86670098Sume		syslog(LOG_ERR, "%s", gai_strerror(error));
86770098Sume		mcleanup(0);
86870098Sume	}
86970098Sume
87070098Sume	/* Count max number of sockets we may open */
87170098Sume	for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
87270098Sume		;
87370098Sume	socks = malloc((maxs + 1) * sizeof(int));
87470098Sume	if (!socks) {
87570098Sume		syslog(LOG_ERR, "couldn't allocate memory for sockets");
87670098Sume		mcleanup(0);
87770098Sume	}
87870098Sume
87970098Sume	*socks = 0;   /* num of sockets counter at start of array */
88070098Sume	s = socks + 1;
88170098Sume	for (r = res; r; r = r->ai_next) {
88270098Sume		*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
88370098Sume		if (*s < 0) {
88470098Sume			syslog(LOG_DEBUG, "socket(): %m");
88570098Sume			continue;
88670098Sume		}
88778302Sgad		if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))
88878302Sgad		    < 0) {
88978302Sgad			syslog(LOG_ERR, "setsockopt(SO_REUSEADDR): %m");
89078302Sgad			close(*s);
89178302Sgad			continue;
89278302Sgad		}
89378302Sgad		if (debuglvl)
89478302Sgad			if (setsockopt(*s, SOL_SOCKET, SO_DEBUG, &debuglvl,
89578302Sgad			    sizeof(debuglvl)) < 0) {
89670098Sume				syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m");
89770098Sume				close(*s);
89870098Sume				continue;
89970098Sume			}
90070098Sume		if (r->ai_family == AF_INET6) {
901100522Sume			if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
90270098Sume				       &on, sizeof(on)) < 0) {
90370098Sume				syslog(LOG_ERR,
904100522Sume				       "setsockopt (IPV6_V6ONLY): %m");
90570098Sume				close(*s);
90670098Sume				continue;
90770098Sume			}
90870098Sume		}
90970098Sume		if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
91070098Sume			syslog(LOG_DEBUG, "bind(): %m");
91170098Sume			close(*s);
91270098Sume			continue;
91370098Sume		}
91470098Sume		(*socks)++;
91570098Sume		s++;
91670098Sume	}
91770098Sume
91870098Sume	if (res)
91970098Sume		freeaddrinfo(res);
92070098Sume
92170098Sume	if (*socks == 0) {
92270098Sume		syslog(LOG_ERR, "Couldn't bind to any socket");
92370098Sume		free(socks);
92470098Sume		mcleanup(0);
92570098Sume	}
92670098Sume	return(socks);
92770098Sume}
92874127Sgad
92974127Sgadstatic void
93078146Sgadusage(void)
93174127Sgad{
93274127Sgad#ifdef INET6
93395070Sgad	fprintf(stderr, "usage: lpd [-cdlsW46] [port#]\n");
93474127Sgad#else
93595070Sgad	fprintf(stderr, "usage: lpd [-cdlsW] [port#]\n");
93674127Sgad#endif
93774127Sgad	exit(EX_USAGE);
93874127Sgad}
939