From 79799b389703e8775f90e9728fa7e9d0dca26739 Mon Sep 17 00:00:00 2001
From: Gerrit Pape <pape@smarden.org>
Date: Tue, 13 Oct 2009 22:38:01 +0000
Subject: [PATCH] Apply fefe's ucspi-tcp-0.88-ipv6.diff19 ipv6 patch

What is ucspi-tcp and why does it need IPv6?

Please see http://cr.yp.to/ucspi-tcp.html for an explanation what
ucspi-tcp is. Basically, many people use it to replace inetd. It
consists of tcpserver and tcpclient. tcpserver can listen on sockets and
start programs in an inetd like fashion except that information like the
IP address and remote port are communicated via environment variables,
so writing a server is very easy and the servers are also more portable
since they don't have to use the socket API directly.

What does your diff do?

The current version adds simple IPv6 support for tcpserver and
tcpclient.

Note that IPv6 has inherent IPv4 compatibility, so IPv4 connections are
still possible without having to start a second tcpserver or something
like that. But since some systems may have IPv6 support in their libc
but not in the kernel, my tcpserver will always try to create an IPv6
listening socket but will fall back to transparent IPv4 support. Note
that this is done by the socket wrappers, not explicitly by tcpserver,
so to tcpserver the connections look like a normal IPv4 mapped IPv6
connection.

If a client connects via IPv4 (and you don't use the new -6 option),
tcpserver will make the environment variables look just like the
unpatched version. tcpclient also has a -6 option now with the same
effect.

IPv6 connections use $PROTO=TCP6, so clients can use this to see how
they should try to parse the IP numbers given in the environment, should
they feel the need to do so.

There is a new -4 option to tcpclient. I updated the resolver so that
you can request an IPv6 address for a machine that has only an A record.
If the lookup for an AAAA record fails, dns_ip6 will do an A lookup and
convert the address(es) to IP4-mapped IPv6 addresses. If you want
tcpclient to connect to the IPv4 address even if there is an AAAA
record, you can use the -4 option to tcpclient.

What does not work yet?

There are quite a few servers using tcpserver out there. If they try to
parse the IP addresses, they are not IPv6 ready and need to be touched.

Amend:
	rm -rf usr/
---
 FILES                   |   37 +++++++
 Makefile                |  184 ++++++++++++++++++++++++++++-----
 TARGETS                 |   28 +++++
 addcr.1                 |   22 ++++
 argv0.1                 |   47 ++++++++
 date@.1                 |   32 ++++++
 delcr.1                 |   30 ++++++
 dns.h                   |   63 +++++++-----
 dns_dfd.c               |   11 +-
 dns_domain.c            |   36 ++++--
 dns_dtda.c              |    2 +-
 dns_ip.c                |    4 +-
 dns_ip6.c               |  103 ++++++++++++++++++
 dns_ipq.c               |    6 +-
 dns_ipq6.c              |   72 +++++++++++++
 dns_name.c              |   19 +++-
 dns_nd.c                |    2 +-
 dns_nd6.c               |   28 +++++
 dns_packet.c            |    9 +-
 dns_random.c            |    3 +-
 dns_rcip.c              |   29 +++---
 dns_rcrw.c              |    5 +-
 dns_resolve.c           |    7 +-
 dns_sortip6.c           |   20 ++++
 dns_transmit.c          |   71 +++++++------
 dns_txt.c               |    4 +-
 error.h                 |    2 +-
 finger@.1               |   45 ++++++++
 fixcr.1                 |   11 ++
 fmt_xlong.c             |   22 ++++
 haveip6.h1              |    1 +
 haveip6.h2              |    1 +
 hier.c                  |   19 ++++
 http@.1                 |   52 +++++++++
 ip4.h                   |    2 +
 ip6.h                   |   28 +++++
 ip6_fmt.c               |   64 +++++++++++
 mconnect.1              |   36 +++++++
 old-rules.c             |  101 ++++++++++++++++++
 pathexec.h              |    2 +-
 pathexec_env.c          |    3 +-
 recordio.1              |   75 +++++++++++++
 remoteinfo.h            |    1 +
 remoteinfo6.c           |   98 +++++++++++++++++
 rules.c                 |    2 +-
 scan_ip6.c              |   87 +++++++++++++++
 scan_xlong.c            |   23 ++++
 socket.h                |   39 ++++++-
 socket_accept6.c        |   44 ++++++++
 socket_bind.c           |    4 +-
 socket_bind6.c          |   45 ++++++++
 socket_conn.c           |    2 +-
 socket_conn6.c          |   38 +++++++
 socket_getifidx.c       |    8 ++
 socket_getifname.c      |   14 +++
 socket_ip4loopback.c    |    2 +
 socket_local6.c         |   39 +++++++
 socket_recv6.c          |   44 ++++++++
 socket_remote6.c        |   39 +++++++
 socket_send6.c          |   40 +++++++
 socket_tcp6.c           |   44 ++++++++
 socket_udp6.c           |   38 +++++++
 socket_v4mappedprefix.c |    2 +
 socket_v6any.c          |    2 +
 socket_v6loopback.c     |    2 +
 str.h                   |   14 ++--
 str_chr.c               |    4 +-
 str_diff.c              |    2 +-
 str_len.c               |    4 +-
 str_start.c             |    2 +-
 stralloc.h              |   12 ++-
 stralloc_catb.c         |    2 +-
 stralloc_cats.c         |    2 +-
 stralloc_opyb.c         |    2 +-
 stralloc_opys.c         |    2 +-
 tcp-environ.5           |   66 ++++++++++++
 tcpcat.1                |   20 ++++
 tcpclient.1             |  173 ++++++++++++++++++++++++++++++
 tcpclient.c             |   73 ++++++++-----
 tcprules.1              |  221 +++++++++++++++++++++++++++++++++++++++
 tcprules.c              |   11 ++-
 tcprulescheck.1         |   25 +++++
 tcpserver.1             |  266 +++++++++++++++++++++++++++++++++++++++++++++++
 tcpserver.c             |  112 ++++++++++++++------
 timeoutconn.h           |    2 +
 timeoutconn6.c          |   34 ++++++
 tryip6.c                |    8 ++
 who@.1                  |   32 ++++++
 88 files changed, 2849 insertions(+), 235 deletions(-)
 create mode 100644 addcr.1
 create mode 100644 argv0.1
 create mode 100644 date@.1
 create mode 100644 delcr.1
 create mode 100644 dns_ip6.c
 create mode 100644 dns_ipq6.c
 create mode 100644 dns_nd6.c
 create mode 100644 dns_sortip6.c
 create mode 100644 finger@.1
 create mode 100644 fixcr.1
 create mode 100644 fmt_xlong.c
 create mode 100644 haveip6.h1
 create mode 100644 haveip6.h2
 create mode 100644 http@.1
 create mode 100644 ip6.h
 create mode 100644 ip6_fmt.c
 create mode 100644 mconnect.1
 create mode 100644 old-rules.c
 create mode 100644 recordio.1
 create mode 100644 remoteinfo6.c
 create mode 100644 scan_ip6.c
 create mode 100644 scan_xlong.c
 create mode 100644 socket_accept6.c
 create mode 100644 socket_bind6.c
 create mode 100644 socket_conn6.c
 create mode 100644 socket_getifidx.c
 create mode 100644 socket_getifname.c
 create mode 100644 socket_ip4loopback.c
 create mode 100644 socket_local6.c
 create mode 100644 socket_recv6.c
 create mode 100644 socket_remote6.c
 create mode 100644 socket_send6.c
 create mode 100644 socket_tcp6.c
 create mode 100644 socket_udp6.c
 create mode 100644 socket_v4mappedprefix.c
 create mode 100644 socket_v6any.c
 create mode 100644 socket_v6loopback.c
 create mode 100644 tcp-environ.5
 create mode 100644 tcpcat.1
 create mode 100644 tcpclient.1
 create mode 100644 tcprules.1
 create mode 100644 tcprulescheck.1
 create mode 100644 tcpserver.1
 create mode 100644 timeoutconn6.c
 create mode 100644 tryip6.c
 create mode 100644 who@.1

diff --git a/FILES b/FILES
index cfb38a5..142aed9 100644
--- a/FILES
+++ b/FILES
@@ -216,3 +216,40 @@ wait_pid.c
 warn-auto.sh
 warn-shsgr
 x86cpuid.c
+dns_ip6.c
+dns_ipq6.c
+dns_nd6.c
+dns_sortip6.c
+fmt_xlong.c
+ip6_fmt.c
+ip6_scan.c
+scan_0x.c
+socket_accept6.c
+socket_bind6.c
+socket_conn6.c
+socket_local6.c
+socket_recv6.c
+socket_remote6.c
+socket_send6.c
+socket_tcp6.c
+timeoutconn6.c
+tryip6.c
+haveip6.h2
+haveip6.h1
+remoteinfo6.c
+addcr.1
+argv0.1
+date@.1
+delcr.1
+finger@.1
+fixcr.1
+http@.1
+mconnect.1
+recordio.1
+tcp-environ.5
+tcpcat.1
+tcpclient.1
+tcprules.1
+tcprulescheck.1
+tcpserver.1
+who@.1
diff --git a/Makefile b/Makefile
index bcf5bd5..9dc6844 100644
--- a/Makefile
+++ b/Makefile
@@ -76,12 +76,14 @@ byte.a: \
 makelib byte_chr.o byte_copy.o byte_cr.o byte_diff.o byte_rchr.o \
 byte_zero.o case_diffb.o case_diffs.o fmt_ulong.o ip4_fmt.o \
 ip4_scan.o scan_ulong.o str_chr.o str_diff.o str_len.o str_start.o \
-uint16_pack.o uint16_unpack.o uint32_pack.o uint32_unpack.o
+uint16_pack.o uint16_unpack.o uint32_pack.o uint32_unpack.o \
+ip6_fmt.o scan_ip6.o scan_xlong.o fmt_xlong.o
 	./makelib byte.a byte_chr.o byte_copy.o byte_cr.o \
 	byte_diff.o byte_rchr.o byte_zero.o case_diffb.o \
 	case_diffs.o fmt_ulong.o ip4_fmt.o ip4_scan.o scan_ulong.o \
 	str_chr.o str_diff.o str_len.o str_start.o uint16_pack.o \
-	uint16_unpack.o uint32_pack.o uint32_unpack.o
+	uint16_unpack.o uint32_pack.o uint32_unpack.o ip6_fmt.o \
+	scan_ip6.o scan_xlong.o fmt_xlong.o
 
 byte_chr.o: \
 compile byte_chr.c byte.h
@@ -180,11 +182,13 @@ compile delcr.c buffer.h exit.h
 dns.a: \
 makelib dns_dfd.o dns_domain.o dns_dtda.o dns_ip.o dns_ipq.o \
 dns_name.o dns_nd.o dns_packet.o dns_random.o dns_rcip.o dns_rcrw.o \
-dns_resolve.o dns_sortip.o dns_transmit.o dns_txt.o
+dns_resolve.o dns_sortip.o dns_transmit.o dns_txt.o dns_ip6.o \
+dns_sortip6.o dns_nd6.o dns_ipq6.o
 	./makelib dns.a dns_dfd.o dns_domain.o dns_dtda.o dns_ip.o \
 	dns_ipq.o dns_name.o dns_nd.o dns_packet.o dns_random.o \
 	dns_rcip.o dns_rcrw.o dns_resolve.o dns_sortip.o \
-	dns_transmit.o dns_txt.o
+	dns_transmit.o dns_txt.o dns_ip6.o dns_sortip6.o dns_nd6.o \
+	dns_ipq6.o
 
 dns_dfd.o: \
 compile dns_dfd.c error.h alloc.h byte.h dns.h stralloc.h gen_alloc.h \
@@ -256,7 +260,7 @@ taia.h tai.h uint64.h taia.h
 dns_transmit.o: \
 compile dns_transmit.c socket.h uint16.h alloc.h error.h byte.h \
 readwrite.h uint16.h dns.h stralloc.h gen_alloc.h iopause.h taia.h \
-tai.h uint64.h taia.h
+tai.h uint64.h taia.h uint32.h
 	./compile dns_transmit.c
 
 dns_txt.o: \
@@ -497,9 +501,15 @@ exit.h fmt.h iopause.h taia.h tai.h uint64.h pathexec.h
 remoteinfo.o: \
 compile remoteinfo.c fmt.h buffer.h socket.h uint16.h error.h \
 iopause.h taia.h tai.h uint64.h timeoutconn.h uint16.h remoteinfo.h \
-stralloc.h gen_alloc.h uint16.h
+stralloc.h gen_alloc.h uint16.h uint32.h
 	./compile remoteinfo.c
 
+remoteinfo6.o: \
+compile remoteinfo6.c fmt.h buffer.h socket.h uint16.h error.h \
+iopause.h taia.h tai.h uint64.h timeoutconn.h uint16.h remoteinfo.h \
+stralloc.h gen_alloc.h uint16.h uint32.h
+	./compile remoteinfo6.c
+
 rts: \
 warn-auto.sh rts.sh conf-home
 	cat warn-auto.sh rts.sh \
@@ -556,43 +566,43 @@ trylsock.c compile load
 	rm -f trylsock.o trylsock
 
 socket_accept.o: \
-compile socket_accept.c byte.h socket.h uint16.h
+compile socket_accept.c byte.h socket.h uint16.h uint32.h
 	./compile socket_accept.c
 
 socket_bind.o: \
-compile socket_bind.c byte.h socket.h uint16.h
+compile socket_bind.c byte.h socket.h uint16.h uint32.h
 	./compile socket_bind.c
 
 socket_conn.o: \
-compile socket_conn.c readwrite.h byte.h socket.h uint16.h
+compile socket_conn.c readwrite.h byte.h socket.h uint16.h uint32.h
 	./compile socket_conn.c
 
 socket_delay.o: \
-compile socket_delay.c socket.h uint16.h
+compile socket_delay.c socket.h uint16.h uint32.h
 	./compile socket_delay.c
 
 socket_listen.o: \
-compile socket_listen.c socket.h uint16.h
+compile socket_listen.c socket.h uint16.h uint32.h
 	./compile socket_listen.c
 
 socket_local.o: \
-compile socket_local.c byte.h socket.h uint16.h
+compile socket_local.c byte.h socket.h uint16.h uint32.h
 	./compile socket_local.c
 
 socket_opts.o: \
-compile socket_opts.c socket.h uint16.h
+compile socket_opts.c socket.h uint16.h uint32.h
 	./compile socket_opts.c
 
 socket_remote.o: \
-compile socket_remote.c byte.h socket.h uint16.h
+compile socket_remote.c byte.h socket.h uint16.h uint32.h
 	./compile socket_remote.c
 
 socket_tcp.o: \
-compile socket_tcp.c ndelay.h socket.h uint16.h
+compile socket_tcp.c ndelay.h socket.h uint16.h uint32.h
 	./compile socket_tcp.c
 
 socket_udp.o: \
-compile socket_udp.c ndelay.h socket.h uint16.h
+compile socket_udp.c ndelay.h socket.h uint16.h uint32.h
 	./compile socket_udp.c
 
 str_chr.o: \
@@ -709,9 +719,9 @@ warn-auto.sh tcpcat.sh conf-home
 	chmod 755 tcpcat
 
 tcpclient: \
-load tcpclient.o remoteinfo.o timeoutconn.o dns.a time.a unix.a \
-byte.a socket.lib
-	./load tcpclient remoteinfo.o timeoutconn.o dns.a time.a \
+load tcpclient.o remoteinfo6.o dns.a time.a unix.a \
+byte.a socket.lib byte.h timeoutconn6.o
+	./load tcpclient remoteinfo6.o timeoutconn6.o dns.a time.a \
 	unix.a byte.a  `cat socket.lib`
 
 tcpclient.o: \
@@ -719,7 +729,7 @@ compile tcpclient.c sig.h exit.h sgetopt.h subgetopt.h uint16.h fmt.h \
 scan.h str.h ip4.h uint16.h socket.h uint16.h fd.h stralloc.h \
 gen_alloc.h buffer.h error.h strerr.h pathexec.h timeoutconn.h \
 uint16.h remoteinfo.h stralloc.h uint16.h dns.h stralloc.h iopause.h \
-taia.h tai.h uint64.h taia.h
+taia.h tai.h uint64.h taia.h uint32.h
 	./compile tcpclient.c
 
 tcprules: \
@@ -741,9 +751,9 @@ stralloc.h gen_alloc.h
 	./compile tcprulescheck.c
 
 tcpserver: \
-load tcpserver.o rules.o remoteinfo.o timeoutconn.o cdb.a dns.a \
+load tcpserver.o rules.o remoteinfo6.o timeoutconn6.o cdb.a dns.a \
 time.a unix.a byte.a socket.lib
-	./load tcpserver rules.o remoteinfo.o timeoutconn.o cdb.a \
+	./load tcpserver rules.o remoteinfo6.o timeoutconn6.o cdb.a \
 	dns.a time.a unix.a byte.a  `cat socket.lib`
 
 tcpserver.o: \
@@ -752,7 +762,7 @@ exit.h env.h prot.h open.h wait.h readwrite.h stralloc.h gen_alloc.h \
 alloc.h buffer.h error.h strerr.h sgetopt.h subgetopt.h pathexec.h \
 socket.h uint16.h ndelay.h remoteinfo.h stralloc.h uint16.h rules.h \
 stralloc.h sig.h dns.h stralloc.h iopause.h taia.h tai.h uint64.h \
-taia.h
+taia.h uint32.h
 	./compile tcpserver.c
 
 time.a: \
@@ -764,9 +774,14 @@ taia_less.o taia_now.o taia_pack.o taia_sub.o taia_uint.o
 
 timeoutconn.o: \
 compile timeoutconn.c ndelay.h socket.h uint16.h iopause.h taia.h \
-tai.h uint64.h error.h timeoutconn.h uint16.h
+tai.h uint64.h error.h timeoutconn.h uint16.h uint32.h
 	./compile timeoutconn.c
 
+timeoutconn6.o: \
+compile timeoutconn6.c ndelay.h socket.h uint16.h iopause.h taia.h \
+tai.h uint64.h error.h timeoutconn.h uint16.h uint32.h
+	./compile timeoutconn6.c
+
 uint16_pack.o: \
 compile uint16_pack.c uint16.h
 	./compile uint16_pack.c
@@ -805,7 +820,12 @@ socket_conn.o socket_delay.o socket_listen.o socket_local.o \
 socket_opts.o socket_remote.o socket_tcp.o socket_udp.o \
 stralloc_cat.o stralloc_catb.o stralloc_cats.o stralloc_copy.o \
 stralloc_eady.o stralloc_opyb.o stralloc_opys.o stralloc_pend.o \
-strerr_die.o strerr_sys.o subgetopt.o wait_nohang.o wait_pid.o
+strerr_die.o strerr_sys.o subgetopt.o wait_nohang.o wait_pid.o \
+socket_conn6.o socket_bind6.o socket_accept6.o socket_recv6.o \
+socket_send6.o socket_local6.o socket_remote6.o socket_tcp6.o \
+socket_getifname.o socket_getifidx.o socket_v4mappedprefix.o \
+socket_ip4loopback.o socket_v6any.o socket_v6loopback.o \
+socket_udp6.o
 	./makelib unix.a alloc.o alloc_re.o buffer.o buffer_0.o \
 	buffer_1.o buffer_2.o buffer_copy.o buffer_get.o \
 	buffer_put.o env.o error.o error_str.o fd_copy.o fd_move.o \
@@ -818,7 +838,12 @@ strerr_die.o strerr_sys.o subgetopt.o wait_nohang.o wait_pid.o
 	socket_udp.o stralloc_cat.o stralloc_catb.o stralloc_cats.o \
 	stralloc_copy.o stralloc_eady.o stralloc_opyb.o \
 	stralloc_opys.o stralloc_pend.o strerr_die.o strerr_sys.o \
-	subgetopt.o wait_nohang.o wait_pid.o
+	subgetopt.o wait_nohang.o wait_pid.o socket_conn6.o \
+	socket_bind6.o socket_accept6.o socket_recv6.o socket_send6.o \
+	socket_local6.o socket_remote6.o socket_tcp6.o \
+	socket_getifname.o socket_getifidx.o socket_v4mappedprefix.o \
+	socket_ip4loopback.o socket_v6any.o socket_v6loopback.o \
+	socket_udp6.o
 
 wait_nohang.o: \
 compile wait_nohang.c haswaitp.h
@@ -834,3 +859,110 @@ warn-auto.sh who@.sh conf-home
 	| sed s}HOME}"`head -1 conf-home`"}g \
 	> who@
 	chmod 755 who@
