diff --git a/ChangeLog b/ChangeLog
index f3b26f5a2cbaf81a61080249bb4581aa89c509c4..f4a86894985328c8ca73a03d1455a434b870ad19 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2012-07-08  Niels Möller  <nisse@lysator.liu.se>
+
+	* src/testsuite/Makefile.in (TARGETS): Added tcpconnnect.
+
+	* src/testsuite/tcpconnect.c: New file. Reimplemented the program
+	with the same name in tcputils.
+
 2012-06-19  Niels Möller  <nisse@lysator.liu.se>
 
 	* src/transport.c (transport_process_packet): Disconnect on
diff --git a/src/testsuite/Makefile.in b/src/testsuite/Makefile.in
index 85408b532bbd610f72bc9ffe636972ca8ca2ef0d..a31a746a71edfb323d400cf4c5bc2e077634ccd6 100644
--- a/src/testsuite/Makefile.in
+++ b/src/testsuite/Makefile.in
@@ -46,7 +46,8 @@ TS_SH = conv-1-test conv-2-test conv-3-test \
 TS_C = $(TS_SOURCES:.c=$(EXEEXT))
 TS_ALL = $(TS_C) $(TS_SH)
 
-TARGETS = $(TS_C) prgrp-timeout$(EXEEXT) mini-inetd$(EXEEXT) $(PASSWD_PRELOAD)
+TARGETS = $(TS_C) prgrp-timeout$(EXEEXT) $(PASSWD_PRELOAD) \
+	  mini-inetd$(EXEEXT) tcpconnect$(EXEEXT)
 
 TEST_OBJS = testutils.$(OBJEXT) \
 	    ../algorithms.$(OBJEXT) \
