I propose a correction to this error. The problem lies in the
fact that upstream's source code makes a compile time decision
on address family, and also makes an implicit assumption on
dual stacked sockets which is not universally true.

The attached difference file is my suggestion which I also
submit in the ITA-closing upload to mentors.debian.net.

Observe that the three combinations

    GNU/Linux, net.ipv6.bindv6only = 1

    GNU/kFreeBSD, net.inet6.ip6.v6only = 0

    GNU/Linux, net.ipv6.bindv6only = 0

all put different demands on socket option handling.
In fact the third of these forced me to make a second
upload to mentors.debian.net.

In crafting this patch, it has been written with my experience
from OpenBSD in mind, but is has not yet been checked there.
The technique is intended to catch Linux and BSD in all
their variations.

Best regards,
  Mats Erik Andersson, DM
Description: Rewrite socket management.
 The original source uses a compile time
 determined method of using IPv6. This can
 cause failures in prepackaged software on
 single stacked machines. The chosen solution
 is to open one listening socket for each
 address family, if needed. To prevent this
 new behaviour, two new options '-4' and '-6'
 are implemented for single socket listening.
 .
 Care is needed to arrive at single family
 sockets independently of 'net.ipv6.bindv6only'.
 .
 A further new option '-e' allows the strong
 elimination of a live file descriptor for
 STDERR.
Author: Mats Erik Andersson <deb...@gisladisker.se>
Forwarded: no
Bug-Debian: http://bugs.debian.org/354778
 http://bugs.debian.org/603110
Last-Update: 2011-03-25

--- micro-inetd-20050629.debian/micro_inetd.1	2011-03-24 11:59:06.000000000 +0100
+++ micro-inetd-20050629/micro_inetd.1	2011-03-24 18:35:05.000000000 +0100
@@ -2,7 +2,7 @@
 .SH NAME
 micro-inetd - simple network service spawner
 .SH SYNOPSIS
-micro-inetd port program [args ...]
+micro-inetd [\-4|\-6|\-e] port program [args ...]
 .SH DESCRIPTION
 Like inetd, this program listens on the net for requests and spawns a
 server to handle them.
@@ -25,6 +25,16 @@ micro-inetd only implements nowait.
 *
 Full inetd lets you specify a user-id to run the server as;
 micro-inetd doesn't try to switch user-ids.
+.SH OPTIONS
+.TP 5
+.B \-4, \-6
+Use a single address family for listening socket. The default, on a
+dual stacked machine, is to open one socket for each address family
+if at all possible.
+.TP 5
+.B \-e
+Do not pass \fISTDERR\fR to the client application. Instead the client
+will see a file descriptor which is terminated in \fI/dev/null\fR.
 .SH "SEE ALSO"
 inetd(8), inetd.conf(5)
 .SH AUTHOR
--- micro-inetd-20050629.debian/micro_inetd.c	2005-06-29 19:27:09.000000000 +0200
+++ micro-inetd-20050629/micro_inetd.c	2011-03-25 10:04:00.000000000 +0100
@@ -38,6 +38,8 @@
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <netdb.h>
+#include <paths.h>
 
 #if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
 #define USE_IPV6
@@ -45,11 +47,12 @@
 
 
 static void usage();
-static int initialize_listen_socket( int pf, int af, unsigned short port );
+static int initialize_listen_socket( int pf, int af, unsigned short port, int fd[] );
 static void child_handler( int sig );
 
 
 static char* argv0;
+static int dostderr = 1;
 
 
 int