+
+socket_conn6.o: \
+compile socket_conn6.c socket.h uint16.h haveip6.h error.h ip6.h \
+uint32.h
+	./compile socket_conn6.c
+
+socket_bind6.o: \
+compile socket_bind6.c socket.h uint16.h haveip6.h error.h ip6.h \
+uint32.h
+	./compile socket_bind6.c
+
+socket_accept6.o: \
+compile socket_accept6.c socket.h uint16.h haveip6.h error.h ip6.h \
+uint32.h
+	./compile socket_accept6.c
+
+socket_recv6.o: \
+compile socket_recv6.c socket.h uint16.h haveip6.h error.h ip6.h \
+uint32.h
+	./compile socket_recv6.c
+
+socket_send6.o: \
+compile socket_send6.c socket.h uint16.h haveip6.h error.h uint32.h
+	./compile socket_send6.c
+
+socket_local6.o: \
+compile socket_local6.c socket.h uint16.h haveip6.h error.h uint32.h
+	./compile socket_local6.c
+
+socket_remote6.o: \
+compile socket_remote6.c socket.h uint16.h haveip6.h error.h uint32.h
+	./compile socket_remote6.c
+
+dns_sortip6.o: \
+compile dns_sortip6.c byte.h dns.h stralloc.h gen_alloc.h iopause.h \
+taia.h tai.h uint64.h taia.h
+	./compile dns_sortip6.c
+
+dns_nd6.o: \
+compile dns_nd6.c byte.h fmt.h dns.h stralloc.h gen_alloc.h iopause.h \
+taia.h tai.h uint64.h taia.h
+	./compile dns_nd6.c
+
+dns_ipq6.o: \
+compile dns_ipq6.c stralloc.h gen_alloc.h case.h byte.h str.h dns.h \
+stralloc.h iopause.h taia.h tai.h uint64.h taia.h ip6.h
+	./compile dns_ipq6.c
+
+dns_ip6.o: \
+compile dns_ip6.c stralloc.h gen_alloc.h uint16.h byte.h dns.h \
+stralloc.h iopause.h taia.h tai.h uint64.h taia.h
+	./compile dns_ip6.c
+
+fmt_xlong.o: \
+compile fmt_xlong.c scan.h
+	./compile fmt_xlong.c
+
+scan_xlong.o: \
+compile scan_xlong.c scan.h
+	./compile scan_xlong.c
+
+ip6_fmt.o: \
+compile ip6_fmt.c fmt.h ip6.h
+	./compile ip6_fmt.c
+
+scan_ip6.o: \
+compile scan_ip6.c scan.h ip6.h
+	./compile scan_ip6.c
+
+socket_tcp6.o: \
+compile socket_tcp6.c ndelay.h socket.h uint16.h haveip6.h uint32.h
+	./compile socket_tcp6.c
+
+socket_udp6.o: \
+compile socket_udp6.c ndelay.h socket.h uint16.h haveip6.h uint32.h
+	./compile socket_udp6.c
+
+haveip6.h: \
+tryip6.c choose compile haveip6.h1 haveip6.h2
+	./choose c tryip6 haveip6.h1 haveip6.h2 > haveip6.h
+
+socket_getifname.o: \
+compile socket_getifname.c socket.h uint16.h uint32.h
+	./compile socket_getifname.c
+
+socket_getifidx.o: \
+compile socket_getifidx.c socket.h uint16.h uint32.h
+	./compile socket_getifidx.c
+
+socket_ip4loopback.o: \
+compile socket_ip4loopback.c
+	./compile socket_ip4loopback.c
+
+socket_v4mappedprefix.o: \
+compile socket_v4mappedprefix.c
+	./compile socket_v4mappedprefix.c
+
+socket_v6any.o: \
+compile socket_v6any.c
+	./compile socket_v6any.c
+
+socket_v6loopback.o: \
+compile socket_v6loopback.c
+	./compile socket_v6loopback.c
+
+clean:
+	rm -f `cat TARGETS`
diff --git a/TARGETS b/TARGETS
index 4d1f2a0..0385f96 100644
--- a/TARGETS
+++ b/TARGETS
@@ -169,3 +169,31 @@ instcheck
 it
 setup
 check
+dns_ip6.o
+dns_ipq6.o
+dns_nd6.o
+dns_sortip6.o
+fmt_xlong.o
+ip6_fmt.o
+ip6_scan.o
+scan_0x.o
+socket_accept6.o
+socket_bind6.o
+socket_conn6.o
+socket_local6.o
+socket_recv6.o
+socket_remote6.o
+socket_send6.o
+socket_tcp6.o
+timeoutconn6.o
+haveip6.h
+remoteinfo6.o
+socket_getifidx.o
+socket_getifname.o
+scan_ip6.o
+scan_xlong.o
+socket_ip4loopback.o
+socket_udp6.o
+socket_v4mappedprefix.o
+socket_v6any.o
+socket_v6loopback.o
diff --git a/addcr.1 b/addcr.1
new file mode 100644
index 0000000..3bae1f7
--- /dev/null
+++ b/addcr.1
@@ -0,0 +1,22 @@
+.TH addcr 1
+.SH NAME
+addcr \- add a CR before each LF
+.SH SYNOPSIS
+.B addcr
+.SH DESCRIPTION
+.B addcr
+inserts CR at the end of each line of input.
+It does not insert CR at the end of a partial final line.
+.SH COMPATIBILITY
+Some vendors ship
+.B unix2dos
+or
+.B bsd2dos
+tools similar to
+.BR addcr .
+Those tools often blow up on long lines and nulls.
+.B addcr
+has no trouble with long lines and nulls.
+.SH "SEE ALSO"
+delcr(1),
+fixcr(1)
diff --git a/argv0.1 b/argv0.1
new file mode 100644
index 0000000..ad9634d
--- /dev/null
+++ b/argv0.1
@@ -0,0 +1,47 @@
+.TH argv0 1
+.SH NAME
+argv0 \- run a program with a specified 0th argument
+.SH SYNOPSIS
+.B argv0
+.I realname
+.I zero
+[
+.I arg ...
+]
+.SH DESCRIPTION
+.B argv0
+runs
+the program stored as
+.I realname
+on disk,
+with the given
+arguments.
+It sets the 0th argument of
+the program to
+.IR zero .
+
+For example,
+
+.EX
+     argv0 /bin/csh -bin/csh
+.EE
+
+runs
+.B /bin/csh
+with a 0th argument of
+.BR -bin/csh .
+.B csh
+will think it is a login shell
+and behave accordingly.
+
+.B argv0
+can be used to run some
+.B inetd
+wrappers under
+.BR tcpserver .
+.SH "SEE ALSO"
+csh(1),
+tcpserver(1),
+execve(2),
+execvp(3),
+inetd(8)
diff --git a/date@.1 b/date@.1
new file mode 100644
index 0000000..fa0ba98
--- /dev/null
+++ b/date@.1
@@ -0,0 +1,32 @@
+.TH date@ 1
+.SH NAME
+date@ \- print the date on a host
+.SH SYNTAX
+.B date@
+[
+.I host
+]
+.SH DESCRIPTION
+.B date@
+connects to TCP port 13 (Daytime) on
+.I host
+and prints any data it receives.
+It removes CR and converts unprintable characters to a visible format.
+
+If
+.I host
+is not supplied,
+.B date@
+connects to the local host.
+
+Some computers respond to port 13 with a human-readable date.
+For example, they may be running
+
+.EX
+     tcpserver 0 13 date &
+.EE
+.SH "SEE ALSO"
+cat(1),
+delcr(1),
+tcpclient(1),
+tcpserver(1)
diff --git a/delcr.1 b/delcr.1
new file mode 100644
index 0000000..18ea736
--- /dev/null
+++ b/delcr.1
@@ -0,0 +1,30 @@
+.TH delcr 1
+.SH NAME
+delcr \- remove a CR before each LF
+.SH SYNOPSIS
+.B delcr
+.SH DESCRIPTION
+.B delcr
+removes a CR at the end of each line of input,
+if a CR is present.
+It also removes a CR at the end of a partial final line.
+
+The pipeline
+
+.EX
+     addcr | delcr
+.EE
+
+prints an exact copy of its input.
+.SH COMPATIBILITY
+Some vendors ship
+.B dos2unix
+or
+.B dos2bsd
+tools similar to
+.BR delcr .
+Those tools often blow up on long lines and nulls.
+.B delcr
+has no trouble with long lines and nulls.
+.SH "SEE ALSO"
+addcr(1)
diff --git a/dns.h b/dns.h
index 0948b1a..f06c5a8 100644
--- a/dns.h
+++ b/dns.h
@@ -34,51 +34,60 @@ struct dns_transmit {
   unsigned int curserver;
   struct taia deadline;
   unsigned int pos;
-  char *servers;
-  char localip[4];
+  const char *servers;
+  char localip[16];
+  unsigned int scope_id;
   char qtype[2];
 } ;
 
-extern void dns_random_init(char *);
+extern void dns_random_init(const char *);
 extern unsigned int dns_random(unsigned int);
 
 extern void dns_sortip(char *,unsigned int);
+extern void dns_sortip6(char *,unsigned int);
 
 extern void dns_domain_free(char **);
-extern int dns_domain_copy(char **,char *);
-extern unsigned int dns_domain_length(char *);
-extern int dns_domain_equal(char *,char *);
-extern char *dns_domain_suffix(char *,char *);
-extern int dns_domain_fromdot(char **,char *,unsigned int);
-extern int dns_domain_todot_cat(stralloc *,char *);
+extern int dns_domain_copy(char **,const char *);
+extern unsigned int dns_domain_length(const char *);
+extern int dns_domain_equal(const char *,const char *);
+extern int dns_domain_suffix(const char *,const char *);
+extern unsigned int dns_domain_suffixpos(const char *,const char *);
+extern int dns_domain_fromdot(char **,const char *,unsigned int);
+extern int dns_domain_todot_cat(stralloc *,const char *);
 
-extern unsigned int dns_packet_copy(char *,unsigned int,unsigned int,char *,unsigned int);
-extern unsigned int dns_packet_getname(char *,unsigned int,unsigned int,char **);
-extern unsigned int dns_packet_skipname(char *,unsigned int,unsigned int);
-extern int dns_packet_nameequal(char *,unsigned int,unsigned int,char *,unsigned int,unsigned int);
+extern unsigned int dns_packet_copy(const char *,unsigned int,unsigned int,char *,unsigned int);
+extern unsigned int dns_packet_getname(const char *,unsigned int,unsigned int,char **);
+extern unsigned int dns_packet_skipname(const char *,unsigned int,unsigned int);
 
-extern int dns_transmit_start(struct dns_transmit *,char *,int,char *,char *,char *);
+extern int dns_transmit_start(struct dns_transmit *,const char *,int,const char *,const char *,const char *);
 extern void dns_transmit_free(struct dns_transmit *);
 extern void dns_transmit_io(struct dns_transmit *,iopause_fd *,struct taia *);
-extern int dns_transmit_get(struct dns_transmit *,iopause_fd *,struct taia *);
+extern int dns_transmit_get(struct dns_transmit *,const iopause_fd *,const struct taia *);
 
 extern int dns_resolvconfip(char *);
-extern int dns_resolve(char *,char *);
+extern int dns_resolve(const char *,const char *);
 extern struct dns_transmit dns_resolve_tx;
 
-extern int dns_ip4_packet(stralloc *,char *,unsigned int);
-extern int dns_ip4(stralloc *,stralloc *);
-extern int dns_name_packet(stralloc *,char *,unsigned int);
-extern void dns_name4_domain(char *,char *);
+extern int dns_ip4_packet(stralloc *,const char *,unsigned int);
+extern int dns_ip4(stralloc *,const stralloc *);
+extern int dns_ip6_packet(stralloc *,const char *,unsigned int);
+extern int dns_ip6(stralloc *,stralloc *);
+extern int dns_name_packet(stralloc *,const char *,unsigned int);
+extern void dns_name4_domain(char *,const char *);
 #define DNS_NAME4_DOMAIN 31
-extern int dns_name4(stralloc *,char *);
-extern int dns_txt_packet(stralloc *,char *,unsigned int);
-extern int dns_txt(stralloc *,stralloc *);
-extern int dns_mx_packet(stralloc *,char *,unsigned int);
-extern int dns_mx(stralloc *,stralloc *);
+extern int dns_name4(stralloc *,const char *);
+extern int dns_txt_packet(stralloc *,const char *,unsigned int);
+extern int dns_txt(stralloc *,const stralloc *);
+extern int dns_mx_packet(stralloc *,const char *,unsigned int);
+extern int dns_mx(stralloc *,const stralloc *);
 
 extern int dns_resolvconfrewrite(stralloc *);
-extern int dns_ip4_qualify_rules(stralloc *,stralloc *,stralloc *,stralloc *);
-extern int dns_ip4_qualify(stralloc *,stralloc *,stralloc *);
+extern int dns_ip4_qualify_rules(stralloc *,stralloc *,const stralloc *,const stralloc *);
+extern int dns_ip4_qualify(stralloc *,stralloc *,const stralloc *);
+extern int dns_ip6_qualify_rules(stralloc *,stralloc *,const stralloc *,const stralloc *);
+extern int dns_ip6_qualify(stralloc *,stralloc *,const stralloc *);
+
+extern int dns_name6_domain(char *,char *);
+#define DNS_NAME6_DOMAIN (4*16+11)
 
 #endif
diff --git a/dns_dfd.c b/dns_dfd.c
index 14a29d8..c924718 100644
--- a/dns_dfd.c
+++ b/dns_dfd.c
@@ -1,9 +1,10 @@
-#include "error.h"
-#include "alloc.h"
+#include <stdlib.h>
+#include <errno.h>
 #include "byte.h"
 #include "dns.h"
+#include "error.h"
 
-int dns_domain_fromdot(char **out,char *buf,unsigned int n)
+int dns_domain_fromdot(char **out,const char *buf,unsigned int n)
 {
   char label[63];
   unsigned int labellen = 0; /* <= sizeof label */
@@ -59,11 +60,11 @@ int dns_domain_fromdot(char **out,char *buf,unsigned int n)
   if (namelen + 1 > sizeof name) return 0;
   name[namelen++] = 0;
 
-  x = alloc(namelen);
+  x = malloc(namelen);
   if (!x) return 0;
   byte_copy(x,namelen,name);
 
-  if (*out) alloc_free(*out);
+  if (*out) free(*out);
   *out = x;
   return 1;
 }
diff --git a/dns_domain.c b/dns_domain.c
index f898485..80ac5ea 100644
--- a/dns_domain.c
+++ b/dns_domain.c
@@ -1,16 +1,15 @@
-#include "error.h"
-#include "alloc.h"
+#include <stdlib.h>
 #include "case.h"
 #include "byte.h"
 #include "dns.h"
 
-unsigned int dns_domain_length(char *dn)
+unsigned int dns_domain_length(const char *dn)
 {
-  char *x;
+  const char *x;
   unsigned char c;
 
   x = dn;
-  while (c = *x++)
+  while ((c = *x++))
     x += (unsigned int) c;
   return x - dn;
 }
@@ -18,26 +17,26 @@ unsigned int dns_domain_length(char *dn)
 void dns_domain_free(char **out)
 {
   if (*out) {
-    alloc_free(*out);
+    free(*out);
     *out = 0;
   }
 }
 
-int dns_domain_copy(char **out,char *in)
+int dns_domain_copy(char **out,const char *in)
 {
   unsigned int len;
   char *x;
 
   len = dns_domain_length(in);
-  x = alloc(len);
+  x = malloc(len);
   if (!x) return 0;
   byte_copy(x,len,in);
-  if (*out) alloc_free(*out);
+  if (*out) free(*out);
   *out = x;
   return 1;
 }
 
-int dns_domain_equal(char *dn1,char *dn2)
+int dns_domain_equal(const char *dn1,const char *dn2)
 {
   unsigned int len;
 
@@ -48,12 +47,25 @@ int dns_domain_equal(char *dn1,char *dn2)
   return 1;
 }
 
