You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Dean Gaudet <dg...@arctic.org> on 1997/10/12 05:02:38 UTC

[CONTRIB] listenwrap

Linux (and freebsd, and others) won't dump core for programs which use
setuid (for security reasons).  There are kernel patches to work around
this ... but I can't restart one of my machines casually just to work
around this at the moment.  So here is a somewhat more portable userspace
solution.

The only thing that apache needs root for is opening the listening
socket(s).  With piped logs you can write your logs as whatever user you
want.  With suexec you can eliminate the other worrys about files created
as user nobody (or whatever you run httpd as). 

This patch adds the (repeatable) command line option "-p fd_num" which
tells apache that it has "permanent listeners" and gives their descriptor
numbers.  When it has permanent listeners it completely ignores any Listen
or BindAddress or whatever settings.

Included is a program listenwrap which should be setuid root.  It opens up
the hard-coded port number, drops privledges and execs apache with the -p
option. 

I've tested it with -USR1 restarts, but I didn't bother testing -HUP
restarts. 

It's somewhat rough and unpolished, but works for me for now. 

Dean

Index: main/http_main.c
===================================================================
RCS file: /export/home/cvs/apachen/src/main/http_main.c,v
retrieving revision 1.234
diff -u -r1.234 http_main.c
--- http_main.c	1997/10/07 19:34:01	1.234
+++ http_main.c	1997/10/12 02:45:57
@@ -195,6 +195,7 @@
  */
 listen_rec *listeners;
 static listen_rec *head_listener;
+static listen_rec *permanent_listeners;
 
 char server_root[MAX_STRING_LEN];
 char server_confname[MAX_STRING_LEN];