@@ -57,37 +60,95 @@ main( int argc, char* argv[] )
     {
     unsigned short port;
     char** child_argv;
-    int listen_fd, conn_fd;
-    struct sockaddr_in sin;
-    int sz;
+    int listen_fd[2], conn_fd;
+    int pf, af;
+    struct sockaddr_storage sin;
+    socklen_t sz;
     fd_set lfdset;
-    int maxfd;
+    int maxfd, numfd, j, pos = 1;
 
+#ifdef USE_IPV6
+    pf = PF_UNSPEC;
+    af = AF_UNSPEC;
+#else /* !USE_IPV6 */
+    pf = PF_INET;
+    af = AF_INET;
+#endif
     argv0 = argv[0];
 
     /* Get arguments. */
     if ( argc < 3 )
 	usage();
-    port = (unsigned short) atoi( argv[1] );
-    child_argv = argv + 2;
+
+    /* Simple parser of command line options. */
+    while ( pos < argc && argv[pos][0] == '-' )
+	{
+	switch ( argv[pos][1] )
+	    {
+	    case '4':
+		pf = PF_INET, af = AF_INET;
+		break;
+	    case '6':
+		pf = PF_INET6, af = AF_INET6;
+		break;
+	    case 'e':
+		dostderr = 0;
+		break;
+	    default:
+		usage();
+		break;
+	    }
+	/* Parsed option, proceed. */
+	++pos;
+	}
+
+    /* Check arguments. */
+    if ( argc < pos + 2 )
+	usage();
+
+    port = (unsigned short) atoi( argv[pos] );
+    child_argv = argv + pos + 1;
 
     /* Initialize listen socket.  If we have v6 use that, since its sockets
     ** will accept v4 connections too.  Otherwise just use v4.
     */
-#ifdef USE_IPV6
-    listen_fd = initialize_listen_socket( PF_INET6, AF_INET6, port );
-#else /* USE_IPV6 */
-    listen_fd = initialize_listen_socket( PF_INET, AF_INET, port );
-#endif /* USE_IPV6 */
+    numfd = initialize_listen_socket( pf, af, port, listen_fd );
+    if ( numfd <= 0 )
+	{
+	fprintf( stderr, "No listening sockets. Aborting.\n");
+	exit( 1 );
+	}
+
+    maxfd = listen_fd[0];
+    if ( (numfd > 1) && (maxfd < listen_fd[1]) )
+	maxfd = listen_fd[1];
 
     /* Set up a signal handler for child reaping. */
     (void) signal( SIGCHLD, child_handler );
 
     for (;;)
 	{
+	int n;
+
+	FD_ZERO(&lfdset);
+	FD_SET(listen_fd[0], &lfdset);
+	if (numfd == 2)
+	    FD_SET(listen_fd[1], &lfdset);
+	n = select( maxfd + 1, &lfdset, NULL, NULL, NULL );
+	if (n <= 0)
+	    continue;
+
+	for ( j = 0; j < numfd; ++j )
+	{
+	/* Indentation level is broken from
+	 * this point and onwards.
+	 */
+	if ( !FD_ISSET(listen_fd[j], &lfdset) )
+	    continue;
+
 	/* Accept a new connection. */
 	sz = sizeof(sin);
-	conn_fd = accept( listen_fd, (struct sockaddr*) &sin, &sz );
+	conn_fd = accept( listen_fd[j], (struct sockaddr*) &sin, &sz );
 	if ( conn_fd < 0 )
 	    {
 	    if ( errno == EINTR )	/* because of SIGCHLD (or ptrace) */
@@ -103,11 +164,18 @@ main( int argc, char* argv[] )
 	    (void) close( 0 );
 	    (void) close( 1 );
 	    (void) close( 2 );
-	    (void) close( listen_fd );
+	    (void) close( listen_fd[j] );
 	    /* Dup the connection onto the standard descriptors. */
 	    (void) dup2( conn_fd, 0 );
 	    (void) dup2( conn_fd, 1 );
-	    (void) dup2( conn_fd, 2 );
+	    if ( dostderr )
+		(void) dup2( conn_fd, STDERR_FILENO );
+	    else
+		{
+		int nullfd = open(_PATH_DEVNULL, O_WRONLY);
+		(void) dup2( nullfd, STDERR_FILENO );
+		(void) close( nullfd );
+		}
 	    (void) close( conn_fd );
 	    /* Run the program. */
 	    (void) execv( child_argv[0], child_argv );
@@ -117,7 +185,8 @@ main( int argc, char* argv[] )
 	    }
 	/* Parent process. */
 	(void) close( conn_fd );
-	}
+	} /* for each possible listening socket */
+	} /* Eternal server loop, waiting for clients. */
 
     }
 
