Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 15 Jun 2001 22:16:54 -0400
From:      Garance A Drosihn <drosih@rpi.edu>
To:        freebsd-audit@freebsd.org, freebsd-print@bostonradio.org
Cc:        Morgan Davis <mdavis@cts.com>
Subject:   Patch: new options for lpd, improved msgs for connect-errs
Message-ID:  <p05100e16b7506317331b@[128.113.24.47]>

next in thread | raw e-mail | index | archive | help
Main goals of this update to lpd:

  1) add new option '-c', which will cause lpd to log appropriate
     messages via syslog for all connection-errors.
  2) add new option '-w', which will cause lpd to accept connections
     from other hosts (generally Windows) even if the connection is
     NOT from a reserved port.
  3) generally improve the helpfulness of the various connection
     error-messages.

I realize this doesn't include an update to the man page yet, but
I thought I'd first see if people wanted the new options to key
off of other letters (instead of -c and -w).

Some discussion:
    Back in July 1997, revision 1.6 (imp) of lpd dropped the check
which required that incoming connections be coming from a reserved
port.  It looks like this was mistakenly copied from openbsd's lpd
(I intend to check further).  In at least my (RPI) environment,
that check really needs to be there.
    The IPv6 update added that check back in.  However, it then turns
out that there are some implementations for lpr on Windows which do
not bother at all with reserved ports, and those clients broke with
the lpd in 4.3-release.  The '-w' option is the quick fix so that
people who want to accept connections from non-reserved ports can
do so.

    Previous to this update, if a connection from a remote host to
a freebsd print server failed, only the remote-host got the error
messsage.  And in some universes (ahem, windows) that message is not
necessarily echoed to the user.  The '-c' option causes connection-
errors to be logged to syslog.  My assumption is that the syslog-ing
should not be done by default, because it would provide a remote user
an easy way to fill up the syslog-files on your machine.
    You'll notice my error-writing routine takes TWO 'msg-format'
strings, and (obviously) only one variable-list of parameters.  This
probably seems weird, particularly with the way I handle 'NULL' being
sent for the first fmt-string, but other alternatives seemed even
messier (IMO).
    I moved the reserved-port check to be the LAST check done (it had
been the first), partially because I wanted to have the name of the
remote host for the error messages printed.  It's also partially
because I was thinking there could be a "third file" that lpd might
scan for hostnames it would allow a connection from (and thus a third
pass in the current two-pass loop).  Say, "/etc/hosts.lpd-extras",
and maybe that third file would indicate specific hosts where a
connection would be allowed from any port, instead of using '-w'
to drop the reserved-port check for ALL hosts.  I don't really want
to implement this "third file" yet, as I have some other ideas where
that "third file" might be useful.
    Also, there's a minor change in the format of error messages sent
to the remote host.  Assuming a print server named 'pserver.rpi.edu',
the remote host would used to see messages like:

pserver.rpi.edu: lpd: Your host does not have line printer access

it now sees:

lpd [@pserver.rpi.edu]: Your host (blah.rpi.edu) does not have 
print-service access

I intend to do the same thing in the frecverr() routine in recvjob.c
(in a later update).  (just the 'lpd [@pserver]' part, I am not adding
the client's hostname to all the error messages).

    - - - - - - -
    So, please look this over, try it out, let me know what you think
about it.  This shouldn't change any of the actual CHECKS that are
being done, it should just change the error messages which result.
But, also let me know if any of the checks could be improved (in a
security-paranoid sense).  Also let me know if the messages to
syslog could be more helpful to potentially-harried administrators
trying to figure out why some client isn't able to connect...
    - - - - - - -

Index: lpd/lpd.c
===================================================================
RCS file: /home/ncvs/src/usr.sbin/lpr/lpd/lpd.c,v
retrieving revision 1.25
diff -u -r1.25 lpd.c
--- lpd/lpd.c	2001/06/16 00:14:02	1.25
+++ lpd/lpd.c	2001/06/16 01:10:34
@@ -112,8 +112,10 @@
  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 @@

  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 @@

  	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 @@
  		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 @@
  		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 @@
  			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 @@
  #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 @@
  	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 @@
  	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,120 @@
  	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",
+	    "Your host (%s) does not have print-service access", from_host);
  	/*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 <stdarg.h>
+/*
+ * 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 +882,9 @@
  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);
  }

-- 
Garance Alistair Drosehn            =   gad@eclipse.acs.rpi.edu
Senior Systems Programmer           or  gad@freebsd.org
Rensselaer Polytechnic Institute    or  drosih@rpi.edu

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-audit" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?p05100e16b7506317331b>