@@ -2406,6 +2407,9 @@
 {
     listen_rec *lr;
 
+    if (permanent_listeners) {
+	return;
+    }
     ap_assert(old_listeners == NULL);
     if (listeners == NULL) {
 	return;
@@ -2461,6 +2465,20 @@
     listen_rec *lr;
     int fd;
 
+    if (permanent_listeners) {
+	lr = permanent_listeners;
+	listeners = lr;
+	head_listener = lr;
+	do {
+	    if (lr->next == NULL) {
+		/* first time through, create the loop */
+		lr->next = permanent_listeners;
+		return;
+	    }
+	    lr = lr->next;
+	} while (lr != permanent_listeners);
+	return;
+    }
     listenmaxfd = -1;
     FD_ZERO(&listenfds);
     lr = listeners;
@@ -3323,6 +3341,58 @@
 void STANDALONE_MAIN(int argc, char **argv);
 #endif /* STANDALONE_MAIN */
 
+static void pre_opened_socket(const char *arg)
+{
+    int fd;
+    struct sockaddr_in local_addr;
+    listen_rec *lr;
+    NET_SIZE_T clen;
+
+    fd = atoi(arg);
+
+    clen = sizeof(local_addr);
+    if (getsockname(fd, (struct sockaddr *)&local_addr, &clen) == -1) {
+	aplog_error(APLOG_MARK, APLOG_ERR, NULL,
+	    "error parsing arg -p '%s', skipping", arg);
+	return;
+    }
+
+    if (local_addr.sin_family != AF_INET) {
+	aplog_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, NULL,
+	    "arg -p %d is not an AF_INET socket, skipping", fd);
+	return;
+    }
+
+#ifdef LINUX
+    /* work around linux 2.0.x bug */
+    if (local_addr.sin_addr.s_addr == htonl(0x7f000001)) {
+	local_addr.sin_addr.s_addr = 0;
+    }
+#endif
+
+    /* We don't want these descriptors to be passed down to the
+     * grandchildren.  Since we're not going to manage them in pools
+     * we have to set the close-on-exec flag.
+     */
+    if (fcntl(fd, F_SETFD, 1)) {
+	aplog_error(APLOG_MARK, APLOG_ERR, NULL,
+	    "arg -p %d, can't fcntl(%d, F_SETFD, 1), skipping", fd, fd);
+	return;
+    }
+
+    lr = malloc(sizeof(listen_rec));
+    if (lr == NULL) {
+	aplog_error(APLOG_MARK, APLOG_CRIT, NULL,
+	    "out of memory allocating permanent_listener");
+	exit(1);
+    }
+    lr->local_addr = local_addr;
+    lr->fd = fd;
+    lr->used = 0;
+    lr->next = permanent_listeners;
+    permanent_listeners = lr;
+}
+
 extern char *optarg;
 extern int optind;
 
@@ -3356,7 +3426,7 @@
 
     setup_prelinked_modules();
 
-    while ((c = getopt(argc, argv, "Xd:f:vhl")) != -1) {
+    while ((c = getopt(argc, argv, "Xd:f:vhlp:")) != -1) {
 	switch (c) {
 	case 'd':
 	    strncpy(server_root, optarg, sizeof(server_root) - 1);
@@ -3375,6 +3445,9 @@
 	case 'l':
 	    show_modules();
 	    exit(0);
+	case 'p':
+	    pre_opened_socket(optarg);
+	    break;
 	case 'X':
 	    ++one_process;	/* Weird debugging mode. */
 	    break;
Index: support/Makefile.tmpl
===================================================================
RCS file: /export/home/cvs/apachen/src/support/Makefile.tmpl,v
retrieving revision 1.6
diff -u -r1.6 Makefile.tmpl
--- Makefile.tmpl	1997/10/12 01:19:01	1.6
+++ Makefile.tmpl	1997/10/12 02:46:03
@@ -27,6 +27,9 @@
 logresolve: logresolve.c
 	$(CC) $(INCLUDES) $(CFLAGS) logresolve.c -o logresolve $(LIBS)
 
+listenwrap: listenwrap.c
+	$(CC) $(INCLUDES) $(CFLAGS) listenwrap.c -o listenwrap $(LIBS)
+
 clean:
 	rm -f $(TARGETS)
 
Index: support/listenwrap.c
===================================================================
RCS file: listenwrap.c
diff -N listenwrap.c
--- /dev/null	Sat Oct 11 19:46:00 1997
+++ listenwrap.c	Sat Oct 11 19:46:03 1997
@@ -0,0 +1,148 @@
+#include "httpd.h"
+
+#define PORT			80
+#define SEND_BUFFER_SIZE	16384
+#define LISTEN_BACKLOG		511
+#define HTTP_USER		500
+#define HTTP_GROUP		500
+#define HTTPD_PATH		"/home/dgaudet/ap/apachen4/src/httpd"
+
+/* stolen from http_main.c ... should be turned into a lib function I guess */
+
+#if defined(TCP_NODELAY) && !defined(MPE)
+static void sock_disable_nagle(int s)
+{
+    /* The Nagle algorithm says that we should delay sending partial
+     * packets in hopes of getting more data.  We don't want to do
+     * this; we are not telnet.  There are bad interactions between
+     * persistent connections and Nagle's algorithm that have very severe
+     * performance penalties.  (Failing to disable Nagle is not much of a
+     * problem with simple HTTP.)
+     *
+     * In spite of these problems, failure here is not a shooting offense.
+     */
+    int just_say_no = 1;
+
+    if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *) &just_say_no,
+		   sizeof(int)) < 0) {
+	perror("setsockopt(TCP_NODELAY)");
+    }
+}
+
+#else
+#define sock_disable_nagle(s)	/* NOOP */
+#endif
+
+#ifndef MAX_SECS_TO_LINGER
+#define MAX_SECS_TO_LINGER 30
+#endif
+
+#ifdef USE_SO_LINGER
+#define NO_LINGCLOSE		/* The two lingering options are exclusive */
+
+static void sock_enable_linger(int s)
+{
+    struct linger li;
+
+    li.l_onoff = 1;
+    li.l_linger = MAX_SECS_TO_LINGER;
+
+    if (setsockopt(s, SOL_SOCKET, SO_LINGER,
+		   (char *) &li, sizeof(struct linger)) < 0) {
+	perror("setsockopt(SO_LINGER)");
+	/* not a fatal error */
+    }
+}
+
+#else
+#define sock_enable_linger(s)	/* NOOP */
+#endif /* USE_SO_LINGER */
+
+static int make_sock(const struct sockaddr_in *local_addr)
+{
+    int s;
+    int one = 1;
+
+    if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+	perror("socket");
+	exit(1);
+    }
+
+    if (bind(s, (struct sockaddr *) local_addr,
+	sizeof(struct sockaddr_in)) == -1) {
+	perror("bind");
+	exit(1);
+    }
+
+    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
+	sizeof(int)) < 0) {
+	perror("setsockopt(SO_REUSEADDR)");
+	exit(1);
+    }
+    one = 1;
+    if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &one,
+	sizeof(int)) < 0) {
+	perror("setsockopt(SO_KEEPALIVE");
+	exit(1);
+    }
+
+    sock_disable_nagle(s);
+    sock_enable_linger(s);
+
+    one = SEND_BUFFER_SIZE;
+    if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
+	    (char *) &one, sizeof(int)) < 0) {
+	perror("setsockopt(SO_SNDBUF)");
+	/* not a fatal error */
+    }
+
+    if (listen(s, LISTEN_BACKLOG) == -1) {
+	perror("listen");
+	exit(1);
+    }
+
+    return s;
+}
+
+void main(int argc, char **argv)
+{
+    struct sockaddr_in local_addr;
+    int s;
+    char **new_argv;
+    char *new_env[] = {
+	"SHELL=/bin/sh",
+	"PATH=/usr/local/bin:/usr/bin:/bin",
+	NULL
+    };
+    char buf[512];
+
+    local_addr.sin_family = AF_INET;
+    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+    local_addr.sin_port = htons(PORT);
+
+    s = make_sock(&local_addr);
+
+    new_argv = malloc(sizeof(char *) * (argc + 3));
+    new_argv[0] = HTTPD_PATH;
+    new_argv[1] = "-p";
+    sprintf(buf, "%d", s);
+    new_argv[2] = buf;
+    memcpy(new_argv + 3, argv + 1, sizeof(char *) * (argc - 1));
+    new_argv[argc+2] = NULL;
+
+    if (setgroups(0, NULL) == -1) {
+	perror("setgroups");
+	exit(1);
+    }
+    if (setgid(HTTP_GROUP) == -1) {
+	perror("setgid");
+	exit(1);
+    }
+    if (setuid(HTTP_USER) == -1) {
+	perror("setuid");
+	exit(1);
+    }
+    execve(HTTPD_PATH, new_argv, new_env);
+    perror("execve");
+    exit(1);
+}



Re: [CONTRIB] listenwrap

Posted by Dean Gaudet <dg...@arctic.org>.

On Sat, 11 Oct 1997, Dean Gaudet wrote:

> Included is a program listenwrap which should be setuid root.  It opens up
> the hard-coded port number, drops privledges and execs apache with the -p
> option. 

Er, I meant to say "which *can* be setuid root".  It doesn't have to be,
you probably start httpd as root anyhow.

Dean