@@ -125,7 +194,8 @@ main( int argc, char* argv[] )
 static
 void usage()
     {
-    (void) fprintf( stderr, "usage:  %s port program [args...]\n", argv0 );
+    (void) fprintf( stderr, "usage:  %s [-4|-6|-e] "
+			    "port program [args...]\n", argv0 );
     exit( 1 );
     }
 
@@ -163,58 +233,92 @@ child_handler( int sig )
 
 
 static int
-initialize_listen_socket( int pf, int af, unsigned short port )
+initialize_listen_socket( int pf, int af, unsigned short port, int fd[] )
     {
     int listen_fd;
-    int on;
-#ifdef USE_IPV6
-    struct sockaddr_in6 sa;
-#else /* USE_IPV6 */
-    struct sockaddr_in sa;
-#endif /* USE_IPV6 */
+    int on, err, numfd = 0;
+    struct addrinfo hints, *res = NULL, *ai = NULL;
+    char portstr[10];
+
+    portstr[0] = '\0';
+    snprintf( portstr, sizeof(portstr) - 1, "%u", port); 
+    portstr[sizeof(portstr) - 1] = '\0';
+
+    memset( &hints, 0, sizeof(hints));
+    hints.ai_family = pf;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+    hints.ai_flags |= AI_ADDRCONFIG;
+#endif
 
-    /* Create socket. */
-    listen_fd = socket( pf, SOCK_STREAM, 0 );
-    if ( listen_fd < 0 )
+    err = getaddrinfo( NULL, portstr, &hints, &res);
+    if ( err < 0 )
         {
-	perror( "socket" );
+	fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
         exit( 1 );
         }
 
+    for ( ai = res; ai; ai = ai->ai_next )
+    {
+	/* Indentation broken from this point onwards. */
+
+    /* Create socket. */
+    listen_fd = socket( ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+    if ( listen_fd < 0)
+	continue;
+
     /* Allow reuse of local addresses. */
     on = 1;
     if ( setsockopt(
              listen_fd, SOL_SOCKET, SO_REUSEADDR, (char*) &on, sizeof(on) ) < 0 )
 	{
-	perror( "setsockopt SO_REUSEADDR" );
-	exit( 1 );
+	close( listen_fd );
+	listen_fd = -1;
+	continue;
 	}
 
-    /* Set up the sockaddr. */
-    (void) memset( (char*) &sa, 0, sizeof(sa) );
-#ifdef USE_IPV6
-    sa.sin6_family = af;
-    sa.sin6_addr = in6addr_any;
-    sa.sin6_port = htons( port );
-#else /* USE_IPV6 */
-    sa.sin_family = af;
-    sa.sin_addr.s_addr = htonl( INADDR_ANY );
-    sa.sin_port = htons( port );
-#endif /* USE_IPV6 */
+    /* Eliminate a dual socket. 'net.ipv6.bindv6only' gives problems. */
+    if ( ai->ai_family == AF_INET6 )
+	{
+	on = 1;
+	/* Ignore any failure. */
+	(void) setsockopt( listen_fd, IPPROTO_IPV6, IPV6_V6ONLY,
+			   &on, sizeof(on) );
+	}
 
     /* Bind it to the socket. */
-    if ( bind( listen_fd, (struct sockaddr*) &sa, sizeof(sa) ) < 0 )
+    if ( bind( listen_fd, ai->ai_addr, ai->ai_addrlen ) < 0 )
         {
-	perror( "bind" );
-        exit( 1 );
+	close( listen_fd );
+	listen_fd = -1;
+	continue;
         }
 
     /* Start a listen going. */
     if ( listen( listen_fd, 1024 ) < 0 )
         {
-	perror( "listen" );
-        exit( 1 );
+	close( listen_fd );
+	listen_fd = -1;
+	continue;
         }
 
-    return listen_fd;
+    /* We have a listening socket. Accept it. */
+    fd[numfd++] = listen_fd;
+
+    /* At most two sockets are expected. */
+    if ( numfd == 2 )
+	break;
+    } /* for (ai = ai->ai_next) */
+
+    if ( res )
+	freeaddrinfo( res );
+
+    if ( numfd == 0 )
+	{
+	fprintf( stderr, "failed to get listening socket\n");
+	exit( 1 );
+	}
+
+    return numfd;
     }

Attachment: signature.asc
Description: Digital signature

Reply via email to