sshd also talking HTTP

Jan Pieter Cornet johnpc at xs4all.nl
Thu Jul 10 20:20:18 EST 2003


(I'm not subscribed to the list, please Cc me on replies).

We have configured sshd to listen on port 80 for some of our users who
are behind sufficiently paranoid firewalls. However, others are now
confused since they're expecting a web server on port 80.

So, I created a small patch (just as proof-of-concept so far), that
determines the type of client connecting. A web client will start talking
itself (GET, HEAD, etc...), while an ssh client will wait for the server
to issue the greeting banner.

So, the patch simply waits 1 second (should be configurable) when someone
connects to port 80 (should also be configurable), and if any data is
available by then, it decides it's an HTTP client, not an SSH client,
and sends a proper redirect.

The patch is attached (or in case the attachment gets stripped, also here:
http://www.xs4all.nl/~johnpc/dirty-sshd-hack.txt )

Could a cleaned up version of this patch be useful for inclusion in future
versions of Opensshd? Note that it is specifically _not_ the idea to put
a full-blown HTTP server inside sshd, only enough to redirect to another
URL (which should of course be configurable).

Another way to solve the same problem would be to run sshd in "inetd"
mode, behind a simple wrapper script that does the HTTP detection and
redirection. However, that has the inherent disadvantages of the inetd
mode (like wasting entropy).

I'm willing to invest some time in making the patch suitable, if it is
decided that it could be useful.

-- 
#!perl -pl	# This kenny-filter is virus-free as long as you don't copy it
$p=3-2*/[^\W\dmpf_]/i;s.[a-z]{$p}.vec($f=join('',$p-1?chr(sub{$_[0]*9+$_[1]*3+
$_[2]}->(map{/p|f/i+/f/i}split//,$&)+97):('m',p,f)[map{((ord$&)%32-1)/$_%3}(9,
3,1)]),5,1)='`'lt$&;$f.eig;				   # Jan-Pieter Cornet
-------------- next part --------------
--- sshd.c.orig	Mon Mar 10 01:38:10 2003
+++ sshd.c	Wed May 14 02:47:07 2003
@@ -52,6 +52,11 @@
 #include <sys/security.h>
 #include <prot.h>
 #endif
+#ifdef DOUBLE_AS_HTTPD
+#include <sys/select.h>
+#include <sys/time.h>
+#include <stdio.h>
+#endif
 
 #include "ssh.h"
 #include "ssh1.h"
@@ -483,6 +488,152 @@
 	}
 }
 