diff --git a/src/testsuite/tcpconnect.c b/src/testsuite/tcpconnect.c
new file mode 100644
index 0000000000000000000000000000000000000000..2c156c88330ab3cd7a4fe001d6fae3501140227b
--- /dev/null
+++ b/src/testsuite/tcpconnect.c
@@ -0,0 +1,368 @@
+/* tcpconnect
+ *
+ * This program is an total reimplementation of the old tcpconnect
+ * from Thomas Bellman's tcputils.
+ *
+ * Copyright (C) 2012 Möller
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netdb.h>
+
+#include <arpa/inet.h>
+
+#include "getopt.h"
+
+static void NORETURN PRINTF_STYLE(1, 2)
+die(const char *format, ...)
+{
+  va_list args;
+  fprintf(stderr, "tcpconnect: ");
+  va_start(args, format);
+  vfprintf(stderr, format, args);
+  va_end(args);
+
+  exit(EXIT_FAILURE);
+}
+
+static void PRINTF_STYLE(1, 2)
+werror(const char *format, ...)
+{
+  va_list args;
+  fprintf(stderr, "tcpconnect: ");
+  va_start(args, format);
+  vfprintf(stderr, format, args);
+  va_end(args);
+}
+
+static void
+usage (FILE *f)
+{
+  fprintf(f,
+	  "tcpconnect [OPTIONS] host post\n"
+	  "Options:\n"
+	  "  -i     Terminate at end-of-file on standard input;\n"
+	  "         don't wait for the server to close the connection.\n"
+	  "  -r     Terminate when the remote server closes the connection;\n"
+	  "         don't wait for end-of-file on standard input.\n"
+	  "  -v     Verbose mode. Prints a message to standard error when\n"
+	  "         the connection has been established.\n"
+	  /* FIXME: ':' is a bad separator for literal IPv6 addresses.
+	     Support [] around the address? */
+	  "  -l ADDR:PORT\n"
+	  "         Bind the local end-point of the connection to IP address\n"
+	  "         ADDR, TCP port PORT. Either the IP address or the port,\n"
+	  "         but not both, may be left out, meaning that the operating\n"
+	  "         system gets to choose that part by itself.\n"
+	  "  -4     Only use IPv4.\n"
+	  "  -6     Only use IPv6.\n"
+	  "  --help Display this help.\n");
+}
+
+#define BUF_SIZE 4096
+
+int
+main (int argc, char **argv)
+{
+  int family = AF_UNSPEC;
+  int wait_stdin_eof = 1;
+  int wait_remote_eof = 1;
+  int verbose = 0;
+#if 0
+  const char *local_ip = NULL;
+  const char *local_port = NULL;
+#endif
+  int c;
+
+  struct addrinfo hints;
+  struct addrinfo *list, *p;
+  int flags;
+  int fd;
+
+  int seen_remote_eof;
+  int seen_stdin_eof;
+
+  char buf_stdin[BUF_SIZE];
+  unsigned buf_size;
+  unsigned buf_pos;
+  int err;
+
+  enum { OPT_HELP = 300 };
+  static const struct option options[] =
+    {
+      /* Name, args, flag, val */
+
+      { "help", no_argument, NULL, OPT_HELP },
+      { NULL, 0, NULL, 0 }
+    };  
+
+  while ( (c = getopt_long (argc, argv, "46irvl:", options, NULL)) != -1)
+    switch (c)
+      {
+      case '?':
+	return EXIT_FAILURE;
+      case OPT_HELP:
+ 	usage(stdout);
+	return EXIT_SUCCESS;
+      case '4':
+	family = AF_INET;
+	break;
+      case '6':
+#ifdef AF_INET6
+	family = AF_INET6;
+	break;
+#else
+	die ("IP version 6 not supported.\n");
+#endif
+      case 'i':
+	wait_remote_eof = 0;
+	break;	
+      case 'r':
+	wait_stdin_eof = 0;
+	break;
+      case 'v':
+	verbose = 1;
+	break;
+      case 'l':
+	die ("Not implemented.\n");
+	break;
+      }
+
+  argc -= optind;
+  argv += optind;
+  
+  if (argc < 2)
+    {
+      usage(stderr);
+      return EXIT_FAILURE;
+    }
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = family;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = IPPROTO_TCP;
+
+  err = getaddrinfo (argv[0], argv[1], &hints, &list);
+  if (err)
+    die("Failed to look up address: %s\n", gai_strerror(err));
+
+  for (p = list, fd = -1; p; p = p->ai_next)
+    {
+      fd = socket (p->ai_family, p->ai_socktype, p->ai_protocol);
+      if (fd < 0)
+	continue;
+      if (connect (fd, p->ai_addr, p->ai_addrlen) >= 0)
+	break;
+
+      close (fd);
+      fd = -1;
+    }
+
+  if (fd < 0)
+    die("Connection failed.\n");
+  
+  if (verbose)
+    switch (p->ai_family)
+      {
+#ifdef AF_INET6
+      case AF_INET6:
+	{
+	  const struct sockaddr_in6 *sin6
+	    = (struct sockaddr_in6 *) p->ai_addr;
+	    
+	  char buf[INET6_ADDRSTRLEN];
+	  if (!inet_ntop (p->ai_family, &sin6->sin6_addr, buf, sizeof(buf)))
+	    die ("inet_ntop failed: %s\n", strerror(errno));
+	  fprintf(stderr, "[Connected to %s port %d]\n",
+		  buf, ntohs (sin6->sin6_port));
+	  break;
+	}
+#endif
+      case AF_INET:
+	{
+	  const struct sockaddr_in *sin
+	    = (struct sockaddr_in *) p->ai_addr;
+	    
+	  char buf[INET_ADDRSTRLEN];
+	  if (!inet_ntop (p->ai_family, &sin->sin_addr, buf, sizeof(buf)))
+	    die ("inet_ntop failed: %s\n", strerror(errno));
+	  fprintf(stderr, "[Connected to %s port %d]\n",
+		  buf, ntohs (sin->sin_port));
+	  break;
+	}
+      default:
+	die ("Unknown address family %d.\n", p->ai_family);
+      }
+  freeaddrinfo (list);
+
+  flags = fcntl(fd, F_GETFL);
+  if (flags < 0)
+    werror("fcntl F_GETFL failed: %s\n", strerror(errno));
+  else if (fcntl(fd, F_SETFL, flags | 1 | O_NONBLOCK) < 0)
+    werror("fcntl F_SETFL failed: %s\n", strerror(errno));
+
+  buf_size = buf_pos = 0;  
+  seen_stdin_eof = seen_remote_eof = 0;
+
+  /* We do blocking reads of stdin and writes to stdout, and use
+     FIONREAD to avoid blocking on read. We do non-blocking reads and
+     writes on the sockets. If we can't write, we keep buffered data
+     in buf_stdin, and don't read any more stdin data until the
+     buffered data has been written to the socket. */
+  for (;;)
+    {
+      fd_set read_fds;
+      fd_set write_fds;
+
+      FD_ZERO (&read_fds);
+      FD_ZERO (&write_fds);
+
+      if (buf_size > buf_pos)
+	FD_SET (fd, &write_fds);
+      else if (!seen_stdin_eof)
+	FD_SET (STDIN_FILENO, &read_fds);
+
+      if (!seen_remote_eof)
+	FD_SET (fd, &read_fds);
+
+      if (select (fd + 1, &read_fds, &write_fds, NULL, NULL) < 0)
+	{
+	  if (errno == EINTR)
+	    continue;
+	  die ("select failed: %s\n", strerror(errno));
+	}
+      if (FD_ISSET (fd, &write_fds))
+	{
+	  int res = write(fd, buf_stdin + buf_pos, buf_size - buf_pos);
+	  if (res < 0)
+	    {
+	      if (errno != EINTR)
+		{
+		  if (verbose)
+		    werror ("write to socket failed: %s\n", strerror(errno));
+		  break;
+		}
+	    }
+	  else
+	    buf_pos += res;
+	}
+      if (FD_ISSET (STDIN_FILENO, &read_fds))
+	{	  
+	  int nbytes, res;
+
+	  if (ioctl (STDIN_FILENO, FIONREAD, &nbytes) < 0
+	      || nbytes <= 0
+	      || nbytes > (int) sizeof(buf_stdin))
+	    nbytes = sizeof(buf_stdin);
+
+	  res = read(STDIN_FILENO, buf_stdin, nbytes);
+	  if (res < 0)
+	    {
+	      if (errno != EINTR)
+		die("read from stdin failed: %s\n", strerror(errno));
+	    }
+	  else if (res == 0)
+	    {
+	      wait_stdin_eof = 0;
+	      if (!wait_remote_eof)
+		break;
+	      if (shutdown (fd, SHUT_WR) < 0 && errno != ENOTCONN)
+		die("shutdown failed: %s\n", strerror(errno));
+	    }
+	  else
+	    {	      
+	      nbytes = res;	      
+	      res = write(fd, buf_stdin, nbytes);
+	      if (res < 0)
+		{
+		  if (errno != EINTR)
+		    {
+		      if (verbose)
+			werror ("write to socket failed: %s\n", strerror(errno));
+		      break;
+		    }
+		  res = 0;
+		}
+	      if (res < nbytes)
+		{
+		  buf_size = nbytes;
+		  buf_pos = res;
+		}
+	    } 
+	}
+      if (FD_ISSET (fd, &read_fds))
+	{
+	  char buf_remote[BUF_SIZE];
+	  int res = read (fd, buf_remote, sizeof (buf_remote));
+
+	  if (res <= 0)
+	    {
+	      /* Treat read errors in the same way as EOF. */
+	      if (res < 0 && (errno == EINTR || errno == EWOULDBLOCK))
+		; /* Do nothing */
+	      else
+		{
+		  if (verbose && res < 0)
+		    werror("read from socket failed: %s\n", strerror(errno));
+
+		  wait_remote_eof = 0;
+		  if (!wait_stdin_eof)
+		    break;
+
+		  /* Useful only in case stdout happens to be a socket. */
+		  shutdown (STDOUT_FILENO, SHUT_WR);
+		}
+	    }
+	  else
+	    {
+	      int size = res;
+	      int done = 0;
+	      while (done < size)
+		{
+		  res = write(STDOUT_FILENO, buf_remote + done, size - done);
+		  if (res < 0)
+		    {
+		      if (errno != EINTR)
+			die ("write to stdout failed: %s\n", strerror(errno));
+		    }
+		  else
+		    done += res;
+		}
+	    }
+	}
+    }
+  close (fd);
+
+  if (verbose)
+    fprintf(stderr, "[Connection closed]");
+
+  return EXIT_SUCCESS;
+}