CONTRIB: tcploop: add action "X" to execute a command

Sometimes it's convenient to be able to execute a command directly on
the stream, whether we're connecting or accepting an incoming connection.
New command 'X' makes this possible. It simply calls execvp() on the
next arguments and branches stdin/stdout/stderr on the socket. Optionally
it's possible to limit the passed FDs to any combination of them by
appending 'i', 'o', 'e' after the X. In any case the program ends just
after executing this command.

Examples :
- chargen server
      tcploop 8001 L A Xo cat /dev/zero

- telnet server
      tcploop 8001 L W N A X /usr/sbin/in.telnetd
diff --git a/contrib/tcploop/tcploop.c b/contrib/tcploop/tcploop.c
index cb9e5f1..f9f5f63 100644
--- a/contrib/tcploop/tcploop.c
+++ b/contrib/tcploop/tcploop.c
@@ -122,6 +122,8 @@
 	    "  O            : wait for Output queue to be empty (POLLOUT + TIOCOUTQ)\n"
 	    "  F            : FIN : shutdown(SHUT_WR)\n"
 	    "  N<max>       : fork New process, limited to <max> concurrent (default 1)\n"
+	    "  X[i|o|e]* ** : execvp() next args passing socket as stdin/stdout/stderr.\n"
+	    "                 If i/o/e present, only stdin/out/err are mapped to socket.\n"
 	    "\n"
 	    "It's important to note that a single FD is used at once and that Accept\n"
 	    "replaces the listening FD with the accepted one. Thus always do it after\n"
@@ -138,6 +140,12 @@
 	    "\n"
 	    "Example TCP client with pauses at each step :\n"
 	    "   tcploop 8001 C T W P100 S10 O P100 R S10 O R G K\n"
+	    "\n"
+	    "Simple chargen server :\n"
+	    "   tcploop 8001 L A Xo cat /dev/zero\n"
+	    "\n"
+	    "Simple telnet server :\n"
+	    "   tcploop 8001 L W N A X /usr/sbin/in.telnetd\n"
 	    "", arg0);
 }
 
@@ -727,6 +735,7 @@
 	int arg;
 	int ret;
 	int sock;
+	int errfd;
 
 	arg0 = argv[0];
 
@@ -899,6 +908,28 @@
 			arg = loop_arg - 1;
 			continue;
 
+		case 'X': // execute command. Optionally supports redirecting only i/o/e
+			if (arg + 1 >= argc)
+				die(1, "Fatal: missing argument after %s\n", argv[arg]);
+
+			errfd = dup(2);
+			fcntl(errfd, F_SETFD, fcntl(errfd, F_GETFD, FD_CLOEXEC) | FD_CLOEXEC);
+			fcntl(sock,  F_SETFL, fcntl(sock,  F_GETFL, O_NONBLOCK) & ~O_NONBLOCK);
+			if (!argv[arg][1] || strchr(argv[arg], 'i'))
+				dup2(sock, 0);
+			if (!argv[arg][1] || strchr(argv[arg], 'o'))
+				dup2(sock, 1);
+			if (!argv[arg][1] || strchr(argv[arg], 'e'))
+				dup2(sock, 2);
+			argv += arg + 1;
+			if (execvp(argv[0], argv) == -1) {
+				int e = errno;
+
+				dup2(errfd, 2); // restore original stderr
+				close(errfd);
+				die(1, "Fatal: execvp(%s) failed : %s\n", argv[0], strerror(e));
+			}
+			break;
 		default:
 			usage(1, arg0);
 		}