+#ifdef DOUBLE_AS_HTTPD
+static void sshd_act_like_an_httpd(int sock_in, int sock_out);
+static void sshd_httpd_timeout(int sig);
+
+/* intercept httpd */
+static void
+sshd_intercept_possible_httpd(int sock_in, int sock_out)
+{
+	struct sockaddr local;
+	int local_len;
+	fd_set readfds;
+	struct timeval onesec;
+
+	local_len = sizeof(local);
+	if ( getsockname(sock_in, &local, &local_len) != 0 ) {
+		log("HTTPD HACK: getsockname failed: %.100s",
+		    strerror(errno));
+		return;
+	}
+	if ( local.sa_family != AF_INET ) {
+		log("HTTPD HACK: strange sock_in.sa_family: %d",
+			local.sa_family);
+		return;
+	}
+	if ( ntohs(((struct sockaddr_in*) &local)->sin_port) != 80 ) {
+		/* XXX this logging should be removed */
+		log("HTTPD HACK: incoming port not 80 but %d",
+			ntohs(((struct sockaddr_in*) &local)->sin_port));
+		return;
+	}
+
+	/* wait 1 second for a valid http request coming in */
+	FD_ZERO(&readfds);
+	FD_SET(sock_in, &readfds);
+	onesec.tv_sec = 1;
+	onesec.tv_usec = 0;
+	if ( ! select(sock_in + 1, &readfds, NULL, NULL, &onesec) ) {
+		log("HTTPD HACK: nothing incoming for 1 second");
+		return;
+	}
+	if ( ! FD_ISSET(sock_in, &readfds) ) {
+		log("HTTPD HACK: hm, sock_in not readable");
+		return;
+	}
+
+	/*
+	 * Something is in the buffer right now. This is not an ssh client.
+	 * so from here on, we will never return to the real program,
+	 * and assume it is an HTTP request.
+	 */
+
+	sshd_act_like_an_httpd(sock_in, sock_out);
+	exit(0);
+}
+
+static void
+sshd_act_like_an_httpd(int sock_in, int sock_out)
+{
+	FILE* in;
+	char httpreq[1024];
+	char hdrline[1024];
+	char outbuf[4096];
+	char* p;
+	char* url;
+
+	/* setup an alarm call to abort playing HTTPD reasonably soon. */
+	signal(SIGALRM, sshd_httpd_timeout);
+	if (!debug_flag)
+		alarm(60);
+
+	if ( !(in = fdopen(sock_in, "r+")) ) {
+		log("HTTPD HACK: fdopen failed: %.100s", strerror(errno));
+		return;
+	}
+
+	/* read in the first line of the request */
+	if ( ! fgets(httpreq, sizeof(httpreq), in) ) {
+		log("HTTPD HACK: fgets failed on first line: %.100s",
+			strerror(errno));
+		return;
+	}
+
+	/* must be a GET request... for NOW. Support more */
+	if ( strncmp(httpreq, "GET ", 4) ) {
+		log("HTTPD HACK: no GET request, but a %.100s", httpreq);
+		return;
+	}
+	url = httpreq + 4;
+
+	if ( !(p = strchr(url, ' ')) ) {
+		log("HTTPD HACK: HTTP/0.9 request: %.100s", httpreq);
+		return;
+	}
+
+	*p++ = '\0';
+	/* must be a HTTP/1.x request */
+	if ( strncmp(p, "HTTP/1.", 7) ) {
+		log("HTTPD HACK: Not a HTTP/1.x request but %.100s", p);
+		return;
+	}
+
+	log("HTTPD HACK: faking a request for GET %.100s", url);
+
+	/* read (and ignore) the subsequent header */
+	strcpy(hdrline, "foo");
+	while ( strlen(hdrline) > 0 ) {
+		if ( ! fgets(hdrline, sizeof(hdrline), in) ) {
+			log("HTTPD HACK: fgets failed on header: %.100s",
+				strerror(errno));
+			return;
+		}
+		/* strip CR+LF */
+		if ( (p = strchr(hdrline, '\r')) != NULL )
+			*p = '\0';
+		if ( (p = strchr(hdrline, '\n')) != NULL )
+			*p = '\0';
+		log("HTTPD HACK: ignoring header %.100s", hdrline);
+	}
+
+	/* output the redirect. To a fixed site for proof of concept. */
+	snprintf(outbuf, sizeof(outbuf), "\
+HTTP/1.0 301 Don't be so lazy and type that www\r\n\
+Server: sshd %.100s\r\n\
+Location: http://www.xs4all.nl%s\r\n\
+Connection: close\r\n\
+Content-Type: text/plain\r\n\
+\r\n\
+Don't be so lazy, and simply type www.xs4all.nl instead of just xs4all.nl\r\n\
+",
+		SSH_VERSION,
+		url
+	    );
+	write(sock_out, &outbuf, strlen(outbuf));
+	return;
+}
+
+static void
+sshd_httpd_timeout(int sig)
+{
+	/* Log error and exit. */
+	fatal("Timeout in acting like HTTPD for connection from %s",
+	    get_remote_ipaddr());
+}
+
+#endif
+
 /* Destroy the host and server keys.  They will no longer be needed. */
 void
 destroy_sensitive_data(void)
@@ -1457,6 +1608,14 @@
 
 	/* Log the connection. */
 	verbose("Connection from %.500s port %d", remote_ip, remote_port);
+
+#ifdef DOUBLE_AS_HTTPD
+	/*
+	 * Wait a few instants to see if an HTTP request comes in,
+	 * then handle that. This is only done for port 80.
+	 */
+	sshd_intercept_possible_httpd(sock_in, sock_out);
+#endif
 
 	/*
 	 * We don\'t want to listen forever unless the other side


More information about the openssh-unix-dev mailing list