-char *dns_domain_suffix(char *big,char *little)
+int dns_domain_suffix(const char *big,const char *little)
+{
+  unsigned char c;
+
+  for (;;) {
+    if (dns_domain_equal(big,little)) return 1;
+    c = *big++;
+    if (!c) return 0;
+    big += c;
+  }
+}
+
+unsigned int dns_domain_suffixpos(const char *big,const char *little)
 {
+  const char *orig = big;
   unsigned char c;
 
   for (;;) {
-    if (dns_domain_equal(big,little)) return big;
+    if (dns_domain_equal(big,little)) return big - orig;
     c = *big++;
     if (!c) return 0;
     big += c;
diff --git a/dns_dtda.c b/dns_dtda.c
index 00b41a1..ba1db4f 100644
--- a/dns_dtda.c
+++ b/dns_dtda.c
@@ -1,7 +1,7 @@
 #include "stralloc.h"
 #include "dns.h"
 
-int dns_domain_todot_cat(stralloc *out,char *d)
+int dns_domain_todot_cat(stralloc *out,const char *d)
 {
   char ch;
   char ch2;
diff --git a/dns_ip.c b/dns_ip.c
index fb0526c..e7c3a9a 100644
--- a/dns_ip.c
+++ b/dns_ip.c
@@ -3,7 +3,7 @@
 #include "byte.h"
 #include "dns.h"
 
-int dns_ip4_packet(stralloc *out,char *buf,unsigned int len)
+int dns_ip4_packet(stralloc *out,const char *buf,unsigned int len)
 {
   unsigned int pos;
   char header[12];
@@ -36,7 +36,7 @@ int dns_ip4_packet(stralloc *out,char *buf,unsigned int len)
 
 static char *q = 0;
 
-int dns_ip4(stralloc *out,stralloc *fqdn)
+int dns_ip4(stralloc *out,const stralloc *fqdn)
 {
   unsigned int i;
   char code;
diff --git a/dns_ip6.c b/dns_ip6.c
new file mode 100644
index 0000000..1a2ce08
--- /dev/null
+++ b/dns_ip6.c
@@ -0,0 +1,103 @@
+#include "stralloc.h"
+#include "uint16.h"
+#include "byte.h"
+#include "dns.h"
+#include "ip4.h"
+#include "ip6.h"
+
+static int dns_ip6_packet_add(stralloc *out,const char *buf,unsigned int len)
+{
+  unsigned int pos;
+  char header[16];
+  uint16 numanswers;
+  uint16 datalen;
+
+  pos = dns_packet_copy(buf,len,0,header,12); if (!pos) return -1;
+  uint16_unpack_big(header + 6,&numanswers);
+  pos = dns_packet_skipname(buf,len,pos); if (!pos) return -1;
+  pos += 4;
+
+  while (numanswers--) {
+    pos = dns_packet_skipname(buf,len,pos); if (!pos) return -1;
+    pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) return -1;
+    uint16_unpack_big(header + 8,&datalen);
+    if (byte_equal(header,2,DNS_T_AAAA)) {
+      if (byte_equal(header + 2,2,DNS_C_IN))
+        if (datalen == 16) {
+	  if (!dns_packet_copy(buf,len,pos,header,16)) return -1;
+	  if (!stralloc_catb(out,header,16)) return -1;
+	}
+    } else if (byte_equal(header,2,DNS_T_A))
+      if (byte_equal(header + 2,2,DNS_C_IN))
+        if (datalen == 4) {
+	  byte_copy(header,12,V4mappedprefix);
+	  if (!dns_packet_copy(buf,len,pos,header+12,4)) return -1;
+	  if (!stralloc_catb(out,header,16)) return -1;
+	}
+    pos += datalen;
+  }
+
+  dns_sortip6(out->s,out->len);
+  return 0;
+}
+
+int dns_ip6_packet(stralloc *out,const char *buf,unsigned int len) {
+  if (!stralloc_copys(out,"")) return -1;
+  return dns_ip6_packet_add(out,buf,len);
+}
+
+static char *q = 0;
+
+int dns_ip6(stralloc *out,stralloc *fqdn)
+{
+  unsigned int i;
+  char code;
+  char ch;
+  char ip[16];
+
+  if (!stralloc_copys(out,"")) return -1;
+  if (!stralloc_readyplus(fqdn,1)) return -1;
+  fqdn->s[fqdn->len]=0;
+  if ((i=scan_ip6(fqdn->s,ip))) {
+    if (fqdn->s[i]) return -1;
+    stralloc_copyb(out,ip,16);
+    return 0;
+  }
+  code = 0;
+  for (i = 0;i <= fqdn->len;++i) {
+    if (i < fqdn->len)
+      ch = fqdn->s[i];
+    else
+      ch = '.';
+
+    if ((ch == '[') || (ch == ']')) continue;
+    if (ch == '.') {
+      if (!stralloc_append(out,&code)) return -1;
+      code = 0;
+      continue;
+    }
+    if ((ch >= '0') && (ch <= '9')) {
+      code *= 10;
+      code += ch - '0';
+      continue;
+    }
+
+    if (!dns_domain_fromdot(&q,fqdn->s,fqdn->len)) return -1;
+    if (!stralloc_copys(out,"")) return -1;
+    if (dns_resolve(q,DNS_T_AAAA) != -1)
+      if (dns_ip6_packet_add(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen) != -1) {
+	dns_transmit_free(&dns_resolve_tx);
+	dns_domain_free(&q);
+      }
+    if (!dns_domain_fromdot(&q,fqdn->s,fqdn->len)) return -1;
+    if (dns_resolve(q,DNS_T_A) != -1)
+      if (dns_ip6_packet_add(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen) != -1) {
+	dns_transmit_free(&dns_resolve_tx);
+	dns_domain_free(&q);
+      }
+    return out->a>0?0:-1;
+  }
+
+  out->len &= ~3;
+  return 0;
+}
diff --git a/dns_ipq.c b/dns_ipq.c
index 8181ab7..5b65e23 100644
--- a/dns_ipq.c
+++ b/dns_ipq.c
@@ -4,7 +4,7 @@
 #include "str.h"
 #include "dns.h"
 
-static int doit(stralloc *work,char *rule)
+static int doit(stralloc *work,const char *rule)
 {
   char ch;
   unsigned int colon;
@@ -30,7 +30,7 @@ static int doit(stralloc *work,char *rule)
   return stralloc_cats(work,rule + colon + 1);
 }
 
-int dns_ip4_qualify_rules(stralloc *out,stralloc *fqdn,stralloc *in,stralloc *rules)
+int dns_ip4_qualify_rules(stralloc *out,stralloc *fqdn,const stralloc *in,const stralloc *rules)
 {
   unsigned int i;
   unsigned int j;
@@ -63,7 +63,7 @@ int dns_ip4_qualify_rules(stralloc *out,stralloc *fqdn,stralloc *in,stralloc *ru
   }
 }
 
-int dns_ip4_qualify(stralloc *out,stralloc *fqdn,stralloc *in)
+int dns_ip4_qualify(stralloc *out,stralloc *fqdn,const stralloc *in)
 {
   static stralloc rules;
   if (dns_resolvconfrewrite(&rules) == -1) return -1;
diff --git a/dns_ipq6.c b/dns_ipq6.c
new file mode 100644
index 0000000..d5cea12
--- /dev/null
+++ b/dns_ipq6.c
@@ -0,0 +1,72 @@
+#include "stralloc.h"
+#include "case.h"
+#include "byte.h"
+#include "str.h"
+#include "dns.h"
+
+static int doit(stralloc *work,const char *rule)
+{
+  char ch;
+  unsigned int colon;
+  unsigned int prefixlen;
+
+  ch = *rule++;
+  if ((ch != '?') && (ch != '=') && (ch != '*') && (ch != '-')) return 1;
+  colon = str_chr(rule,':');
+  if (!rule[colon]) return 1;
+
+  if (work->len < colon) return 1;
+  prefixlen = work->len - colon;
+  if ((ch == '=') && prefixlen) return 1;
+  if (case_diffb(rule,colon,work->s + prefixlen)) return 1;
+  if (ch == '?') {
+    if (byte_chr(work->s,prefixlen,'.') < prefixlen) return 1;
+    if (byte_chr(work->s,prefixlen,':') < prefixlen) return 1;
+    if (byte_chr(work->s,prefixlen,'[') < prefixlen) return 1;
+    if (byte_chr(work->s,prefixlen,']') < prefixlen) return 1;
+  }
+
+  work->len = prefixlen;
+  if (ch == '-') work->len = 0;
+  return stralloc_cats(work,rule + colon + 1);
+}
+
+int dns_ip6_qualify_rules(stralloc *out,stralloc *fqdn,const stralloc *in,const stralloc *rules)
+{
+  unsigned int i;
+  unsigned int j;
+  unsigned int plus;
+  unsigned int fqdnlen;
+
+  if (!stralloc_copy(fqdn,in)) return -1;
+
+  for (j = i = 0;j < rules->len;++j)
+    if (!rules->s[j]) {
+      if (!doit(fqdn,rules->s + i)) return -1;
+      i = j + 1;
+    }
+
+  fqdnlen = fqdn->len;
+  plus = byte_chr(fqdn->s,fqdnlen,'+');
+  if (plus >= fqdnlen)
+    return dns_ip6(out,fqdn);
+
+  i = plus + 1;
+  for (;;) {
+    j = byte_chr(fqdn->s + i,fqdnlen - i,'+');
+    byte_copy(fqdn->s + plus,j,fqdn->s + i);
+    fqdn->len = plus + j;
+    if (dns_ip6(out,fqdn) == -1) return -1;
+    if (out->len) return 0;
+    i += j;
+    if (i >= fqdnlen) return 0;
+    ++i;
+  }
+}
+
+int dns_ip6_qualify(stralloc *out,stralloc *fqdn,const stralloc *in)
+{
+  static stralloc rules;
+  if (dns_resolvconfrewrite(&rules) == -1) return -1;
+  return dns_ip6_qualify_rules(out,fqdn,in,&rules);
+}
diff --git a/dns_name.c b/dns_name.c
index dcb10c7..1f03186 100644
--- a/dns_name.c
+++ b/dns_name.c
@@ -2,10 +2,11 @@
 #include "uint16.h"
 #include "byte.h"
 #include "dns.h"
+#include "ip6.h"
 
 static char *q = 0;
 
-int dns_name_packet(stralloc *out,char *buf,unsigned int len)
+int dns_name_packet(stralloc *out,const char *buf,unsigned int len)
 {
   unsigned int pos;
   char header[12];
@@ -35,7 +36,7 @@ int dns_name_packet(stralloc *out,char *buf,unsigned int len)
   return 0;
 }
 
-int dns_name4(stralloc *out,char ip[4])
+int dns_name4(stralloc *out,const char ip[4])
 {
   char name[DNS_NAME4_DOMAIN];
 
@@ -46,3 +47,17 @@ int dns_name4(stralloc *out,char ip[4])
   dns_domain_free(&q);
   return 0;
 }
+
+int dns_name6(stralloc *out,char ip[16])
+{
+  char name[DNS_NAME6_DOMAIN];
+
+  if (ip6_isv4mapped(ip))
+    return dns_name4(out,ip+12);
+  dns_name6_domain(name,ip);
+  if (dns_resolve(name,DNS_T_PTR) == -1) return -1;
+  if (dns_name_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen) == -1) return -1;
+  dns_transmit_free(&dns_resolve_tx);
+  dns_domain_free(&q);
+  return 0;
+}
diff --git a/dns_nd.c b/dns_nd.c
index 279d74d..aa54e5d 100644
--- a/dns_nd.c
+++ b/dns_nd.c
@@ -2,7 +2,7 @@
 #include "fmt.h"
 #include "dns.h"
 
-void dns_name4_domain(char name[DNS_NAME4_DOMAIN],char ip[4])
+void dns_name4_domain(char name[DNS_NAME4_DOMAIN],const char ip[4])
 {
   unsigned int namelen;
   unsigned int i;
diff --git a/dns_nd6.c b/dns_nd6.c
new file mode 100644
index 0000000..fb1da88
--- /dev/null
+++ b/dns_nd6.c
@@ -0,0 +1,28 @@
+#include "byte.h"
+#include "fmt.h"
+#include "dns.h"
+
+/* RFC1886:
+ *   4321:0:1:2:3:4:567:89ab
+ * ->
+ *   b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.IP6.INT.
+ */
+
+static inline char tohex(char c) {
+  return c>=10?c-10+'a':c+'0';
+}
+
+int dns_name6_domain(char name[DNS_NAME6_DOMAIN],char ip[16])
+{
+  unsigned int j;
+
+  for (j=0; j<16; j++) {
+    name[j*4]=1;
+    name[j*4+1]=tohex(ip[15-j] & 15);
+    name[j*4+2]=1;
+    name[j*4+3]=tohex((unsigned char)ip[15-j] >> 4);
+  }
+  byte_copy(name + 4*16,10,"\3ip6\4arpa\0");
+  return 4*16+10;
+}
+
diff --git a/dns_packet.c b/dns_packet.c
index 04a2cc8..72cfb35 100644
--- a/dns_packet.c
+++ b/dns_packet.c
@@ -2,10 +2,11 @@
 DNS should have used LZ77 instead of its own sophomoric compression algorithm.
 */
 
-#include "error.h"
+#include <errno.h>
 #include "dns.h"
+#include "error.h"
 
-unsigned int dns_packet_copy(char *buf,unsigned int len,unsigned int pos,char *out,unsigned int outlen)
+unsigned int dns_packet_copy(const char *buf,unsigned int len,unsigned int pos,char *out,unsigned int outlen)
 {
   while (outlen) {
     if (pos >= len) { errno = error_proto; return 0; }
@@ -15,7 +16,7 @@ unsigned int dns_packet_copy(char *buf,unsigned int len,unsigned int pos,char *o
   return pos;
 }
 
-unsigned int dns_packet_skipname(char *buf,unsigned int len,unsigned int pos)
+unsigned int dns_packet_skipname(const char *buf,unsigned int len,unsigned int pos)
 {
   unsigned char ch;
 
@@ -32,7 +33,7 @@ unsigned int dns_packet_skipname(char *buf,unsigned int len,unsigned int pos)
   return 0;
 }
 
-unsigned int dns_packet_getname(char *buf,unsigned int len,unsigned int pos,char **d)
+unsigned int dns_packet_getname(const char *buf,unsigned int len,unsigned int pos,char **d)
 {
   unsigned int loop = 0;
   unsigned int state = 0;
diff --git a/dns_random.c b/dns_random.c
index b9892b4..2158ed4 100644
--- a/dns_random.c
+++ b/dns_random.c
@@ -1,3 +1,4 @@
+#include <unistd.h>
 #include "dns.h"
 #include "taia.h"
 #include "uint32.h"
@@ -29,7 +30,7 @@ static void surf(void)
   }
 }
 
-void dns_random_init(char data[128])
+void dns_random_init(const char data[128])
 {
   int i;
   struct taia t;
diff --git a/dns_rcip.c b/dns_rcip.c
index 2356c8b..794f6be 100644
--- a/dns_rcip.c
+++ b/dns_rcip.c
@@ -2,12 +2,13 @@
 #include "openreadclose.h"
 #include "byte.h"
 #include "ip4.h"
-#include "env.h"
+#include "ip6.h"
 #include "dns.h"
+#include "env.h"
 
 static stralloc data = {0};
 
-static int init(char ip[64])
+static int init(char ip[256])
 {
   int i;
   int j;
@@ -16,15 +17,16 @@ static int init(char ip[64])
 
   x = env_get("DNSCACHEIP");
   if (x)
-    while (iplen <= 60)
+    while (iplen <= 60) {
       if (*x == '.')
 	++x;
       else {
-        i = ip4_scan(x,ip + iplen);
+        i = scan_ip6(x,ip + iplen);
 	if (!i) break;
 	x += i;
-	iplen += 4;
+	iplen += 16;
       }
+    }
 
   if (!iplen) {
     i = openreadclose("/etc/resolv.conf",&data,64);
@@ -39,8 +41,9 @@ static int init(char ip[64])
             while ((data.s[i] == ' ') || (data.s[i] == '\t'))
               ++i;
             if (iplen <= 60)
-              if (ip4_scan(data.s + i,ip + iplen))
-                iplen += 4;
+              if (scan_ip6(data.s + i,ip + iplen)) {
+                iplen += 16;
+	      }
           }
           i = j + 1;
         }
@@ -48,19 +51,19 @@ static int init(char ip[64])
   }
 
   if (!iplen) {
-    byte_copy(ip,4,"\177\0\0\1");
-    iplen = 4;
+    byte_copy(ip,16,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1");
+    iplen = 16;
   }
-  byte_zero(ip + iplen,64 - iplen);
+  byte_zero(ip + iplen,256 - iplen);
   return 0;
 }
 
 static int ok = 0;
 static unsigned int uses;
 static struct taia deadline;
-static char ip[64]; /* defined if ok */
+static char ip[256]; /* defined if ok */
 
-int dns_resolvconfip(char s[64])
+int dns_resolvconfip(char s[256])
 {
   struct taia now;
 
@@ -77,6 +80,6 @@ int dns_resolvconfip(char s[64])
   }
 
   --uses;
-  byte_copy(s,64,ip);
+  byte_copy(s,256,ip);
   return 0;
 }
diff --git a/dns_rcrw.c b/dns_rcrw.c
index 6f215ac..b0c8e6d 100644
--- a/dns_rcrw.c
+++ b/dns_rcrw.c
@@ -1,16 +1,17 @@
+#include <unistd.h>
 #include "taia.h"
-#include "env.h"
 #include "byte.h"
 #include "str.h"
 #include "openreadclose.h"
 #include "dns.h"
+#include "env.h"
 
 static stralloc data = {0};
 
 static int init(stralloc *rules)
 {
   char host[256];
-  char *x;
+  const char *x;
   int i;
   int j;
   int k;
diff --git a/dns_resolve.c b/dns_resolve.c
index 3365c00..82b5bbb 100644
--- a/dns_resolve.c
+++ b/dns_resolve.c
@@ -2,19 +2,20 @@
 #include "taia.h"
 #include "byte.h"
 #include "dns.h"
+#include "ip6.h"
 
 struct dns_transmit dns_resolve_tx = {0};
 
-int dns_resolve(char *q,char qtype[2])
+int dns_resolve(const char *q,const char qtype[2])
 {
   struct taia stamp;
   struct taia deadline;
-  char servers[64];
+  char servers[256];
   iopause_fd x[1];
   int r;
 
   if (dns_resolvconfip(servers) == -1) return -1;
-  if (dns_transmit_start(&dns_resolve_tx,servers,1,q,qtype,"\0\0\0\0") == -1) return -1;
+  if (dns_transmit_start(&dns_resolve_tx,servers,1,q,qtype,V6any) == -1) return -1;
 
   for (;;) {
     taia_now(&stamp);
diff --git a/dns_sortip6.c b/dns_sortip6.c
new file mode 100644
index 0000000..7e752e9
--- /dev/null
+++ b/dns_sortip6.c
@@ -0,0 +1,20 @@
+#include "byte.h"
+#include "dns.h"
+
+/* XXX: sort servers by configurable notion of closeness? */
+/* XXX: pay attention to competence of each server? */
+
+void dns_sortip6(char *s,unsigned int n)
+{
+  unsigned int i;
+  char tmp[16];
+
+  n >>= 4;
+  while (n > 1) {
+    i = dns_random(n);
+    --n;
+    byte_copy(tmp,16,s + (i << 4));
+    byte_copy(s + (i << 4),16,s + (n << 4));
+    byte_copy(s + (n << 4),16,tmp);
+  }
+}
diff --git a/dns_transmit.c b/dns_transmit.c
index df12826..9511511 100644
--- a/dns_transmit.c
+++ b/dns_transmit.c
@@ -1,12 +1,15 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <stdlib.h>
 #include "socket.h"
-#include "alloc.h"
-#include "error.h"
+#include <errno.h>
 #include "byte.h"
-#include "readwrite.h"
 #include "uint16.h"
 #include "dns.h"
+#include "ip6.h"
 
-static int serverwantstcp(char *buf,unsigned int len)
+static int serverwantstcp(const char *buf,unsigned int len)
 {
   char out[12];
 
@@ -15,7 +18,7 @@ static int serverwantstcp(char *buf,unsigned int len)
   return 0;
 }
 
-static int serverfailed(char *buf,unsigned int len)
+static int serverfailed(const char *buf,unsigned int len)
 {
   char out[12];
   unsigned int rcode;
@@ -23,11 +26,11 @@ static int serverfailed(char *buf,unsigned int len)
   if (!dns_packet_copy(buf,len,0,out,12)) return 1;
   rcode = out[3];
   rcode &= 15;
-  if (rcode && (rcode != 3)) { errno = error_again; return 1; }
+  if (rcode && (rcode != 3)) { errno = EAGAIN; return 1; }
   return 0;
 }
 
-static int irrelevant(struct dns_transmit *d,char *buf,unsigned int len)
+static int irrelevant(const struct dns_transmit *d,const char *buf,unsigned int len)
 {
   char out[12];
   char *dn;
@@ -40,8 +43,8 @@ static int irrelevant(struct dns_transmit *d,char *buf,unsigned int len)
 
   dn = 0;
   pos = dns_packet_getname(buf,len,pos,&dn); if (!pos) return 1;
-  if (!dns_domain_equal(dn,d->query + 14)) { alloc_free(dn); return 1; }
-  alloc_free(dn);
+  if (!dns_domain_equal(dn,d->query + 14)) { free(dn); return 1; }
+  free(dn);
 
   pos = dns_packet_copy(buf,len,pos,out,4); if (!pos) return 1;
   if (byte_diff(out,2,d->qtype)) return 1;
@@ -53,14 +56,14 @@ static int irrelevant(struct dns_transmit *d,char *buf,unsigned int len)
 static void packetfree(struct dns_transmit *d)
 {
   if (!d->packet) return;
-  alloc_free(d->packet);
+  free(d->packet);
   d->packet = 0;
 }
 
 static void queryfree(struct dns_transmit *d)
 {
   if (!d->query) return;
-  alloc_free(d->query);
+  free(d->query);
   d->query = 0;
 }
 
@@ -83,9 +86,9 @@ static int randombind(struct dns_transmit *d)
   int j;
 
   for (j = 0;j < 10;++j)
-    if (socket_bind4(d->s1 - 1,d->localip,1025 + dns_random(64510)) == 0)
+    if (socket_bind6(d->s1 - 1,d->localip,1025 + dns_random(64510),d->scope_id) == 0)
       return 0;
-  if (socket_bind4(d->s1 - 1,d->localip,0) == 0)
+  if (socket_bind6(d->s1 - 1,d->localip,0,d->scope_id) == 0)
     return 0;
   return -1;
 }
@@ -94,22 +97,22 @@ static const int timeouts[4] = { 1, 3, 11, 45 };
 
 static int thisudp(struct dns_transmit *d)
 {
-  char *ip;
+  const char *ip;
 
   socketfree(d);
 
   while (d->udploop < 4) {
     for (;d->curserver < 16;++d->curserver) {
-      ip = d->servers + 4 * d->curserver;
-      if (byte_diff(ip,4,"\0\0\0\0")) {
+      ip = d->servers + 16 * d->curserver;
+      if (byte_diff(ip,16,V6any)) {
 	d->query[2] = dns_random(256);
 	d->query[3] = dns_random(256);
   
-        d->s1 = 1 + socket_udp();
+        d->s1 = 1 + socket_udp6();
         if (!d->s1) { dns_transmit_free(d); return -1; }
 	if (randombind(d) == -1) { dns_transmit_free(d); return -1; }
 
-        if (socket_connect4(d->s1 - 1,ip,53) == 0)
+        if (socket_connect6(d->s1 - 1,ip,53,d->scope_id) == 0)
           if (send(d->s1 - 1,d->query + 2,d->querylen - 2,0) == d->querylen - 2) {
             struct taia now;
             taia_now(&now);
@@ -145,29 +148,29 @@ static int nextudp(struct dns_transmit *d)
 static int thistcp(struct dns_transmit *d)
 {
   struct taia now;
-  char *ip;
+  const char *ip;
 
   socketfree(d);
   packetfree(d);
 
   for (;d->curserver < 16;++d->curserver) {
-    ip = d->servers + 4 * d->curserver;
-    if (byte_diff(ip,4,"\0\0\0\0")) {
+    ip = d->servers + 16 * d->curserver;
+    if (byte_diff(ip,16,V6any)) {
       d->query[2] = dns_random(256);
       d->query[3] = dns_random(256);
 
-      d->s1 = 1 + socket_tcp();
+      d->s1 = 1 + socket_tcp6();
       if (!d->s1) { dns_transmit_free(d); return -1; }
       if (randombind(d) == -1) { dns_transmit_free(d); return -1; }
   
       taia_now(&now);
       taia_uint(&d->deadline,10);
       taia_add(&d->deadline,&d->deadline,&now);
-      if (socket_connect4(d->s1 - 1,ip,53) == 0) {
+      if (socket_connect6(d->s1 - 1,ip,53,d->scope_id) == 0) {
         d->tcpstate = 2;
         return 0;
       }
-      if ((errno == error_inprogress) || (errno == error_wouldblock)) {
+      if ((errno == EINPROGRESS) || (errno == EWOULDBLOCK)) {
         d->tcpstate = 1;
         return 0;
       }
@@ -191,16 +194,16 @@ static int nexttcp(struct dns_transmit *d)
   return thistcp(d);
 }
 
-int dns_transmit_start(struct dns_transmit *d,char servers[64],int flagrecursive,char *q,char qtype[2],char localip[4])
+int dns_transmit_start(struct dns_transmit *d,const char servers[256],int flagrecursive,const char *q,const char qtype[2],const char localip[16])
 {
   unsigned int len;
 
   dns_transmit_free(d);
-  errno = error_io;
+  errno = EIO;
 
   len = dns_domain_length(q);
   d->querylen = len + 18;
-  d->query = alloc(d->querylen);
+  d->query = malloc(d->querylen);
   if (!d->query) return -1;
 
   uint16_pack_big(d->query,len + 16);
@@ -211,7 +214,7 @@ int dns_transmit_start(struct dns_transmit *d,char servers[64],int flagrecursive
 
   byte_copy(d->qtype,2,qtype);
   d->servers = servers;
-  byte_copy(d->localip,4,localip);
+  byte_copy(d->localip,16,localip);
 
   d->udploop = flagrecursive ? 1 : 0;
 
@@ -236,19 +239,19 @@ void dns_transmit_io(struct dns_transmit *d,iopause_fd *x,struct taia *deadline)
     *deadline = d->deadline;
 }
 
-int dns_transmit_get(struct dns_transmit *d,iopause_fd *x,struct taia *when)
+int dns_transmit_get(struct dns_transmit *d,const iopause_fd *x,const struct taia *when)
 {
   char udpbuf[513];
   unsigned char ch;
   int r;
   int fd;
 
-  errno = error_io;
+  errno = EIO;
   fd = d->s1 - 1;
 
   if (!x->revents) {
     if (taia_less(when,&d->deadline)) return 0;
-    errno = error_timeout;
+    errno = ETIMEDOUT;
     if (d->tcpstate == 0) return nextudp(d);
     return nexttcp(d);
   }
@@ -260,7 +263,7 @@ have sent query to curserver on UDP socket s
 */
     r = recv(fd,udpbuf,sizeof udpbuf,0);
     if (r <= 0) {
-      if (d->udploop == 2) return 0;
+      if (errno == ECONNREFUSED) if (d->udploop == 2) return 0;
       return nextudp(d);
     }
     if (r + 1 > sizeof udpbuf) return 0;
@@ -274,7 +277,7 @@ have sent query to curserver on UDP socket s
     socketfree(d);
 
     d->packetlen = r;
-    d->packet = alloc(d->packetlen);
+    d->packet = malloc(d->packetlen);
     if (!d->packet) { dns_transmit_free(d); return -1; }
     byte_copy(d->packet,d->packetlen,udpbuf);
     queryfree(d);
@@ -334,7 +337,7 @@ have received one byte of packet length into packetlen
     d->packetlen += ch;
     d->tcpstate = 5;
     d->pos = 0;
-    d->packet = alloc(d->packetlen);
+    d->packet = malloc(d->packetlen);
     if (!d->packet) { dns_transmit_free(d); return -1; }
     return 0;
   }
diff --git a/dns_txt.c b/dns_txt.c
index 263b641..44deafe 100644
--- a/dns_txt.c
+++ b/dns_txt.c
@@ -3,7 +3,7 @@
 #include "byte.h"
 #include "dns.h"
 
-int dns_txt_packet(stralloc *out,char *buf,unsigned int len)
+int dns_txt_packet(stralloc *out,const char *buf,unsigned int len)
 {
   unsigned int pos;
   char header[12];
@@ -48,7 +48,7 @@ int dns_txt_packet(stralloc *out,char *buf,unsigned int len)
 
 static char *q = 0;
 
-int dns_txt(stralloc *out,stralloc *fqdn)
+int dns_txt(stralloc *out,const stralloc *fqdn)
 {
   if (!dns_domain_fromdot(&q,fqdn->s,fqdn->len)) return -1;
   if (dns_resolve(q,DNS_T_TXT) == -1) return -1;
diff --git a/error.h b/error.h
index d5f3c7e..f660d93 100644
--- a/error.h
+++ b/error.h
@@ -1,7 +1,7 @@
 #ifndef ERROR_H
 #define ERROR_H
 
-extern int errno;
+#include <errno.h>
 
 extern int error_intr;
 extern int error_nomem;
diff --git a/finger@.1 b/finger@.1
new file mode 100644
index 0000000..93b6288
--- /dev/null
+++ b/finger@.1
@@ -0,0 +1,45 @@
+.TH finger@ 1
+.SH NAME
+finger@ \- get user information from a host
+.SH SYNTAX
+.B finger@
+[
+.I host
+[
+.I user
+]
+]
+.SH DESCRIPTION
+.B finger@
+connects to TCP port 79 (Finger) on
+.IR host ,
+sends
+.I user
+(with an extra CR)
+to
+.IR host ,
+and prints any data it receives.
+It removes CR and converts unprintable characters to a visible format.
+Some computers respond to port 79 with information about
+.IR user .
+
+If
+.I user
+is not supplied,
+.B finger@
+sends a blank line to
+.IR host .
+Some computers respond with information about
+all the users who are logged in.
+
+If
+.I host
+is not supplied,
+.B finger@
+connects to the local host.
+.SH "SEE ALSO"
+addcr(1),
+cat(1),
+delcr(1),
+finger(1),
+tcpclient(1)
diff --git a/fixcr.1 b/fixcr.1
new file mode 100644
index 0000000..ebb8b53
--- /dev/null
+++ b/fixcr.1
@@ -0,0 +1,11 @@
+.TH fixcr 1
+.SH NAME
+fixcr \- make sure that there is a CR before each LF
+.SH SYNOPSIS
+.B fixcr
+.SH DESCRIPTION
+.B fixcr
+inserts CR at the end of each line of input where a CR is not already present.
+It does not insert CR at the end of a partial final line.
+.SH "SEE ALSO"
+addcr(1)
diff --git a/fmt_xlong.c b/fmt_xlong.c
new file mode 100644
index 0000000..332fc9a
--- /dev/null
+++ b/fmt_xlong.c
@@ -0,0 +1,22 @@
+#include "fmt.h"
+
+char tohex(char num) {
+  if (num<10)
+    return num+'0';
+  else if (num<16)
+    return num-10+'a';
+  else
+    return -1;
+}
+
+unsigned int fmt_xlong(register char *s,register unsigned long u)
+{
+  register unsigned int len; register unsigned long q;
+  len = 1; q = u;
+  while (q > 15) { ++len; q /= 16; }
+  if (s) {
+    s += len;
+    do { *--s = tohex(u % 16); u /= 16; } while(u); /* handles u == 0 */
+  }
+  return len;
+}
diff --git a/haveip6.h1 b/haveip6.h1
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/haveip6.h1
@@ -0,0 +1 @@
+
diff --git a/haveip6.h2 b/haveip6.h2
new file mode 100644
index 0000000..5564de9
--- /dev/null
+++ b/haveip6.h2
@@ -0,0 +1 @@
+#define LIBC_HAS_IP6 1
diff --git a/hier.c b/hier.c
index 5663ada..546cc6d 100644
--- a/hier.c
+++ b/hier.c
@@ -4,6 +4,9 @@ void hier()
 {
   h(auto_home,-1,-1,02755);
   d(auto_home,"bin",-1,-1,02755);
+  d(auto_home,"man",-1,-1,02755);
+  d(auto_home,"man/man1",-1,-1,02755);
+  d(auto_home,"man/man5",-1,-1,02755);
 
   c(auto_home,"bin","tcpserver",-1,-1,0755);
   c(auto_home,"bin","tcprules",-1,-1,0755);
@@ -22,4 +25,20 @@ void hier()
   c(auto_home,"bin","delcr",-1,-1,0755);
   c(auto_home,"bin","fixcrio",-1,-1,0755);
   c(auto_home,"bin","rblsmtpd",-1,-1,0755);
+
+  c(auto_home,"man/man1","tcpclient.1",-1,-1,0644);
+  c(auto_home,"man/man1","tcpserver.1",-1,-1,0644);
+  c(auto_home,"man/man1","tcprules.1",-1,-1,0644);
+  c(auto_home,"man/man1","tcprulescheck.1",-1,-1,0644);
+  c(auto_home,"man/man1","fixcr.1",-1,-1,0644);
+  c(auto_home,"man/man1","addcr.1",-1,-1,0644);
+  c(auto_home,"man/man1","delcr.1",-1,-1,0644);
+  c(auto_home,"man/man1","who@.1",-1,-1,0644);
+  c(auto_home,"man/man1","date@.1",-1,-1,0644);
+  c(auto_home,"man/man1","finger@.1",-1,-1,0644);
+  c(auto_home,"man/man1","http@.1",-1,-1,0644);
+  c(auto_home,"man/man1","mconnect.1",-1,-1,0644);
+  c(auto_home,"man/man1","argv0.1",-1,-1,0644);
+  c(auto_home,"man/man1","recordio.1",-1,-1,0644);
+  c(auto_home,"man/man5","tcp-environ.5",-1,-1,0644);
 }
diff --git a/http@.1 b/http@.1
new file mode 100644
index 0000000..4861b34
--- /dev/null
+++ b/http@.1
@@ -0,0 +1,52 @@
+.TH http@ 1
+.SH NAME
+http@ \- get a web page from a host through HTTP
+.SH SYNTAX
+.B http@
+[
+.I host
+[
+.I page
+[
+.I port
+]
+]
+]
+.SH DESCRIPTION
+.B http@
+connects to
+.I port
+on
+.IR host ,
+sends
+.B GET /\fIpage
+(with an extra CR)
+to
+.IR host ,
+and prints any data it receives,
+removing CR from the end of each line.
+
+If
+.I port
+is not supplied,
+.B http@
+uses port 80 (HTTP).
+
+If
+.I page
+is not supplied,
+.B http@
+sends
+.B GET /
+to
+.IR host .
+
+If
+.I host
+is not supplied,
+.B http@
+connects to the local host.
+.SH "SEE ALSO"
+addcr(1),
+delcr(1),
+tcpclient(1)
diff --git a/ip4.h b/ip4.h
index 64a7c1e..b906557 100644
--- a/ip4.h
+++ b/ip4.h
@@ -6,4 +6,6 @@ extern unsigned int ip4_fmt(char *,char *);
 
 #define IP4_FMT 20
 
+extern const char ip4loopback[4]; /* = {127,0,0,1}; */
+
 #endif
diff --git a/ip6.h b/ip6.h
new file mode 100644
index 0000000..88ff120
--- /dev/null
+++ b/ip6.h
@@ -0,0 +1,28 @@
+#ifndef IP6_H
+#define IP6_H
+
+#include "byte.h"
+
+extern unsigned int scan_ip6(const char *src,char *ip);
+extern unsigned int fmt_ip6(char *dest,const char *ip);
+
+extern unsigned int scan_ip6_flat(const char *src,char *);
+extern unsigned int fmt_ip6_flat(char *dest,const char *);
+
+/*
+ ip6 address syntax: (h = hex digit), no leading '0' required
+   1. hhhh:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh
+   2. any number of 0000 may be abbreviated as "::", but only once
+ flat ip6 address syntax:
+   hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+ */
+
+#define IP6_FMT 40
+
+extern const unsigned char V4mappedprefix[12]; /*={0,0,0,0,0,0,0,0,0,0,0xff,0xff}; */
+extern const unsigned char V6loopback[16]; /*={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; */
+extern const unsigned char V6any[16]; /*={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; */
+
+#define ip6_isv4mapped(ip) (byte_equal(ip,12,V4mappedprefix))
+
+#endif
diff --git a/ip6_fmt.c b/ip6_fmt.c
new file mode 100644
index 0000000..d7c010a
--- /dev/null
+++ b/ip6_fmt.c
@@ -0,0 +1,64 @@
+#include "fmt.h"
+#include "byte.h"
+#include "ip4.h"
+#include "ip6.h"
+
+unsigned int ip6_fmt(char *s,char ip[16])
+{
+  unsigned long len,temp, k, pos0=0,len0=0, pos1=0, compr=0;
+
+  for (k=0; k<16; k+=2) {
+    if (ip[k]==0 && ip[k+1]==0) {
+      if (!compr) {
+        compr=1;
+        pos1=k;
+      }
+      if (k==14) { k=16; goto last; }
+    } else if (compr) {
+    last:
+      if ((temp=k-pos1) > len0) {
+        len0=temp;
+        pos0=pos1;
+      }
+      compr=0;
+    }
+  }
+
+  for (len=0,k=0; k<16; k+=2) {
+    if (k==12 && ip6_isv4mapped(ip)) {
+      len += ip4_fmt(s,ip+12);
+      break;
+    }
+    if (pos0==k && len0) {
+      if (k==0) { ++len; if (s) *s++ = ':'; }
+      ++len; if (s) *s++ = ':';
+      k += len0-2;
+      continue;
+    }
+    temp = ((unsigned long) (unsigned char) ip[k] << 8) +
+            (unsigned long) (unsigned char) ip[k+1];
+    temp = fmt_xlong(s,temp); len += temp; if (s) s += temp;
+    if (k<14) { ++len; if (s) *s++ = ':'; }
+  }
+
+  return len;
+}
+
+static char tohex(char num) {
+  if (num<10)
+    return num+'0';
+  else if (num<16)
+    return num-10+'a';
+  else
+    return -1;
+}
+
+unsigned int ip6_fmt_flat(char *s,char ip[16])
+{
+  int i;
+  for (i=0; i<16; i++) {
+    *s++=tohex((unsigned char)ip[i] >> 4);
+    *s++=tohex((unsigned char)ip[i] & 15);
+  }
+  return 32;
+}
diff --git a/mconnect.1 b/mconnect.1
new file mode 100644
index 0000000..6648367
--- /dev/null
+++ b/mconnect.1
@@ -0,0 +1,36 @@
+.TH mconnect 1
+.SH NAME
+mconnect \- connect to the SMTP server on a host
+.SH SYNTAX
+.B mconnect
+[
+.I host
+[
+.I port
+]
+]
+.SH DESCRIPTION
+.B mconnect
+connects to
+.I port
+on
+.IR host .
+It sends its input to
+.IR host ,
+adding a CR to each line.
+Meanwhile it prints anything it receives from
+.IR host .
+
+If
+.I port
+is not supplied,
+.B mconnect
+uses port 25 (SMTP).
+
+If
+.I host
+is not supplied,
+.B mconnect
+connects to the local host.
+.SH "SEE ALSO"
+tcpclient(1)
diff --git a/old-rules.c b/old-rules.c
new file mode 100644
index 0000000..7225115
--- /dev/null
+++ b/old-rules.c
@@ -0,0 +1,101 @@
+#include "alloc.h"
+#include "stralloc.h"
+#include "open.h"
+#include "cdb.h"
+#include "rules.h"
+
+stralloc rules_name = {0};
+
+static struct cdb c;
+
+static int dorule(void (*callback)(char *,unsigned int))
+{
+  char *data;
+  unsigned int datalen;
+
+  switch(cdb_find(&c,rules_name.s,rules_name.len)) {
+    case -1: return -1;
+    case 0: return 0;
+  }
+
+  datalen = cdb_datalen(&c);
+  data = alloc(datalen);
+  if (!data) return -1;
+  if (cdb_read(&c,data,datalen,cdb_datapos(&c)) == -1) {
+    alloc_free(data);
+    return -1;
+  }
+
+  callback(data,datalen);
+  alloc_free(data);
+  return 1;
+}
+
+static int doit(void (*callback)(char *,unsigned int),char *ip,char *host,char *info)
+{
+  int r;
+
+  if (info) {
+    if (!stralloc_copys(&rules_name,info)) return -1;
+    if (!stralloc_cats(&rules_name,"@")) return -1;
+    if (!stralloc_cats(&rules_name,ip)) return -1;
+    r = dorule(callback);
+    if (r) return r;
+
+    if (host) {
+      if (!stralloc_copys(&rules_name,info)) return -1;
+      if (!stralloc_cats(&rules_name,"@=")) return -1;
+      if (!stralloc_cats(&rules_name,host)) return -1;
+      r = dorule(callback);
+      if (r) return r;
+    }
+  }
+
+  if (!stralloc_copys(&rules_name,ip)) return -1;
+  r = dorule(callback);
+  if (r) return r;
+
+  if (host) {
+    if (!stralloc_copys(&rules_name,"=")) return -1;
+    if (!stralloc_cats(&rules_name,host)) return -1;
+    r = dorule(callback);
+    if (r) return r;
+  }
+
+  if (!stralloc_copys(&rules_name,ip)) return -1;
+  while (rules_name.len > 0) {
+    if (ip[rules_name.len - 1] == '.' ||
+	(ip[rules_name.len-1]==':' && rules_name.len>1)) {
+      r = dorule(callback);
+      if (r) return r;
+    }
+    --rules_name.len;
+  }
+
+  if (host) {
+    while (*host) {
+      if (*host == '.') {
+        if (!stralloc_copys(&rules_name,"=")) return -1;
+        if (!stralloc_cats(&rules_name,host)) return -1;
+	r = dorule(callback);
+	if (r) return r;
+      }
+      ++host;
+    }
+    if (!stralloc_copys(&rules_name,"=")) return -1;
+    r = dorule(callback);
+    if (r) return r;
+  }
+
+  rules_name.len = 0;
+  return dorule(callback);
+}
+
+int rules(void (*callback)(char *,unsigned int),int fd,char *ip,char *host,char *info)
+{
+  int r;
+  cdb_init(&c,fd);
+  r = doit(callback,ip,host,info);
+  cdb_free(&c);
+  return r;
+}
diff --git a/pathexec.h b/pathexec.h
index 6fcbb89..bef93b4 100644
--- a/pathexec.h
+++ b/pathexec.h
@@ -2,7 +2,7 @@
 #define PATHEXEC_H
 
 extern void pathexec_run(char *,char **,char **);
-extern int pathexec_env(char *,char *);
+extern int pathexec_env(const char *,const char *);
 extern void pathexec(char **);
 
 #endif
diff --git a/pathexec_env.c b/pathexec_env.c
index 48bba7e..157e71b 100644
--- a/pathexec_env.c
+++ b/pathexec_env.c
@@ -8,7 +8,7 @@
 static stralloc plus;
 static stralloc tmp;
 
-int pathexec_env(char *s,char *t)
+int pathexec_env(const char *s,const char *t)
 {
   if (!s) return 1;
   if (!stralloc_copys(&tmp,s)) return 0;
@@ -22,7 +22,6 @@ int pathexec_env(char *s,char *t)
 
 void pathexec(char **argv)
 {
-  char *path;
   char **e;
   unsigned int elen;
   unsigned int i;
diff --git a/recordio.1 b/recordio.1
new file mode 100644
index 0000000..e056776
--- /dev/null
+++ b/recordio.1
@@ -0,0 +1,75 @@
+.TH recordio 1
+.SH NAME
+recordio \- record the input and output of a program
+.SH SYNTAX
+.B recordio
+.I program
+[
+.I arg ...
+]
+.SH DESCRIPTION
+.B recordio
+runs
+.I program
+with the given arguments.
+It prints lines to stderr
+showing the input and output of
+.IR program .
+
+At the beginning of each line on stderr,
+.B recordio
+inserts the
+.I program
+process ID,
+along with
+.B <
+for input or
+.B >
+for output.
+At the end of each line it inserts a space, a plus sign, or [EOF];
+a space indicates that there was a newline in the input or output,
+and [EOF] indicates the end of input or output.
+
+.B recordio
+prints every packet of input and output immediately.
+It does not attempt to combine packets into coherent stderr lines.
+For example,
+
+.EX
+     recordio sh -c 'cat /dev/fd/8 2>&1' > /dev/null
+.EE
+
+could produce
+
+.EX
+     5135 > cat: /dev/fd/8: Bad file descriptor 
+.br
+     5135 > [EOF]
+.EE
+
+or
+
+.EX
+     5135 > cat: +
+.br
+     5135 > /dev/fd/8+
+.br
+     5135 > : +
+.br
+     5135 > Bad file descriptor 
+.br
+     5135 > [EOF]
+.EE
+
+.B recordio
+uses several lines for long packets
+to guarantee that each line is printed atomically to stderr.
+
+.B recordio
+runs as a child of
+.IR program .
+It exits when it sees the end of
+.IR program 's
+output.
+.SH "SEE ALSO"
+tcpserver(1)
diff --git a/remoteinfo.h b/remoteinfo.h
index 2ca779d..0884cc1 100644
--- a/remoteinfo.h
+++ b/remoteinfo.h
@@ -5,5 +5,6 @@
 #include "uint16.h"
 
 extern int remoteinfo(stralloc *,char *,uint16,char *,uint16,unsigned int);
+extern int remoteinfo6(stralloc *,char *,uint16,char *,uint16,unsigned int,uint32);
 
 #endif
diff --git a/remoteinfo6.c b/remoteinfo6.c
new file mode 100644
index 0000000..cf3b7c1
--- /dev/null
+++ b/remoteinfo6.c
@@ -0,0 +1,98 @@
+#include "fmt.h"
+#include "buffer.h"
+#include "socket.h"
+#include "error.h"
+#include "iopause.h"
+#include "timeoutconn.h"
+#include "remoteinfo.h"
+
+static struct taia now;
+static struct taia deadline;
+
+static int mywrite(int fd,char *buf,int len)
+{
+  iopause_fd x;
+
+  x.fd = fd;
+  x.events = IOPAUSE_WRITE;
+  for (;;) {
+    taia_now(&now);
+    iopause(&x,1,&deadline,&now);
+    if (x.revents) break;
+    if (taia_less(&deadline,&now)) {
+      errno = error_timeout;
+      return -1;
+    }
+  }
+  return write(fd,buf,len);
+}
+
+static int myread(int fd,char *buf,int len)
+{
+  iopause_fd x;
+
+  x.fd = fd;
+  x.events = IOPAUSE_READ;
+  for (;;) {
+    taia_now(&now);
+    iopause(&x,1,&deadline,&now);
+    if (x.revents) break;
+    if (taia_less(&deadline,&now)) {
+      errno = error_timeout;
+      return -1;
+    }
+  }
+  return read(fd,buf,len);
+}
+
+static int doit(stralloc *out,int s,char ipremote[16],uint16 portremote,char iplocal[16],uint16 portlocal,unsigned int timeout,uint32 netif)
+{
+  buffer b;
+  char bspace[128];
+  char strnum[FMT_ULONG];
+  int numcolons;
+  char ch;
+
+  if (socket_bind6(s,iplocal,0,netif) == -1) return -1;
+  if (timeoutconn6(s,ipremote,113,timeout,netif) == -1) return -1;
+
+  buffer_init(&b,mywrite,s,bspace,sizeof bspace);
+  buffer_put(&b,strnum,fmt_ulong(strnum,portremote));
+  buffer_put(&b," , ",3);
+  buffer_put(&b,strnum,fmt_ulong(strnum,portlocal));
+  buffer_put(&b,"\r\n",2);
+  if (buffer_flush(&b) == -1) return -1;
+
+  buffer_init(&b,myread,s,bspace,sizeof bspace);
+  numcolons = 0;
+  for (;;) {
+    if (buffer_get(&b,&ch,1) != 1) return -1;
+    if ((ch == ' ') || (ch == '\t') || (ch == '\r')) continue;
+    if (ch == '\n') return 0;
+    if (numcolons < 3) {
+      if (ch == ':') ++numcolons;
+    }
+    else {
+      if (!stralloc_append(out,&ch)) return -1;
+      if (out->len > 256) return 0;
+    }
+  }
+}
+
+int remoteinfo6(stralloc *out,char ipremote[16],uint16 portremote,char iplocal[16],uint16 portlocal,unsigned int timeout,uint32 netif)
+{
+  int s;
+  int r;
+
+  if (!stralloc_copys(out,"")) return -1;
+
+  taia_now(&now);
+  taia_uint(&deadline,timeout);
+  taia_add(&deadline,&now,&deadline);
+
+  s = socket_tcp6();
+  if (s == -1) return -1;
+  r = doit(out,s,ipremote,portremote,iplocal,portlocal,timeout,netif);
+  close(s);
+  return r;
+}
diff --git a/rules.c b/rules.c
index 1840360..4fc2354 100644
--- a/rules.c
+++ b/rules.c
@@ -64,7 +64,7 @@ static int doit(void (*callback)(char *,unsigned int),char *ip,char *host,char *
 
   if (!stralloc_copys(&rules_name,ip)) return -1;
   while (rules_name.len > 0) {
-    if (ip[rules_name.len - 1] == '.') {
+    if (ip[rules_name.len - 1] == '.' || ip[rules_name.len - 1] == ':') {
       r = dorule(callback);
       if (r) return r;
     }
diff --git a/scan_ip6.c b/scan_ip6.c
new file mode 100644
index 0000000..ee239fd
--- /dev/null
+++ b/scan_ip6.c
@@ -0,0 +1,87 @@
+#include "scan.h"
+#include "ip4.h"
+#include "ip6.h"
+
+/*
+ * IPv6 addresses are really ugly to parse.
+ * Syntax: (h = hex digit)
+ *   1. hhhh:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh
+ *   2. any number of 0000 may be abbreviated as "::", but only once
+ *   3. The last two words may be written as IPv4 address
+ */
+
+unsigned int scan_ip6(const char *s,char ip[16])
+{
+  unsigned int i;
+  unsigned int len=0;
+  unsigned long u;
+
+  char suffix[16];
+  int prefixlen=0;
+  int suffixlen=0;
+
+  if ((i=ip4_scan((char*)s,ip+12))) {
+    for (len=0; len<12; ++len) ip[len]=V4mappedprefix[len];
+    return i;
+  }
+  for (i=0; i<16; i++) ip[i]=0;
+  for (;;) {
+    if (*s == ':') {
+      len++;
+      if (s[1] == ':') {	/* Found "::", skip to part 2 */
+	s+=2;
+	len++;
+	break;
+      }
+      s++;
+    }
+    i = scan_xlong((char*)s,&u);
+    if (!i) return 0;
+    if (prefixlen==12 && s[i]=='.') {
+      /* the last 4 bytes may be written as IPv4 address */
+      i=ip4_scan((char*)s,ip+12);
+      if (i)
+	return i+len;
+      else
+	return 0;
+    }
+    ip[prefixlen++] = (u >> 8);
+    ip[prefixlen++] = (u & 255);
+    s += i; len += i;
+    if (prefixlen==16)
+      return len;
+  }
+
+/* part 2, after "::" */
+  for (;;) {
+    if (*s == ':') {
+      if (suffixlen==0)
+	break;
+      s++;
+      len++;
+    } else if (suffixlen!=0)
+      break;
+    i = scan_xlong((char*)s,&u);
+    if (!i) {
+      len--;
+      break;
+    }
+    if (suffixlen+prefixlen<=12 && s[i]=='.') {
+      int j=ip4_scan((char*)s,suffix+suffixlen);
+      if (j) {
+	suffixlen+=4;
+	len+=j;
+	break;
+      } else
+	prefixlen=12-suffixlen;	/* make end-of-loop test true */
+    }
+    suffix[suffixlen++] = (u >> 8);
+    suffix[suffixlen++] = (u & 255);
+    s += i; len += i;
+    if (prefixlen+suffixlen==16)
+      break;
+  }
+  for (i=0; i<suffixlen; i++)
+    ip[16-suffixlen+i] = suffix[i];
+  return len;
+}
diff --git a/scan_xlong.c b/scan_xlong.c
new file mode 100644
index 0000000..6e46d74
--- /dev/null
+++ b/scan_xlong.c
@@ -0,0 +1,23 @@
+#include "scan.h"
+
+static int fromhex(unsigned char c) {
+  if (c>='0' && c<='9')
+    return c-'0';
+  else if (c>='A' && c<='F')
+    return c-'A'+10;
+  else if (c>='a' && c<='f')
+    return c-'a'+10;
+  return -1;
+}
+
+unsigned int scan_xlong(char *src,unsigned long *dest) {
+  register const char *tmp=src;
+  register int l=0;
+  register unsigned char c;
+  while ((c=fromhex(*tmp))<16) {
+    l=(l<<4)+c;
+    ++tmp;
+  }
+  *dest=l;
+  return tmp-src;
+}
diff --git a/socket.h b/socket.h
index 80fb260..4fba762 100644
--- a/socket.h
+++ b/socket.h
@@ -2,21 +2,52 @@
 #define SOCKET_H
 
 #include "uint16.h"
+#include "uint32.h"
 
 extern int socket_tcp(void);
 extern int socket_udp(void);
+extern int socket_tcp6(void);
+extern int socket_udp6(void);
 
-extern int socket_connect4(int,char *,uint16);
+extern int socket_connect4(int,const char *,uint16);
+extern int socket_connect6(int s,const char *ip,uint16 port,uint32 scope_id);
 extern int socket_connected(int);
-extern int socket_bind4(int,char *,uint16);
-extern int socket_bind4_reuse(int,char *,uint16);
+extern int socket_bind4(int,const char *,uint16);
+extern int socket_bind4_reuse(int,const char *,uint16);
+extern int socket_bind6(int s,const char *ip,uint16 port,uint32 scope_id);
+extern int socket_bind6_reuse(int s,const char *ip,uint16 port,uint32 scope_id);
 extern int socket_listen(int,int);
 extern int socket_accept4(int,char *,uint16 *);
+extern int socket_accept6(int s,char *ip,uint16 *port,uint32 *scope_id);
 extern int socket_recv4(int,char *,int,char *,uint16 *);
-extern int socket_send4(int,char *,int,char *,uint16);
+extern int socket_send4(int,const char *,int,const char *,uint16);
+extern int socket_recv6(int s,char *buf,unsigned int len,char *ip,uint16 *port,uint32 *scope_id);
+extern int socket_send6(int s,const char *buf,unsigned int len,const char *ip,uint16 port,uint32 scope_id);
 extern int socket_local4(int,char *,uint16 *);
 extern int socket_remote4(int,char *,uint16 *);
+extern int socket_local6(int s,char *ip,uint16 *port,uint32 *scope_id);
+extern int socket_remote6(int s,char *ip,uint16 *port,uint32 *scope_id);
+
+/* enable sending udp packets to the broadcast address */
+extern int socket_broadcast(int);
+/* join a multicast group on the given interface */
+extern int socket_mcjoin4(int,char *,char *);
+extern int socket_mcjoin6(int,char *,int);
+/* leave a multicast group on the given interface */
+extern int socket_mcleave4(int,char *);
+extern int socket_mcleave6(int,char *);
+/* set multicast TTL/hop count for outgoing packets */
+extern int socket_mcttl4(int,char);
+extern int socket_mcttl6(int,char);
+/* enable multicast loopback */
+extern int socket_mcloop4(int,char);
+extern int socket_mcloop6(int,char);
+
+extern const char* socket_getifname(uint32 interface);
+extern uint32 socket_getifidx(const char *ifname);
 
 extern void socket_tryreservein(int,int);
 
+extern int noipv6;
+
 #endif
diff --git a/socket_accept6.c b/socket_accept6.c
new file mode 100644
index 0000000..a8a9a07
--- /dev/null
+++ b/socket_accept6.c
@@ -0,0 +1,44 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "byte.h"
+#include "socket.h"
+#include "ip6.h"
+#include "haveip6.h"
+#include "error.h"
+
+int socket_accept6(int s,char ip[16],uint16 *port,uint32 *scope_id)
+{
+#ifdef LIBC_HAS_IP6
+  struct sockaddr_in6 sa;
+#else
+  struct sockaddr_in sa;
+#endif
+  unsigned int dummy = sizeof sa;
+  int fd;
+
+  fd = accept(s,(struct sockaddr *) &sa,&dummy);
+  if (fd == -1) return -1;
+
+#ifdef LIBC_HAS_IP6
+  if (sa.sin6_family==AF_INET) {
+    struct sockaddr_in *sa4=(struct sockaddr_in*)&sa;
+    byte_copy(ip,12,V4mappedprefix);
+    byte_copy(ip+12,4,(char *) &sa4->sin_addr);
+    uint16_unpack_big((char *) &sa4->sin_port,port);
+    return fd;
+  }
+  byte_copy(ip,16,(char *) &sa.sin6_addr);
+  uint16_unpack_big((char *) &sa.sin6_port,port);
+  if (scope_id) *scope_id=sa.sin6_scope_id;
+
+  return fd;
+#else
+  byte_copy(ip,12,V4mappedprefix);
+  byte_copy(ip+12,4,(char *) &sa.sin_addr);
+  uint16_unpack_big((char *) &sa.sin_port,port);
+  if (scope_id) *scope_id=0;
+  return fd;
+#endif
+}
diff --git a/socket_bind.c b/socket_bind.c
index 20830a4..067b4a8 100644
--- a/socket_bind.c
+++ b/socket_bind.c
@@ -5,7 +5,7 @@
 #include "byte.h"
 #include "socket.h"
 
-int socket_bind4(int s,char ip[4],uint16 port)
+int socket_bind4(int s,const char ip[4],uint16 port)
 {
   struct sockaddr_in sa;
 
@@ -17,7 +17,7 @@ int socket_bind4(int s,char ip[4],uint16 port)
   return bind(s,(struct sockaddr *) &sa,sizeof sa);
 }
 
-int socket_bind4_reuse(int s,char ip[4],uint16 port)
+int socket_bind4_reuse(int s,const char ip[4],uint16 port)
 {
   int opt = 1;
   setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof opt);
diff --git a/socket_bind6.c b/socket_bind6.c
new file mode 100644
index 0000000..8a5a7cd
--- /dev/null
+++ b/socket_bind6.c
@@ -0,0 +1,45 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "byte.h"
+#include "socket.h"
+#include "ip6.h"
+#include "haveip6.h"
+#include "error.h"
+
+int socket_bind6(int s,const char ip[16],uint16 port,uint32 scope_id)
+{
+#ifdef LIBC_HAS_IP6
+  struct sockaddr_in6 sa;
+
+  if (noipv6) {
+#endif
+    int i;
+    for (i=0; i<16; i++)
+      if (ip[i]!=0) break;
+    if (i==16 || ip6_isv4mapped(ip))
+      return socket_bind4(s,ip+12,port);
+#ifdef LIBC_HAS_IP6
+  }
+  byte_zero(&sa,sizeof sa);
+  sa.sin6_family = AF_INET6;
+  uint16_pack_big((char *) &sa.sin6_port,port);
+/*  implicit: sa.sin6_flowinfo = 0; */
+  byte_copy((char *) &sa.sin6_addr,16,ip);
+  sa.sin6_scope_id=scope_id;
+
+  return bind(s,(struct sockaddr *) &sa,sizeof sa);
+#else
+  errno=error_proto;
+  return -1;
+#endif
+}
+
+int socket_bind6_reuse(int s,const char ip[16],uint16 port,uint32 scope_id)
+{
+  int opt = 1;
+  setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof opt);
+  return socket_bind6(s,ip,port,scope_id);
+}
+
diff --git a/socket_conn.c b/socket_conn.c
index 35adac4..dcc93ac 100644
--- a/socket_conn.c
+++ b/socket_conn.c
@@ -6,7 +6,7 @@
 #include "byte.h"
 #include "socket.h"
 
-int socket_connect4(int s,char ip[4],uint16 port)
+int socket_connect4(int s,const char ip[4],uint16 port)
 {
   struct sockaddr_in sa;
 
diff --git a/socket_conn6.c b/socket_conn6.c
new file mode 100644
index 0000000..0ad886d
--- /dev/null
+++ b/socket_conn6.c
@@ -0,0 +1,38 @@
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include "byte.h"
+#include "socket.h"
+#include "ip6.h"
+#include "haveip6.h"
+#include "uint32.h"
+#include "ip4.h"
+
+int socket_connect6(int s,const char ip[16],uint16 port,uint32 scope_id)
+{
+#ifdef LIBC_HAS_IP6
+  struct sockaddr_in6 sa;
+
+  if (noipv6) {
+#endif
+    if (ip6_isv4mapped(ip))
+      return socket_connect4(s,ip+12,port);
+    if (byte_equal(ip,16,V6loopback))
+      return socket_connect4(s,ip4loopback,port);
+#ifdef LIBC_HAS_IP6
+  }
+  byte_zero(&sa,sizeof sa);
+  sa.sin6_family = PF_INET6;
+  uint16_pack_big((char *) &sa.sin6_port,port);
+  sa.sin6_flowinfo = 0;
+  sa.sin6_scope_id = scope_id;
+  byte_copy((char *) &sa.sin6_addr,16,ip);
+
+  return connect(s,(struct sockaddr *) &sa,sizeof sa);
+#else
+  errno=EPROTONOSUPPORT;
+  return -1;
+#endif
+}
diff --git a/socket_getifidx.c b/socket_getifidx.c
new file mode 100644
index 0000000..452d6d7
--- /dev/null
+++ b/socket_getifidx.c
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include "socket.h"
+
+uint32 socket_getifidx(const char* ifname) {
+  return if_nametoindex(ifname);
+}
diff --git a/socket_getifname.c b/socket_getifname.c
new file mode 100644
index 0000000..77edff9
--- /dev/null
+++ b/socket_getifname.c
@@ -0,0 +1,14 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include "socket.h"
+
+static char ifname[IFNAMSIZ];
+
+const char* socket_getifname(uint32 interface) {
+  char *tmp=if_indextoname(interface,ifname);
+  if (tmp)
+    return tmp;
+  else
+    return "[unknown]";
+}
diff --git a/socket_ip4loopback.c b/socket_ip4loopback.c
new file mode 100644
index 0000000..1bbbe95
--- /dev/null
+++ b/socket_ip4loopback.c
@@ -0,0 +1,2 @@
+
+const char ip4loopback[4] = {127,0,0,1};
diff --git a/socket_local6.c b/socket_local6.c
new file mode 100644
index 0000000..23427c3
--- /dev/null
+++ b/socket_local6.c
@@ -0,0 +1,39 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "byte.h"
+#include "socket.h"
+#include "ip6.h"
+#include "haveip6.h"
+#include "error.h"
+
+int socket_local6(int s,char ip[16],uint16 *port,uint32 *scope_id)
+{
+#ifdef LIBC_HAS_IP6
+  struct sockaddr_in6 sa;
+#else
+  struct sockaddr_in sa;
+#endif
+  unsigned int dummy = sizeof sa;
+
+  if (getsockname(s,(struct sockaddr *) &sa,&dummy) == -1) return -1;
+#ifdef LIBC_HAS_IP6
+  if (sa.sin6_family==AF_INET) {
+    struct sockaddr_in *sa4=(struct sockaddr_in*)&sa;
+    byte_copy(ip,12,V4mappedprefix);
+    byte_copy(ip+12,4,(char *) &sa4->sin_addr);
+    uint16_unpack_big((char *) &sa4->sin_port,port);
+    return 0;
+  }
+  byte_copy(ip,16,(char *) &sa.sin6_addr);
+  uint16_unpack_big((char *) &sa.sin6_port,port);
+  if (scope_id) *scope_id=sa.sin6_scope_id;
+#else
+  byte_copy(ip,12,V4mappedprefix);
+  byte_copy(ip+12,4,(char *) &sa.sin_addr);
+  uint16_unpack_big((char *) &sa.sin_port,port);
+  if (scope_id) *scope_id=0;
+#endif
+  return 0;
+}
diff --git a/socket_recv6.c b/socket_recv6.c
new file mode 100644
index 0000000..a86ca96
--- /dev/null
+++ b/socket_recv6.c
@@ -0,0 +1,44 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "byte.h"
+#include "socket.h"
+#include "ip6.h"
+#include "haveip6.h"
+#include "error.h"
+
+int socket_recv6(int s,char *buf,unsigned int len,char ip[16],uint16 *port,uint32 *scope_id)
+{
+#ifdef LIBC_HAS_IP6
+  struct sockaddr_in6 sa;
+#else
+  struct sockaddr_in sa;
+#endif
+  unsigned int dummy = sizeof sa;
+  int r;
+
+  byte_zero(&sa,dummy);
+  r = recvfrom(s,buf,len,0,(struct sockaddr *) &sa,&dummy);
+  if (r == -1) return -1;
+
+#ifdef LIBC_HAS_IP6
+  if (noipv6) {
+    struct sockaddr_in *sa4=(struct sockaddr_in *)&sa;
+    byte_copy(ip,12,V4mappedprefix);
+    byte_copy(ip+12,4,(char *) &sa4->sin_addr);
+    uint16_unpack_big((char *) &sa4->sin_port,port);
+    return r;
+  }
+  byte_copy(ip,16,(char *) &sa.sin6_addr);
+  uint16_unpack_big((char *) &sa.sin6_port,port);
+  if (scope_id) *scope_id=sa.sin6_scope_id;
+#else
+  byte_copy(ip,12,(char *)V4mappedprefix);
+  byte_copy(ip+12,4,(char *) &sa.sin_addr);
+  uint16_unpack_big((char *) &sa.sin_port,port);
+  if (scope_id) *scope_id=0;
+#endif
+
+  return r;
+}
diff --git a/socket_remote6.c b/socket_remote6.c
new file mode 100644
index 0000000..e60a539
--- /dev/null
+++ b/socket_remote6.c
@@ -0,0 +1,39 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "byte.h"
+#include "socket.h"
+#include "ip6.h"
+#include "haveip6.h"
+#include "error.h"
+
+int socket_remote6(int s,char ip[16],uint16 *port,uint32 *scope_id)
+{
+#ifdef LIBC_HAS_IP6
+  struct sockaddr_in6 sa;
+#else
+  struct sockaddr_in sa;
+#endif
+  unsigned int dummy = sizeof sa;
+
+  if (getpeername(s,(struct sockaddr *) &sa,&dummy) == -1) return -1;
+#ifdef LIBC_HAS_IP6
+  if (sa.sin6_family==AF_INET) {
+    struct sockaddr_in *sa4=(struct sockaddr_in*)&sa;
+    byte_copy(ip,12,V4mappedprefix);
+    byte_copy(ip+12,4,(char *) &sa4->sin_addr);
+    uint16_unpack_big((char *) &sa4->sin_port,port);
+    return 0;
+  }
+  byte_copy(ip,16,(char *) &sa.sin6_addr);
+  uint16_unpack_big((char *) &sa.sin6_port,port);
+  if (scope_id) *scope_id=sa.sin6_scope_id;
+#else
+  byte_copy(ip,12,V4mappedprefix);
+  byte_copy(ip+12,4,(char *) &sa.sin_addr);
+  uint16_unpack_big((char *) &sa.sin_port,port);
+  if (scope_id) *scope_id=0;
+#endif
+  return 0;
+}
diff --git a/socket_send6.c b/socket_send6.c
new file mode 100644
index 0000000..4b2d1e8
--- /dev/null
+++ b/socket_send6.c
@@ -0,0 +1,40 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "byte.h"
+#include "socket.h"
+#include "ip4.h"
+#include "ip6.h"
+#include "haveip6.h"
+#include "error.h"
+
+int socket_send6(int s,const char *buf,unsigned int len,const char ip[16],uint16 port,uint32 scope_id)
+{
+#ifdef LIBC_HAS_IP6
+  struct sockaddr_in6 sa;
+#else
+  struct sockaddr_in sa;
+#endif
+
+  byte_zero(&sa,sizeof sa);
+#ifdef LIBC_HAS_IP6
+  if (noipv6) {
+#endif
+    if (ip6_isv4mapped(ip))
+      return socket_send4(s,buf,len,ip+12,port);
+    if (byte_equal(ip,16,V6loopback))
+      return socket_send4(s,buf,len,ip4loopback,port);
+#ifdef LIBC_HAS_IP6
+    errno=error_proto;
+    return -1;
+  }
+  sa.sin6_family = AF_INET6;
+  uint16_pack_big((char *) &sa.sin6_port,port);
+  byte_copy((char *) &sa.sin6_addr,16,ip);
+  return sendto(s,buf,len,0,(struct sockaddr *) &sa,sizeof sa);
+#else
+  errno=error_proto;
+  return -1;
+#endif
+}
diff --git a/socket_tcp6.c b/socket_tcp6.c
new file mode 100644
index 0000000..74099e2
--- /dev/null
+++ b/socket_tcp6.c
@@ -0,0 +1,44 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include "ndelay.h"
+#include "socket.h"
+#include "haveip6.h"
+#include "error.h"
+
+#ifdef LIBC_HAS_IP6
+int noipv6=0;
+#else
+int noipv6=1;
+#endif
+
+int socket_tcp6(void)
+{
+#ifdef LIBC_HAS_IP6
+  int s;
+
+  if (noipv6) goto compat;
+  s = socket(PF_INET6,SOCK_STREAM,0);
+  if (s == -1) {
+    if (errno == EINVAL || errno == EAFNOSUPPORT) {
+compat:
+      s=socket(AF_INET,SOCK_STREAM,0);
+      noipv6=1;
+      if (s==-1) return -1;
+    } else
+    return -1;
+  }
+  if (ndelay_on(s) == -1) { close(s); return -1; }
+#ifdef IPV6_V6ONLY
+  {
+    int zero=0;
+    setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(void*)&zero,sizeof(zero));
+  }
+#endif
+  return s;
+#else
+  return socket_tcp();
+#endif
+}
diff --git a/socket_udp6.c b/socket_udp6.c
new file mode 100644
index 0000000..3769b1d
--- /dev/null
+++ b/socket_udp6.c
@@ -0,0 +1,38 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include "haveip6.h"
+#include "socket.h"
+
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT EINVAL
+#endif
+
+int socket_udp6(void)
+{
+#ifdef LIBC_HAS_IP6
+  int s;
+
+  if (noipv6) goto compat;
+  s = socket(PF_INET6,SOCK_DGRAM,0);
+  if (s == -1) {
+    if (errno == EINVAL || errno == EAFNOSUPPORT) {
+compat:
+      s=socket(AF_INET,SOCK_DGRAM,0);
+      noipv6=1;
+      if (s==-1) return -1;
+    } else
+    return -1;
+  }
+#ifdef IPV6_V6ONLY
+  {
+    int zero=0;
+    setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(void*)&zero,sizeof(zero));
+  }
+#endif
+  return s;
+#else
+  return socket_udp();
+#endif
+}
diff --git a/socket_v4mappedprefix.c b/socket_v4mappedprefix.c
new file mode 100644
index 0000000..dbed824
--- /dev/null
+++ b/socket_v4mappedprefix.c
@@ -0,0 +1,2 @@
+
+const unsigned char V4mappedprefix[12]={0,0,0,0,0,0,0,0,0,0,0xff,0xff};
diff --git a/socket_v6any.c b/socket_v6any.c
new file mode 100644
index 0000000..c6d0cbb
--- /dev/null
+++ b/socket_v6any.c
@@ -0,0 +1,2 @@
+
+const unsigned char V6any[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
diff --git a/socket_v6loopback.c b/socket_v6loopback.c
new file mode 100644
index 0000000..b81ee65
--- /dev/null
+++ b/socket_v6loopback.c
@@ -0,0 +1,2 @@
+
+const unsigned char V6loopback[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
diff --git a/str.h b/str.h
index ab4aedd..a2a4b75 100644
--- a/str.h
+++ b/str.h
@@ -1,13 +1,13 @@
 #ifndef STR_H
 #define STR_H
 
-extern unsigned int str_copy(char *,char *);
-extern int str_diff(char *,char *);
-extern int str_diffn(char *,char *,unsigned int);
-extern unsigned int str_len(char *);
-extern unsigned int str_chr(char *,int);
-extern unsigned int str_rchr(char *,int);
-extern int str_start(char *,char *);
+extern unsigned int str_copy(char *,const char *);
+extern int str_diff(const char *,const char *);
+extern int str_diffn(const char *,const char *,unsigned int);
+extern unsigned int str_len(const char *);
+extern unsigned int str_chr(const char *,int);
+extern unsigned int str_rchr(const char *,int);
+extern int str_start(const char *,const char *);
 
 #define str_equal(s,t) (!str_diff((s),(t)))
 
diff --git a/str_chr.c b/str_chr.c
index 886d6b6..042dfa2 100644
--- a/str_chr.c
+++ b/str_chr.c
@@ -1,9 +1,9 @@
 #include "str.h"
 
-unsigned int str_chr(register char *s,int c)
+unsigned int str_chr(register const char *s,int c)
 {
   register char ch;
-  register char *t;
+  register const char *t;
 
   ch = c;
   t = s;
diff --git a/str_diff.c b/str_diff.c
index 037dcdf..071e7f5 100644
--- a/str_diff.c
+++ b/str_diff.c
@@ -1,6 +1,6 @@
 #include "str.h"
 
-int str_diff(register char *s,register char *t)
+int str_diff(register const char *s,register const char *t)
 {
   register char x;
 
diff --git a/str_len.c b/str_len.c
index 5bd3f62..8411ebf 100644
--- a/str_len.c
+++ b/str_len.c
@@ -1,8 +1,8 @@
 #include "str.h"
 
-unsigned int str_len(char *s)
+unsigned int str_len(const char *s)
 {
-  register char *t;
+  register const char *t;
 
   t = s;
   for (;;) {
diff --git a/str_start.c b/str_start.c
index 43430bb..757189d 100644
--- a/str_start.c
+++ b/str_start.c
@@ -1,6 +1,6 @@
 #include "str.h"
 
-int str_start(register char *s,register char *t)
+int str_start(register const char *s,register const char *t)
 {
   register char x;
 
diff --git a/stralloc.h b/stralloc.h
index 7866812..cc17048 100644
--- a/stralloc.h
+++ b/stralloc.h
@@ -9,18 +9,20 @@ extern int stralloc_ready(stralloc *,unsigned int);
 extern int stralloc_readyplus(stralloc *,unsigned int);
 extern int stralloc_copy(stralloc *,stralloc *);
 extern int stralloc_cat(stralloc *,stralloc *);
-extern int stralloc_copys(stralloc *,char *);
-extern int stralloc_cats(stralloc *,char *);
-extern int stralloc_copyb(stralloc *,char *,unsigned int);
-extern int stralloc_catb(stralloc *,char *,unsigned int);
+extern int stralloc_copys(stralloc *,const char *);
+extern int stralloc_cats(stralloc *,const char *);
+extern int stralloc_copyb(stralloc *,const char *,unsigned int);
+extern int stralloc_catb(stralloc *,const char *,unsigned int);
 extern int stralloc_append(stralloc *,char *); /* beware: this takes a pointer to 1 char */
-extern int stralloc_starts(stralloc *,char *);
+extern int stralloc_starts(stralloc *,const char *);
 
 #define stralloc_0(sa) stralloc_append(sa,"")
 
 extern int stralloc_catulong0(stralloc *,unsigned long,unsigned int);
 extern int stralloc_catlong0(stralloc *,long,unsigned int);
 
+extern void stralloc_free(stralloc *);
+
 #define stralloc_catlong(sa,l) (stralloc_catlong0((sa),(l),0))
 #define stralloc_catuint0(sa,i,n) (stralloc_catulong0((sa),(i),(n)))
 #define stralloc_catint0(sa,i,n) (stralloc_catlong0((sa),(i),(n)))
diff --git a/stralloc_catb.c b/stralloc_catb.c
index b739bed..b606e32 100644
--- a/stralloc_catb.c
+++ b/stralloc_catb.c
@@ -1,7 +1,7 @@
 #include "stralloc.h"
 #include "byte.h"
 
-int stralloc_catb(stralloc *sa,char *s,unsigned int n)
+int stralloc_catb(stralloc *sa,const char *s,unsigned int n)
 {
   if (!sa->s) return stralloc_copyb(sa,s,n);
   if (!stralloc_readyplus(sa,n + 1)) return 0;
diff --git a/stralloc_cats.c b/stralloc_cats.c
index 8b11e94..92cb66e 100644
--- a/stralloc_cats.c
+++ b/stralloc_cats.c
@@ -2,7 +2,7 @@
 #include "str.h"
 #include "stralloc.h"
 
-int stralloc_cats(stralloc *sa,char *s)
+int stralloc_cats(stralloc *sa,const char *s)
 {
   return stralloc_catb(sa,s,str_len(s));
 }
diff --git a/stralloc_opyb.c b/stralloc_opyb.c
index 46b99fc..593029d 100644
--- a/stralloc_opyb.c
+++ b/stralloc_opyb.c
@@ -1,7 +1,7 @@
 #include "stralloc.h"
 #include "byte.h"
 
-int stralloc_copyb(stralloc *sa,char *s,unsigned int n)
+int stralloc_copyb(stralloc *sa,const char *s,unsigned int n)
 {
   if (!stralloc_ready(sa,n + 1)) return 0;
   byte_copy(sa->s,n,s);
diff --git a/stralloc_opys.c b/stralloc_opys.c
index 78594b0..860c7e0 100644
--- a/stralloc_opys.c
+++ b/stralloc_opys.c
@@ -2,7 +2,7 @@
 #include "str.h"
 #include "stralloc.h"
 
-int stralloc_copys(stralloc *sa,char *s)
+int stralloc_copys(stralloc *sa,const char *s)
 {
   return stralloc_copyb(sa,s,str_len(s));
 }
diff --git a/tcp-environ.5 b/tcp-environ.5
new file mode 100644
index 0000000..fecad70
--- /dev/null
+++ b/tcp-environ.5
@@ -0,0 +1,66 @@
+.TH tcp-environ 5
+.SH NAME
+tcp-environ \- TCP-related environment variables
+.SH DESCRIPTION
+The following environment variables
+describe a TCP connection.
+They are set up by
+.BR tcp-env ,
+.BR tcpclient ,
+and
+.BR tcpserver .
+Note that
+.BR TCPLOCALHOST ,
+.BR TCPREMOTEHOST ,
+and
+.B TCPREMOTEINFO
+can contain arbitrary characters.
+.TP 5
+PROTO
+The string
+.BR TCP .
+.TP 5
+TCPLOCALHOST
+The domain name of the local host,
+with uppercase letters converted to lowercase.
+If there is no currently available domain name
+for the local IP address,
+.B TCPLOCALHOST
+is not set.
+.TP 5
+TCPLOCALIP
+The IP address of the local host, in dotted-decimal form.
+.TP 5
+TCPLOCALPORT
+The local TCP port number, in decimal.
+.TP 5
+TCPREMOTEHOST
+The domain name of the remote host,
+with uppercase letters converted to lowercase.
+If there is no currently available domain name
+for the remote IP address,
+.B TCPREMOTEHOST
+is not set.
+.TP 5
+TCPREMOTEINFO
+A connection-specific string, perhaps a username,
+supplied by the remote host
+via 931/1413/IDENT/TAP.
+If the remote host did not supply connection information,
+.B TCPREMOTEINFO
+is not set.
+.TP 5
+TCPREMOTEIP
+The IP address of the remote host.
+.TP 5
+TCPREMOTEPORT
+The remote TCP port number.
+.TP 5
+TCPINTERFACE
+The interface name ("eth0") for IPv6 connections using link-local
+addresses.
+.SH "SEE ALSO"
+tcpclient(1),
+tcpserver(1),
+tcp-env(1),
+tcp(4)
diff --git a/tcpcat.1 b/tcpcat.1
new file mode 100644
index 0000000..4c51ed5
--- /dev/null
+++ b/tcpcat.1
@@ -0,0 +1,20 @@
+.TH tcpcat 1
+.SH NAME
+tcpcat \- print data from a TCP port
+.SH SYNTAX
+.B tcpcat
+.I host
+.I port
+.SH DESCRIPTION
+.B tcpcat
+connects to
+.I port
+on
+.I host
+and prints any data it receives.
+
+.B tcpcat
+can be used to transfer binary data.
+It does no conversions.
+.SH "SEE ALSO"
+tcpclient(1)
diff --git a/tcpclient.1 b/tcpclient.1
new file mode 100644
index 0000000..f82f6b3
--- /dev/null
+++ b/tcpclient.1
@@ -0,0 +1,173 @@
+.TH tcpclient 1
+.SH NAME
+tcpclient \- create an outgoing TCP connection
+.SH SYNOPSIS
+.B tcpclient
+[
+.B \-46hHrRdDqQv
+]
+[
+.B \-i\fIlocalip
+]
+[
+.B \-p\fIlocalport
+]
+[
+.B \-T\fItimeoutconn
+]
+[
+.B \-l\fIlocalname
+]
+[
+.B \-t\fItimeoutinfo
+]
+[
+.B \-I\fIinterface
+]
+.I host
+.I port
+.I program
+[
+.I arg ...
+]
+.SH DESCRIPTION
+.B tcpclient
+attempts to connect to a TCP server.
+If it is successful, it runs
+.I program
+with the given arguments,
+with descriptor 6 reading from the network
+and descriptor 7 writing to the network.
+
+The server's address is given by
+.I host
+and
+.IR port .
+.I host
+may be 0, referring to the local machine,
+or a dotted-decimal IP address,
+or a host name;
+if a host has several IP addresses,
+.B tcpclient
+tries each in turn.
+.I port
+may be a numeric port number
+or a port name.
+
+.B tcpclient
+sets up several environment variables,
+as described in
+.B tcp-environ(5).
+.SH OPTIONS
+.TP
+.B \-i\fIlocalip
+Use
+.I localip
+as the IP address for the local side of the connection;
+quit if
+.I localip
+is not available.
+.TP
+.B \-p\fIlocalport
+Use
+.I localport
+as the port number for the local side of the connection;
+quit if
+.I localport
+is not available.
+.TP
+.B \-I\fIinterface
+Use
+.I interface
+as the local network interface.  This is only defined for IPv6 sockets
+and needed if you use link-local IPv6 addresses.
+.TP
+.B \-T\fItimeoutconn
+Give up on the
+connection attempt
+after
+.I timeoutconn
+seconds. Default: 60.
+This timeout applies to each IP address tried.
+.TP
+.B \-d
+(Default.)
+Delay sending data for a fraction of a second whenever the
+remote host is responding slowly,
+to make better use of the network.
+.TP
+.B \-D
+Never delay sending data;
+enable TCP_NODELAY.
+This is appropriate for interactive connections.
+.TP
+.B \-q
+Quiet.
+Do not print any messages.
+.TP
+.B \-Q
+(Default.)
+Print error messages.
+.TP
+.B \-v
+Verbose.
+Print all available messages.
+.SH "DATA-GATHERING OPTIONS"
+.TP
+.B \-h
+(Default.)
+Look up the remote host name for
+.BR TCPREMOTEHOST .
+.TP
+.B \-H
+Do not look up the remote host name;
+unset
+.BR TCPREMOTEHOST .
+.TP
+.B \-l\fIlocalname
+Do not look up the local host name;
+use
+.I localname
+for
+.BR TCPLOCALHOST .
+.TP
+.B \-r
+(Default.)
+Attempt to obtain
+.B TCPREMOTEINFO
+from the remote host.
+.TP
+.B \-R
+Do not attempt to obtain
+.B TCPREMOTEINFO
+from the remote host.
+.TP
+.B \-t\fItimeoutinfo
+Give up on the 
+.B TCPREMOTEINFO
+connection attempt
+after
+.I timeoutinfo
+seconds. Default: 26.
+.TP
+.B \-4
+Fall back to IPv4 sockets.  This is necessary for terminally broken
+systems like OpenBSD which will not let IPv6 sockets connect to
+V4-mapped IPv6 addresses.  Please note that this also applies to DNS
+lookups, so you will have to use an DNS resolver with an IPv6 address to
+connect to IPv6 systems.  Use \fBDNSCACHEIP\fR to set the DNS resolver
+IP dynamically.
+.TP
+.B \-6
+Force IPv6 mode in UCSPI environment variables, even for
+IPv4 connections.  This will set \fB$PROTO\fR to \fBTCP6\fR and put
+IPv4-mapped IPv6 addresses in \fBTCPLOCALIP\fR and \fBTCPREMOTEIP\fR.
+.SH "SEE ALSO"
+date@(1),
+finger@(1),
+http@(1),
+mconnect(1),
+tcpcat(1),
+tcpserver(1),
+who@(1),
+tcp-environ(5)
diff --git a/tcpclient.c b/tcpclient.c
index 9f6d7f2..77b1ad5 100644
--- a/tcpclient.c
+++ b/tcpclient.c
@@ -9,6 +9,7 @@
 #include "scan.h"
 #include "str.h"
 #include "ip4.h"
+#include "ip6.h"
 #include "uint16.h"
 #include "socket.h"
 #include "fd.h"
@@ -20,6 +21,7 @@
 #include "timeoutconn.h"
 #include "remoteinfo.h"
 #include "dns.h"
+#include "byte.h"
 
 #define FATAL "tcpclient: fatal: "
 #define CONNECT "tcpclient: unable to connect to "
@@ -31,27 +33,30 @@ void nomem(void)
 void usage(void)
 {
   strerr_die1x(100,"tcpclient: usage: tcpclient \
-[ -hHrRdDqQv ] \
+[ -46hHrRdDqQv ] \
 [ -i localip ] \
 [ -p localport ] \
 [ -T timeoutconn ] \
 [ -l localname ] \
 [ -t timeoutinfo ] \
+[ -I interface ] \
 host port program");
 }
 
+int forcev6 = 0;
 int verbosity = 1;
 int flagdelay = 1;
 int flagremoteinfo = 1;
 int flagremotehost = 1;
 unsigned long itimeout = 26;
 unsigned long ctimeout[2] = { 2, 58 };
+uint32 netif = 0;
 
-char iplocal[4] = { 0,0,0,0 };
+char iplocal[16] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 };
 uint16 portlocal = 0;
 char *forcelocal = 0;
 
-char ipremote[4];
+char ipremote[16];
 uint16 portremote;
 
 char *hostname;
@@ -61,12 +66,13 @@ static stralloc moreaddresses;
 static stralloc tmp;
 static stralloc fqdn;
 char strnum[FMT_ULONG];
-char ipstr[IP4_FMT];
+char ipstr[IP6_FMT];
 
 char seed[128];
 
 main(int argc,char **argv)
 {
+  int fakev4=0;
   unsigned long u;
   int opt;
   char *x;
@@ -80,8 +86,10 @@ main(int argc,char **argv)
   close(7);
   sig_ignore(sig_pipe);
  
-  while ((opt = getopt(argc,argv,"dDvqQhHrRi:p:t:T:l:")) != opteof)
+  while ((opt = getopt(argc,argv,"46dDvqQhHrRi:p:t:T:l:I:")) != opteof)
     switch(opt) {
+      case '4': noipv6 = 1; break;
+      case '6': forcev6 = 1; break;
       case 'd': flagdelay = 1; break;
       case 'D': flagdelay = 0; break;
       case 'v': verbosity = 2; break;
@@ -97,7 +105,8 @@ main(int argc,char **argv)
 		if (optarg[j] == '+') ++j;
 		scan_ulong(optarg + j,&ctimeout[1]);
 		break;
-      case 'i': if (!ip4_scan(optarg,iplocal)) usage(); break;
+      case 'i': if (!scan_ip6(optarg,iplocal)) usage(); break;
+      case 'I': netif=socket_getifidx(optarg); break;
       case 'p': scan_ulong(optarg,&u); portlocal = u; break;
       default: usage();
     }
@@ -108,8 +117,8 @@ main(int argc,char **argv)
 
   hostname = *argv;
   if (!hostname) usage();
-  if (str_equal(hostname,"")) hostname = "127.0.0.1";
-  if (str_equal(hostname,"0")) hostname = "127.0.0.1";
+  if (!hostname[0] || str_equal(hostname,"0"))
+    hostname = (noipv6?"127.0.0.1":"::1");
 
   x = *++argv;
   if (!x) usage();
@@ -127,33 +136,36 @@ main(int argc,char **argv)
   if (!*++argv) usage();
 
   if (!stralloc_copys(&tmp,hostname)) nomem();
-  if (dns_ip4_qualify(&addresses,&fqdn,&tmp) == -1)
+  if (dns_ip6_qualify(&addresses,&fqdn,&tmp) == -1)
     strerr_die4sys(111,FATAL,"temporarily unable to figure out IP address for ",hostname,": ");
-  if (addresses.len < 4)
+  if (addresses.len < 16)
     strerr_die3x(111,FATAL,"no IP address for ",hostname);
 
-  if (addresses.len == 4) {
+  if (addresses.len == 16) {
     ctimeout[0] += ctimeout[1];
     ctimeout[1] = 0;
   }
 
   for (cloop = 0;cloop < 2;++cloop) {
     if (!stralloc_copys(&moreaddresses,"")) nomem();
-    for (j = 0;j + 4 <= addresses.len;j += 4) {
-      s = socket_tcp();
+    for (j = 0;j + 16 <= addresses.len;j += 4) {
+      s = socket_tcp6();
       if (s == -1)
         strerr_die2sys(111,FATAL,"unable to create socket: ");
-      if (socket_bind4(s,iplocal,portlocal) == -1)
+      if (socket_bind6(s,iplocal,portlocal,netif) == -1)
         strerr_die2sys(111,FATAL,"unable to bind socket: ");
-      if (timeoutconn(s,addresses.s + j,portremote,ctimeout[cloop]) == 0)
+      if (timeoutconn6(s,addresses.s + j,portremote,ctimeout[cloop],netif) == 0)
         goto CONNECTED;
       close(s);
       if (!cloop && ctimeout[1] && (errno == error_timeout)) {
-	if (!stralloc_catb(&moreaddresses,addresses.s + j,4)) nomem();
+	if (!stralloc_catb(&moreaddresses,addresses.s + j,16)) nomem();
       }
       else {
         strnum[fmt_ulong(strnum,portremote)] = 0;
-        ipstr[ip4_fmt(ipstr,addresses.s + j)] = 0;
+	if (ip6_isv4mapped(addresses.s+j))
+	  ipstr[ip4_fmt(ipstr,addresses.s + j + 12)] = 0;
+	else
+	  ipstr[ip6_fmt(ipstr,addresses.s + j)] = 0;
         strerr_warn5(CONNECT,ipstr," port ",strnum,": ",&strerr_sys);
       }
     }
@@ -169,37 +181,46 @@ main(int argc,char **argv)
   if (!flagdelay)
     socket_tcpnodelay(s); /* if it fails, bummer */
 
-  if (!pathexec_env("PROTO","TCP")) nomem();
-
-  if (socket_local4(s,iplocal,&portlocal) == -1)
+  if (socket_local6(s,iplocal,&portlocal,&netif) == -1)
     strerr_die2sys(111,FATAL,"unable to get local address: ");
 
+  if (!forcev6 && (ip6_isv4mapped(iplocal) || byte_equal(iplocal,16,V6any)))
+    fakev4=1;
+
+  if (!pathexec_env("PROTO",fakev4?"TCP":"TCP6")) nomem();
+
   strnum[fmt_ulong(strnum,portlocal)] = 0;
   if (!pathexec_env("TCPLOCALPORT",strnum)) nomem();
-  ipstr[ip4_fmt(ipstr,iplocal)] = 0;
+  if (fakev4)
+    ipstr[ip4_fmt(ipstr,iplocal+12)] = 0;
+  else
+    ipstr[ip6_fmt(ipstr,iplocal)] = 0;
   if (!pathexec_env("TCPLOCALIP",ipstr)) nomem();
 
   x = forcelocal;
   if (!x)
-    if (dns_name4(&tmp,iplocal) == 0) {
+    if (dns_name6(&tmp,iplocal) == 0) {
       if (!stralloc_0(&tmp)) nomem();
       x = tmp.s;
     }
   if (!pathexec_env("TCPLOCALHOST",x)) nomem();
 
-  if (socket_remote4(s,ipremote,&portremote) == -1)
+  if (socket_remote6(s,ipremote,&portremote,&netif) == -1)
     strerr_die2sys(111,FATAL,"unable to get remote address: ");
 
   strnum[fmt_ulong(strnum,portremote)] = 0;
   if (!pathexec_env("TCPREMOTEPORT",strnum)) nomem();
-  ipstr[ip4_fmt(ipstr,ipremote)] = 0;
+  if (fakev4)
+    ipstr[ip4_fmt(ipstr,ipremote+12)] = 0;
+  else
+    ipstr[ip6_fmt(ipstr,ipremote)] = 0;
   if (!pathexec_env("TCPREMOTEIP",ipstr)) nomem();
   if (verbosity >= 2)
     strerr_warn4("tcpclient: connected to ",ipstr," port ",strnum,0);
 
   x = 0;
   if (flagremotehost)
-    if (dns_name4(&tmp,ipremote) == 0) {
+    if (dns_name6(&tmp,ipremote) == 0) {
       if (!stralloc_0(&tmp)) nomem();
       x = tmp.s;
     }
@@ -207,7 +228,7 @@ main(int argc,char **argv)
 
   x = 0;
   if (flagremoteinfo)
-    if (remoteinfo(&tmp,ipremote,portremote,iplocal,portlocal,itimeout) == 0) {
+    if (remoteinfo6(&tmp,ipremote,portremote,iplocal,portlocal,itimeout,netif) == 0) {
       if (!stralloc_0(&tmp)) nomem();
       x = tmp.s;
     }
diff --git a/tcprules.1 b/tcprules.1
new file mode 100644
index 0000000..084165b
--- /dev/null
+++ b/tcprules.1
@@ -0,0 +1,221 @@
+.TH tcprules 1
+.SH NAME
+tcprules \- compile rules for tcpserver
+.SH SYNOPSIS
+.B tcprules
+.I rules.cdb
+.I rules.tmp
+.SH OVERVIEW
+.B tcpserver
+optionally follows rules to decide whether a TCP connection is acceptable.
+For example, a rule of
+
+.EX
+   18.23.0.32:deny
+.EE
+
+prohibits connections from IP address 18.23.0.32.
+
+.B tcprules
+reads rules from its standard input
+and writes them into
+.I rules.cdb
+in a binary format suited
+for quick access by
+.BR tcpserver .
+
+.B tcprules
+can be used while
+.B tcpserver
+is running:
+it ensures that
+.I rules.cdb
+is updated atomically.
+It does this by first writing the rules to
+.I rules.tmp
+and then moving
+.I rules.tmp
+on top of
+.IR rules.cdb .
+If
+.I rules.tmp
+already exists, it is destroyed.
+The directories containing
+.I rules.cdb
+and
+.I rules.tmp
+must be writable to
+.BR tcprules ;
+they must also be on the same filesystem.
+
+If there is a problem with the input,
+.B tcprules
+complains and leaves
+.I rules.cdb
+alone.
+
+The binary
+.I rules.cdb
+format is portable across machines.
+.SH "RULE FORMAT"
+A rule takes up one line.
+A file containing rules
+may also contain comments: lines beginning with # are ignored.
+
+Each rule contains an
+.BR address ,
+a colon,
+and a list of
+.BR instructions ,
+with no extra spaces.
+When
+.B tcpserver
+receives a connection from that address,
+it follows the instructions.
+.SH "ADDRESSES"
+.B tcpserver
+starts by looking for a rule with address
+.IR TCPREMOTEINFO\fB@\fITCPREMOTEIP .
+If it doesn't find one, or if
+.I TCPREMOTEINFO
+is not set, it tries the address
+.IR TCPREMOTEIP .
+If that doesn't work, it tries shorter and shorter prefixes of
+.I TCPREMOTEIP
+ending with a dot.
+If none of them work, it tries the empty string.
+
+For example, here are some rules:
+
+.EX
+   joe@127.0.0.1:first
+.br
+   18.23.0.32:second
+.br
+   127.:third
+.br
+   :fourth
+.br
+   ::1:fifth
+.EE
+
+If
+.I TCPREMOTEIP
+is
+.BR 10.119.75.38 ,
+.B tcpserver
+will follow the
+.B fourth
+instructions.
+
+If
+.I TCPREMOTEIP
+is
+.BR ::1 ,
+.B tcpserver
+will follow the
+.B fifth
+instructions.  Note that you cannot detect IPv4 mapped addresses by
+matching "::ffff", as those addresses will be converted to IPv4 before
+looking at the rules.
+
+If
+.I TCPREMOTEIP
+is
+.BR 18.23.0.32 ,
+.B tcpserver
+will follow the
+.B second
+instructions.
+
+If
+.I TCPREMOTEINFO
+is
+.B bill
+and
+.I TCPREMOTEIP
+is
+.BR 127.0.0.1 ,
+.B tcpserver
+will follow the
+.B third
+instructions.
+
+If
+.I TCPREMOTEINFO
+is
+.B joe
+and
+.I TCPREMOTEIP
+is
+.BR 127.0.0.1 ,
+.B tcpserver
+will follow the
+.B first
+instructions.
+.SH "ADDRESS RANGES"
+.B tcprules
+treats
+.B 1.2.3.37-53:ins
+as an abbreviation
+for the rules
+.BR 1.2.3.37:ins ,
+.BR 1.2.3.38:ins ,
+and so on up through
+.BR 1.2.3.53:ins .
+Similarly,
+.BR 10.2-3.:ins
+is an abbreviation for
+.B 10.2.:ins
+and
+.BR 10.3.:ins .
+.SH "INSTRUCTIONS"
+The instructions in a rule must begin with either
+.B allow
+or
+.BR deny .
+.B deny
+tells
+.B tcpserver
+to drop the connection without running anything.
+For example, the rule
+
+.EX
+   :deny
+.EE
+
+tells
+.B tcpserver
+to drop all connections that aren't handled by more specific rules.
+
+The instructions may continue with some environment variables,
+in the format
+.IR ,VAR="VALUE" .
+.B tcpserver
+adds
+.I VAR=VALUE
+to the current environment.
+For example,
+
+.EX
+   10.0.:allow,RELAYCLIENT="@fix.me"
+.EE
+
+adds
+.B RELAYCLIENT=@fix.me
+to the environment.
+The quotes here may be replaced by any repeated character:
+
+.EX
+   10.0.:allow,RELAYCLIENT=/@fix.me/
+.EE
+
+Any number of variables may be listed:
+
+.EX
+   127.0.0.1:allow,RELAYCLIENT="",TCPLOCALHOST="movie.edu"
+.EE
+.SH "SEE ALSO"
+tcprulescheck(1),
+tcpserver(1),
+tcp-environ(5)
diff --git a/tcprules.c b/tcprules.c
index a684ac5..83519c8 100644
--- a/tcprules.c
+++ b/tcprules.c
@@ -123,8 +123,15 @@ main(int argc,char **argv)
     }
     line.len = len; /* for die_bad() */
 
-    colon = byte_chr(x,len,':');
-    if (colon == len) continue;
+    colon = 0;
+    for (;;) {
+      int tmp;
+      tmp = byte_chr(x + colon,len - colon,':');
+      colon += tmp;
+      if (colon == len) continue;
+      if (byte_equal(x+colon+1,4,"deny") || byte_equal(x+colon+1,5,"allow")) break;
+      ++colon;
+    }
 
     if (!stralloc_copyb(&address,x,colon)) nomem();
     if (!stralloc_copys(&data,"")) nomem();
diff --git a/tcprulescheck.1 b/tcprulescheck.1
new file mode 100644
index 0000000..3f0de24
--- /dev/null
+++ b/tcprulescheck.1
@@ -0,0 +1,25 @@
+.TH tcprulescheck 1
+.SH NAME
+tcprulescheck \- try out rules for tcpserver
+.SH SYNTAX
+.B tcprulescheck
+.I rules.cdb
+.I tcpremoteip
+[
+.I tcpremoteinfo
+]
+.SH DESCRIPTION
+.B tcprulescheck
+says what
+.B tcpserver
+will do with a connection from
+IP address
+.IR tcpremoteip ,
+following the rules compiled into
+.I rules.cdb
+by
+.BR tcprules .
+.SH "SEE ALSO"
+tcprules(1),
+tcpserver(1),
+tcp-environ(5)
diff --git a/tcpserver.1 b/tcpserver.1
new file mode 100644
index 0000000..72c5ca0
--- /dev/null
+++ b/tcpserver.1
@@ -0,0 +1,266 @@
+.TH tcpserver 1
+.SH NAME
+tcpserver \- accept incoming TCP connections
+.SH SYNOPSIS
+.B tcpserver
+[
+.B \-146jpPhHrRoOdDqQv
+]
+[
+.B \-c\fIlimit
+]
+[
+.B \-x\fIrules.cdb
+]
+[
+.B \-B\fIbanner
+]
+[
+.B \-g\fIgid
+]
+[
+.B \-u\fIuid
+]
+[
+.B \-b\fIbacklog
+]
+[
+.B \-l\fIlocalname
+]
+[
+.B \-t\fItimeout
+]
+[
+.B \-I\fIinterface
+]
+.I host
+.I port
+.I program
+[
+.I arg ...
+]
+.SH DESCRIPTION
+.B tcpserver
+waits for connections from TCP clients.
+For each connection, it runs
+.I program
+with the given arguments,
+with descriptor 0 reading from the network
+and descriptor 1 writing to the network.
+
+The server's address is given by
+.I host
+and
+.IR port .
+.I host
+can be 0, allowing connections from any host;
+or a particular IP address,
+allowing connections only to that address;
+or a host name, allowing connections to the first IP address
+for that host.
+.I port
+may be a numeric port number
+or a port name.
+If
+.I port
+is 0,
+.B tcpserver
+will choose a free port.
+
+.B tcpserver
+sets up several environment variables,
+as described in
+.B tcp-environ(5).
+
+.B tcpserver
+exits when it receives SIGTERM.
+.SH "OPTIONS"
+.TP
+.B \-c\fIlimit
+Do not handle more than
+.I limit
+simultaneous connections.
+If there are
+.I limit
+simultaneous copies of
+.I program
+running, defer acceptance of a new connection
+until one copy finishes.
+.I limit
+must be a positive integer.
+Default: 40.
+.TP
+.B \-x\fIrules.cdb
+Follow the rules compiled into
+.I rules.cdb
+by
+.BR tcprules .
+These rules may specify setting environment variables
+or rejecting connections from bad sources.
+
+.B tcpserver
+does not read
+.I rules.cdb
+into memory;
+you can rerun
+.B tcprules
+to change
+.BR tcpserver 's
+behavior on the fly.
+.TP
+.B \-B\fIbanner
+Write
+.I banner
+to the network immediately after each connection is made.
+.B tcpserver
+writes
+.I banner
+before looking up
+.BR TCPREMOTEHOST ,
+before looking up
+.BR TCPREMOTEINFO ,
+and before checking
+.IR rules.cdb .
+
+This feature can be used to reduce latency in protocols
+where the client waits for a greeting from the server.
+.TP
+.B \-g\fIgid
+Switch group ID to
+.I gid
+after preparing to receive connections.
+.I gid
+must be a positive integer.
+.TP
+.B \-u\fIuid
+Switch user ID to 
+.I uid
+after preparing to receive connections.
+.I uid
+must be a positive integer.
+.TP
+.B \-1
+After preparing to receive connections,
+print the local port number to standard output.
+.TP
+.B \-4
+Fall back to IPv4 sockets.  This is necessary for terminally broken
+systems like OpenBSD which will not let IPv6 sockets connect to
+V4-mapped IPv6 addresses.  Please note that this also applies to DNS
+lookups, so you will have to use an DNS resolver with an IPv6 address to
+accept IPv6 connections.  Use \fBDNSCACHEIP\fR to set the DNS resolver
+IP dynamically.
+.TP
+.B \-6
+Force IPv6 mode in UCSPI environment variables, even for
+IPv4 connections.  This will set \fB$PROTO\fR to \fBTCP6\fR and put
+IPv4-mapped IPv6 addresses in \fBTCPLOCALIP\fR and \fBTCPREMOTEIP\fR.
+.TP
+.B \-I\fIinterface
+Bind to the network interface
+.I interface
+("eth0" on Linux, for example).  This is only defined and needed for
+IPv6 link-local addresses.
+.TP
+.B \-b\fIbacklog
+Allow up to
+.I backlog
+simultaneous SYN_RECEIVEDs.
+Default: 20.
+On some systems,
+.I backlog
+is silently limited to 5.
+See
+.BR listen (2)
+for more details.
+.TP
+.B \-o
+Leave IP options alone.
+If the client is sending packets along an IP source route,
+send packets back along the same route.
+.TP
+.B \-O
+(Default.)
+Kill IP options.
+A client can still use source routing to connect and to send data,
+but packets will be sent back along the default route.
+.TP
+.B \-d
+(Default.)
+Delay sending data for a fraction of a second whenever the
+remote host is responding slowly,
+to make better use of the network.
+.TP
+.B \-D
+Never delay sending data;
+enable TCP_NODELAY.
+This is appropriate for interactive connections.
+.TP
+.B \-q
+Quiet.
+Do not print any messages.
+.TP
+.B \-Q
+(Default.)
+Print error messages.
+.TP
+.B \-v
+Verbose.
+Print all available messages.
+.SH "DATA-GATHERING OPTIONS"
+.TP
+.B \-p
+Paranoid.
+After looking up the remote host name,
+look up the IP addresses for that name,
+and make sure one of them matches
+.BR TCPREMOTEIP .
+If none of them do,
+unset
+.BR TCPREMOTEHOST .
+.TP
+.B \-P
+(Default.)
+Not paranoid.
+.TP
+.B \-h
+(Default.)
+Look up the remote host name and set
+.BR TCPREMOTEHOST .
+.TP
+.B \-H
+Do not look up the remote host name.
+.TP
+.B \-l\fIlocalname
+Do not look up the local host name;
+use
+.I localname
+for
+.BR TCPLOCALHOST .
+.TP
+.B \-r
+(Default.)
+Attempt to obtain
+.B TCPREMOTEINFO
+from the remote host.
+.TP
+.B \-R
+Do not attempt to obtain
+.B TCPREMOTEINFO
+from the remote host.
+.TP
+.B \-t\fItimeout
+Give up on the 
+.B TCPREMOTEINFO
+connection attempt
+after
+.I timeout
+seconds. Default: 26.
+.SH "SEE ALSO"
+argv0(1),
+fixcr(1),
+recordio(1),
+tcpclient(1),
+tcprules(1),
+listen(2),
+tcp-environ(5)
diff --git a/tcpserver.c b/tcpserver.c
index 979a0be..aab637f 100644
--- a/tcpserver.c
+++ b/tcpserver.c
@@ -7,6 +7,7 @@
 #include "fmt.h"
 #include "scan.h"
 #include "ip4.h"
+#include "ip6.h"
 #include "fd.h"
 #include "exit.h"
 #include "env.h"
@@ -28,6 +29,7 @@
 #include "sig.h"
 #include "dns.h"
 
+int forcev6 = 0;
 int verbosity = 1;
 int flagkillopts = 1;
 int flagdelay = 1;
@@ -36,20 +38,21 @@ int flagremoteinfo = 1;
 int flagremotehost = 1;
 int flagparanoid = 0;
 unsigned long timeout = 26;
+uint32 netif = 0;
 
 static stralloc tcpremoteinfo;
 
 uint16 localport;
 char localportstr[FMT_ULONG];
-char localip[4];
-char localipstr[IP4_FMT];
+char localip[16];
+char localipstr[IP6_FMT];
 static stralloc localhostsa;
 char *localhost = 0;
 
 uint16 remoteport;
 char remoteportstr[FMT_ULONG];
-char remoteip[4];
-char remoteipstr[IP4_FMT];
+char remoteip[16];
+char remoteipstr[IP6_FMT];
 static stralloc remotehostsa;
 char *remotehost = 0;
 
@@ -96,12 +99,12 @@ void safecats(char *s)
     if (ch < 33) ch = '?';
     if (ch > 126) ch = '?';
     if (ch == '%') ch = '?'; /* logger stupidity */
-    if (ch == ':') ch = '?';
+/*    if (ch == ':') ch = '?'; */
     append(&ch);
   }
   cats("...");
 }
-void env(char *s,char *t)
+void env(const char *s,const char *t)
 {
   if (!pathexec_env(s,t)) drop_nomem();
 }
@@ -135,9 +138,16 @@ void found(char *data,unsigned int datalen)
 
 void doit(int t)
 {
+  int fakev4=0;
   int j;
+  uint32 scope_id;
 
-  remoteipstr[ip4_fmt(remoteipstr,remoteip)] = 0;
+  if (!forcev6 && ip6_isv4mapped(remoteip))
+    fakev4=1;
+  if (fakev4)
+    remoteipstr[ip4_fmt(remoteipstr,remoteip+12)] = 0;
+  else
+    remoteipstr[ip6_fmt(remoteipstr,remoteip)] = 0;
 
   if (verbosity >= 2) {
     strnum[fmt_ulong(strnum,getpid())] = 0;
@@ -155,30 +165,40 @@ void doit(int t)
       strerr_die2sys(111,DROP,"unable to print banner: ");
   }
 
-  if (socket_local4(t,localip,&localport) == -1)
+  if (socket_local6(t,localip,&localport,&scope_id) == -1)
     strerr_die2sys(111,DROP,"unable to get local address: ");
 
-  localipstr[ip4_fmt(localipstr,localip)] = 0;
+  if (fakev4)
+    localipstr[ip4_fmt(localipstr,localip+12)] = 0;
+  else
+    localipstr[ip6_fmt(localipstr,localip)] = 0;
   remoteportstr[fmt_ulong(remoteportstr,remoteport)] = 0;
 
   if (!localhost)
-    if (dns_name4(&localhostsa,localip) == 0)
+    if (dns_name6(&localhostsa,localip) == 0)
       if (localhostsa.len) {
 	if (!stralloc_0(&localhostsa)) drop_nomem();
 	localhost = localhostsa.s;
       }
-  env("PROTO","TCP");
+  env("PROTO",fakev4?"TCP":"TCP6");
   env("TCPLOCALIP",localipstr);
+  localipstr[ip6_fmt(localipstr,localip)]=0;
+  env("TCP6LOCALIP",localipstr);
+
   env("TCPLOCALPORT",localportstr);
+  env("TCP6LOCALPORT",localportstr);
   env("TCPLOCALHOST",localhost);
+  env("TCP6LOCALHOST",localhost);
+  if (!fakev4 && scope_id)
+    env("TCP6INTERFACE",socket_getifname(scope_id));
 
   if (flagremotehost)
-    if (dns_name4(&remotehostsa,remoteip) == 0)
+    if (dns_name6(&remotehostsa,remoteip) == 0)
       if (remotehostsa.len) {
 	if (flagparanoid)
-	  if (dns_ip4(&tmp,&remotehostsa) == 0)
-	    for (j = 0;j + 4 <= tmp.len;j += 4)
-	      if (byte_equal(remoteip,4,tmp.s + j)) {
+	  if (dns_ip6(&tmp,&remotehostsa) == 0)
+	    for (j = 0;j + 16 <= tmp.len;j += 16)
+	      if (byte_equal(remoteip,16,tmp.s + j)) {
 		flagparanoid = 0;
 		break;
 	      }
@@ -188,15 +208,20 @@ void doit(int t)
 	}
       }
   env("TCPREMOTEIP",remoteipstr);
+  remoteipstr[ip6_fmt(remoteipstr,remoteip)]=0;
+  env("TCP6REMOTEIP",remoteipstr);
   env("TCPREMOTEPORT",remoteportstr);
+  env("TCP6REMOTEPORT",remoteportstr);
   env("TCPREMOTEHOST",remotehost);
+  env("TCP6REMOTEHOST",remotehost);
 
   if (flagremoteinfo) {
-    if (remoteinfo(&tcpremoteinfo,remoteip,remoteport,localip,localport,timeout) == -1)
+    if (remoteinfo6(&tcpremoteinfo,remoteip,remoteport,localip,localport,timeout,netif) == -1)
       flagremoteinfo = 0;
     if (!stralloc_0(&tcpremoteinfo)) drop_nomem();
   }
   env("TCPREMOTEINFO",flagremoteinfo ? tcpremoteinfo.s : 0);
+  env("TCP6REMOTEINFO",flagremoteinfo ? tcpremoteinfo.s : 0);
 
   if (fnrules) {
     int fdrules;
@@ -206,7 +231,15 @@ void doit(int t)
       if (!flagallownorules) drop_rules();
     }
     else {
-      if (rules(found,fdrules,remoteipstr,remotehost,flagremoteinfo ? tcpremoteinfo.s : 0) == -1) drop_rules();
+      int fakev4=0;
+      char* temp;
+      if (!forcev6 && ip6_isv4mapped(remoteip))
+	fakev4=1;
+      if (fakev4)
+	temp=remoteipstr+7;
+      else
+	temp=remoteipstr;
+      if (rules(found,fdrules,temp,remotehost,flagremoteinfo ? tcpremoteinfo.s : 0) == -1) drop_rules();
       close(fdrules);
     }
   }
@@ -240,7 +273,7 @@ void usage(void)
 {
   strerr_warn1("\
 tcpserver: usage: tcpserver \
-[ -1UXpPhHrRoOdDqQv ] \
+[ -461UXpPhHrRoOdDqQv ] \
 [ -c limit ] \
 [ -x rules.cdb ] \
 [ -B banner ] \
@@ -249,6 +282,7 @@ tcpserver: usage: tcpserver \
 [ -b backlog ] \
 [ -l localname ] \
 [ -t timeout ] \
+[ -I interface ] \
 host port program",0);
   _exit(100);
 }
@@ -299,8 +333,8 @@ main(int argc,char **argv)
   unsigned long u;
   int s;
   int t;
- 
-  while ((opt = getopt(argc,argv,"dDvqQhHrR1UXx:t:u:g:l:b:B:c:pPoO")) != opteof)
+
+  while ((opt = getopt(argc,argv,"46dDvqQhHrR1UXx:t:u:g:l:b:B:c:I:pPoO")) != opteof)
     switch(opt) {
       case 'b': scan_ulong(optarg,&backlog); break;
       case 'c': scan_ulong(optarg,&limit); break;
@@ -325,7 +359,10 @@ main(int argc,char **argv)
 		x = env_get("GID"); if (x) scan_ulong(x,&gid); break;
       case 'u': scan_ulong(optarg,&uid); break;
       case 'g': scan_ulong(optarg,&gid); break;
+      case 'I': netif=socket_getifidx(optarg); break;
       case '1': flag1 = 1; break;
+      case '4': noipv6 = 1; break;
+      case '6': forcev6 = 1; break;
       case 'l': localhost = optarg; break;
       default: usage();
     }
@@ -337,8 +374,7 @@ main(int argc,char **argv)
  
   hostname = *argv++;
   if (!hostname) usage();
-  if (str_equal(hostname,"")) hostname = "0.0.0.0";
-  if (str_equal(hostname,"0")) hostname = "0.0.0.0";
+  if (str_equal(hostname,"")) hostname = "0";
 
   x = *argv++;
   if (!x) usage();
@@ -348,7 +384,7 @@ main(int argc,char **argv)
     se = getservbyname(x,"tcp");
     if (!se)
       strerr_die3x(111,FATAL,"unable to figure out port number for ",x);
-    localport = ntohs(se->s_port);
+    uint16_unpack_big((char*)&se->s_port,&localport);
   }
 
   if (!*argv) usage();
@@ -358,20 +394,26 @@ main(int argc,char **argv)
   sig_catch(sig_term,sigterm);
   sig_ignore(sig_pipe);
  
-  if (!stralloc_copys(&tmp,hostname))
-    strerr_die2x(111,FATAL,"out of memory");
-  if (dns_ip4_qualify(&addresses,&fqdn,&tmp) == -1)
-    strerr_die4sys(111,FATAL,"temporarily unable to figure out IP address for ",hostname,": ");
-  if (addresses.len < 4)
-    strerr_die3x(111,FATAL,"no IP address for ",hostname);
-  byte_copy(localip,4,addresses.s);
-
-  s = socket_tcp();
+  if (str_equal(hostname,"0")) {
+    byte_zero(localip,sizeof localip);
+  } else {
+    if (!stralloc_copys(&tmp,hostname))
+      strerr_die2x(111,FATAL,"out of memory");
+    if (dns_ip6_qualify(&addresses,&fqdn,&tmp) == -1)
+      strerr_die4sys(111,FATAL,"temporarily unable to figure out IP address for ",hostname,": ");
+    if (addresses.len < 16)
+      strerr_die3x(111,FATAL,"no IP address for ",hostname);
+    byte_copy(localip,16,addresses.s);
+    if (ip6_isv4mapped(localip))
+      noipv6=1;
+  }
+
+  s = socket_tcp6();
   if (s == -1)
     strerr_die2sys(111,FATAL,"unable to create socket: ");
-  if (socket_bind4_reuse(s,localip,localport) == -1)
+  if (socket_bind6_reuse(s,localip,localport,netif) == -1)
     strerr_die2sys(111,FATAL,"unable to bind: ");
-  if (socket_local4(s,localip,&localport) == -1)
+  if (socket_local6(s,localip,&localport,&netif) == -1)
     strerr_die2sys(111,FATAL,"unable to get local address: ");
   if (socket_listen(s,backlog) == -1)
     strerr_die2sys(111,FATAL,"unable to listen: ");
@@ -399,7 +441,7 @@ main(int argc,char **argv)
     while (numchildren >= limit) sig_pause();
 
     sig_unblock(sig_child);
-    t = socket_accept4(s,remoteip,&remoteport);
+    t = socket_accept6(s,remoteip,&remoteport,&netif);
     sig_block(sig_child);
 
     if (t == -1) continue;
diff --git a/timeoutconn.h b/timeoutconn.h
index 7f9dcc9..01e6a75 100644
--- a/timeoutconn.h
+++ b/timeoutconn.h
@@ -2,7 +2,9 @@
 #define TIMEOUTCONN_H
 
 #include "uint16.h"
+#include "uint32.h"
 
 extern int timeoutconn(int,char *,uint16,unsigned int);
+extern int timeoutconn6(int,char *,uint16,unsigned int,uint32);
 
 #endif
diff --git a/timeoutconn6.c b/timeoutconn6.c
new file mode 100644
index 0000000..75e9f5a
--- /dev/null
+++ b/timeoutconn6.c
@@ -0,0 +1,34 @@
+#include "ndelay.h"
+#include "socket.h"
+#include "iopause.h"
+#include "error.h"
+#include "timeoutconn.h"
+
+int timeoutconn6(int s,char ip[16],uint16 port,unsigned int timeout,uint32 netif)
+{
+  struct taia now;
+  struct taia deadline;
+  iopause_fd x;
+
+  if (socket_connect6(s,ip,port,netif) == -1) {
+    if ((errno != error_wouldblock) && (errno != error_inprogress)) return -1;
+    x.fd = s;
+    x.events = IOPAUSE_WRITE;
+    taia_now(&now);
+    taia_uint(&deadline,timeout);
+    taia_add(&deadline,&now,&deadline);
+    for (;;) {
+      taia_now(&now);
+      iopause(&x,1,&deadline,&now);
+      if (x.revents) break;
+      if (taia_less(&deadline,&now)) {
+	errno = error_timeout; /* note that connect attempt is continuing */
+	return -1;
+      }
+    }
+    if (!socket_connected(s)) return -1;
+  }
+
+  if (ndelay_off(s) == -1) return -1;
+  return 0;
+}
diff --git a/tryip6.c b/tryip6.c
new file mode 100644
index 0000000..e0d7cfb
--- /dev/null
+++ b/tryip6.c
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+main() {
+  struct sockaddr_in6 sa;
+  sa.sin6_family = PF_INET6;
+}
diff --git a/who@.1 b/who@.1
new file mode 100644
index 0000000..0c13f84
--- /dev/null
+++ b/who@.1
@@ -0,0 +1,32 @@
+.TH who@ 1
+.SH NAME
+who@ \- print list of active users on a host
+.SH SYNTAX
+.B who@
+[
+.I host
+]
+.SH DESCRIPTION
+.B who@
+connects to TCP port 11 (Systat) on
+.I host
+and prints any data it receives.
+It removes CR and converts unprintable characters to a visible format.
+
+If
+.I host
+is not supplied,
+.B who@
+connects to the local host.
+
+Some computers respond to port 11 with a list of active users.
+For example, they may be running
+
+.EX
+     tcpserver 0 11 who &
+.EE
+.SH "SEE ALSO"
+cat(1),
+delcr(1),
+tcpclient(1),
+tcpserver(1)
-- 
1.6.0.3

