From e066fac19f8dd58b76967b86465ce5d78339107b Mon Sep 17 00:00:00 2001 From: gad Date: Mon, 25 Jun 2001 01:45:25 +0000 Subject: Add two new options for lpd: -c will log all connection-errors to syslog, while -w allows connection from non-reserved ports. Also improves the helpfulness of various connection-error messages. The changes for IPv6 added back in the reserved-port check which was mistakenly dropped from lpd in 1997 (copying a change from openbsd). It is best to have that check in place, but the check breaks lpr's from some implementations of lpr/lpd for Windows. The -w option is for those admins who need to accept jobs from non-reserved ports, the -c option is for admins who would like a print-server machine to log all failed connection-attempts to syslog. Reviewed by: freebsd-audit@FreeBSD.org freebsd-print@bostonradio.org MFC after: 2 weeks --- usr.sbin/lpr/lpd/lpd.8 | 34 +++++++++- usr.sbin/lpr/lpd/lpd.c | 166 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 168 insertions(+), 32 deletions(-) (limited to 'usr.sbin/lpr/lpd') diff --git a/usr.sbin/lpr/lpd/lpd.8 b/usr.sbin/lpr/lpd/lpd.8 index aeb569e..ace150b 100644 --- a/usr.sbin/lpr/lpd/lpd.8 +++ b/usr.sbin/lpr/lpd/lpd.8 @@ -32,7 +32,7 @@ .\" @(#)lpd.8 8.3 (Berkeley) 4/19/94 .\" $FreeBSD$ .\" -.Dd April 19, 1994 +.Dd June 06, 2001 .Dt LPD 8 .Os BSD 4.2 .Sh NAME @@ -40,7 +40,7 @@ .Nd line printer spooler daemon .Sh SYNOPSIS .Nm -.Op Fl dlp46 +.Op Fl cdlpw46 .Op Ar port# .Sh DESCRIPTION .Nm Lpd @@ -62,6 +62,19 @@ the request so the parent can continue to listen for more requests. .Pp Available options: .Bl -tag -width Ds +.It Fl c +By default, if some remote host has a connection error while trying to +send a print request to +.Nm +on a local host, +.Nm +will only send error message to that remote host. +The +.Fl c +flag causes +.Nm +to also log all of those connection errors via +.Xr syslog 3 . .It Fl d Turn on .Dv SO_DEBUG @@ -81,6 +94,23 @@ The flag causes .Nm not to open an Internet listening socket. +This means that +.Nm +will not accept any connections from any remote +hosts, although it will still accept print requests +from all local users. +.It Fl w +By default, the +.Nm +daemon will only accept connections which originate +from a reserved-port (<1024) on the remote host. +The +.Fl w +flag causes +.Nm +to accept connections coming from any port. +This is can be useful when you want to accept print jobs +from certain implementations of lpr written for Windows. .It Fl 4 Inet only. .It Fl 6 diff --git a/usr.sbin/lpr/lpd/lpd.c b/usr.sbin/lpr/lpd/lpd.c index 9c1fdaa..16e8796 100644 --- a/usr.sbin/lpr/lpd/lpd.c +++ b/usr.sbin/lpr/lpd/lpd.c @@ -112,8 +112,10 @@ static void reapchild(int _signo); static void mcleanup(int _signo); static void doit(void); static void startup(void); -static void chkhost(struct sockaddr *_f); +static void chkhost(struct sockaddr *_f, int _ch_opts); static int ckqueue(struct printer *_pp); +static void fhosterr(int _dosys, const char *_sysmsg, const char *_usermsg, + ...); static int *socksetup(int _af, int _debuglvl); static void usage(void); @@ -123,10 +125,13 @@ extern int __ivaliduser_sa __P((FILE *, struct sockaddr *, socklen_t, uid_t uid, euid; +#define LPD_NOPORTCHK 0001 /* skip reserved-port check */ +#define LPD_LOGCONNERR 0002 /* (sys)log connection errors */ + int main(int argc, char **argv) { - int errs, f, funix, *finet, fromlen, i, options, socket_debug; + int ch_options, errs, f, funix, *finet, fromlen, i, socket_debug; fd_set defreadfds; struct sockaddr_un un, fromunix; struct sockaddr_storage frominet; @@ -137,6 +142,8 @@ main(int argc, char **argv) euid = geteuid(); /* these shouldn't be different */ uid = getuid(); + + ch_options = 0; socket_debug = 0; gethostname(local_host, sizeof(local_host)); @@ -146,8 +153,12 @@ main(int argc, char **argv) errx(EX_NOPERM,"must run as root"); errs = 0; - while ((i = getopt(argc, argv, "dlp46")) != -1) + while ((i = getopt(argc, argv, "cdlpw46")) != -1) switch (i) { + case 'c': + /* log all kinds of connection-errors to syslog */ + ch_options |= LPD_LOGCONNERR; + break; case 'd': socket_debug++; break; @@ -157,6 +168,11 @@ main(int argc, char **argv) case 'p': pflag++; break; + case 'w': + /* allow connections coming from a non-reserved port */ + /* (done by some lpr-implementations for MS-Windows) */ + ch_options |= LPD_NOPORTCHK; + break; case '4': family = PF_INET; inet_flag++; @@ -366,7 +382,8 @@ main(int argc, char **argv) if (domain == AF_INET) { /* for both AF_INET and AF_INET6 */ from_remote = 1; - chkhost((struct sockaddr *)&frominet); + chkhost((struct sockaddr *)&frominet, + ch_options); } else from_remote = 0; doit(); @@ -600,36 +617,40 @@ ckqueue(struct printer *pp) #define DUMMY ":nobody::" /* - * Check to see if the from host has access to the line printer. + * Check to see if the host connecting to this host has access to any + * lpd services on this host. */ static void -chkhost(struct sockaddr *f) +chkhost(struct sockaddr *f, int ch_opts) { struct addrinfo hints, *res, *r; register FILE *hostf; - int first = 1; - int good = 0; char hostbuf[NI_MAXHOST], ip[NI_MAXHOST]; char serv[NI_MAXSERV]; - int error, addrlen; - caddr_t addr; + int error, errsav, fpass, good, wantsl; - error = getnameinfo(f, f->sa_len, NULL, 0, serv, sizeof(serv), - NI_NUMERICSERV); - if (error || atoi(serv) >= IPPORT_RESERVED) - fatal(0, "Malformed from address"); + wantsl = 0; + if (ch_opts & LPD_LOGCONNERR) + wantsl = 1; /* also syslog the errors */ + + from_host = ".na."; /* Need real hostname for temporary filenames */ error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf), NULL, 0, NI_NAMEREQD); if (error) { + errsav = error; error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf), NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID); if (error) - fatal(0, "Host name for your address unknown"); + fhosterr(wantsl, + "can not determine hostname for remote host (%d)", + "Host name for your address not known", error); else - fatal(0, "Host name for your address (%s) unknown", - hostbuf); + fhosterr(wantsl, + "Host name for remote host (%s) not known (%d)", + "Host name for your address (%s) not known", + hostbuf, errsav); } strlcpy(frombuf, hostbuf, sizeof(frombuf)); @@ -639,7 +660,8 @@ chkhost(struct sockaddr *f) error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf), NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID); if (error) - fatal(0, "Cannot print address"); + fhosterr(wantsl, "Cannot print IP address (error %d)", + "Cannot print IP address", error); from_ip = strdup(hostbuf); /* Reject numeric addresses */ @@ -649,7 +671,8 @@ chkhost(struct sockaddr *f) hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; if (getaddrinfo(from_host, NULL, &hints, &res) == 0) { freeaddrinfo(res); - fatal(0, "reverse lookup results in non-FQDN %s", from_host); + fhosterr(wantsl, NULL, "reverse lookup results in non-FQDN %s", + from_host); } /* Check for spoof, ala rlogind */ @@ -658,38 +681,121 @@ chkhost(struct sockaddr *f) hints.ai_socktype = SOCK_DGRAM; /*dummy*/ error = getaddrinfo(from_host, NULL, &hints, &res); if (error) { - fatal(0, "hostname for your address (%s) unknown: %s", from_ip, - gai_strerror(error)); + fhosterr(wantsl, "dns lookup for address %s failed: %s", + "hostname for your address (%s) unknown: %s", from_ip, + gai_strerror(error)); } good = 0; for (r = res; good == 0 && r; r = r->ai_next) { error = getnameinfo(r->ai_addr, r->ai_addrlen, ip, sizeof(ip), - NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID); + NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID); if (!error && !strcmp(from_ip, ip)) good = 1; } if (res) freeaddrinfo(res); if (good == 0) - fatal(0, "address for your hostname (%s) not matched", - from_ip); + fhosterr(wantsl, "address for remote host (%s) not matched", + "address for your hostname (%s) not matched", from_ip); + fpass = 1; hostf = fopen(_PATH_HOSTSEQUIV, "r"); again: if (hostf) { if (__ivaliduser_sa(hostf, f, f->sa_len, DUMMY, DUMMY) == 0) { (void) fclose(hostf); - return; + goto foundhost; } (void) fclose(hostf); } - if (first == 1) { - first = 0; + if (fpass == 1) { + fpass = 2; hostf = fopen(_PATH_HOSTSLPD, "r"); goto again; } - fatal(0, "Your host does not have line printer access"); + fhosterr(wantsl, "refused connection from %s, sip=%s", + "Print-services are not available to your host (%s).", from_host, + from_ip); /*NOTREACHED*/ + +foundhost: + if (ch_opts & LPD_NOPORTCHK) + return; /* skip the reserved-port check */ + + error = getnameinfo(f, f->sa_len, NULL, 0, serv, sizeof(serv), + NI_NUMERICSERV); + if (error) + fhosterr(wantsl, NULL, "malformed from-address (%d)", error); + + if (atoi(serv) >= IPPORT_RESERVED) + fhosterr(wantsl, NULL, "connected from invalid port (%s)", + serv); +} + +#include +/* + * Handle fatal errors in chkhost. The first message will optionally be sent + * to syslog, the second one is sent to the connecting host. If the first + * message is NULL, then the same message is used for both. Note that the + * argument list for both messages are assumed to be the same (or at least + * the initial arguments for one must be EXACTLY the same as the complete + * argument list for the other message). + * + * The idea is that the syslog message is meant for an administrator of a + * print server (the host receiving connections), while the usermsg is meant + * for a remote user who may or may not be clueful, and may or may not be + * doing something nefarious. Some remote users (eg, MS-Windows...) may not + * even see whatever message is sent, which is why there's the option to + * start 'lpd' with the connection-errors also sent to syslog. + * + * Given that hostnames can theoretically be fairly long (well, over 250 + * bytes), it would probably be helpful to have the 'from_host' field at + * the end of any error messages which include that info. + */ +void +fhosterr(int dosys, const char *sysmsg, const char *usermsg, ...) +{ + va_list ap; + char *sbuf, *ubuf; + const char *testone; + + va_start(ap, usermsg); + vasprintf(&ubuf, usermsg, ap); + va_end(ap); + + if (dosys) { + sbuf = ubuf; /* assume sysmsg == NULL */ + if (sysmsg != NULL) { + va_start(ap, usermsg); + vasprintf(&sbuf, sysmsg, ap); + va_end(ap); + } + /* + * If the first variable-parameter is not the 'from_host', + * then first write THAT information as a line to syslog. + */ + va_start(ap, usermsg); + testone = va_arg(ap, const char *); + if (testone != from_host) { + syslog(LOG_WARNING, "for connection from %s:", from_host); + } + va_end(ap); + + /* now write the syslog message */ + syslog(LOG_WARNING, "%s", sbuf); + } + + printf("%s [@%s]: %s\n", progname, local_host, ubuf); + fflush(stdout); + + /* + * Add a minimal delay before exiting (and disconnecting from the + * sending-host). This is just in case that machine responds by + * INSTANTLY retrying (and instantly re-failing...). This may also + * give the other side more time to read the error message. + */ + sleep(2); /* a paranoid throttling measure */ + exit(1); } /* setup server socket for specified address family */ @@ -777,9 +883,9 @@ static void usage(void) { #ifdef INET6 - fprintf(stderr, "usage: lpd [-dlp46] [port#]\n"); + fprintf(stderr, "usage: lpd [-cdlpw46] [port#]\n"); #else - fprintf(stderr, "usage: lpd [-dlp] [port#]\n"); + fprintf(stderr, "usage: lpd [-cdlpw] [port#]\n"); #endif exit(EX_USAGE); } -- cgit v1.1