diff options
author | shin <shin@FreeBSD.org> | 2000-01-27 09:28:38 +0000 |
---|---|---|
committer | shin <shin@FreeBSD.org> | 2000-01-27 09:28:38 +0000 |
commit | ce15efb7c04858f00b57c16093d4a3043809048e (patch) | |
tree | 8b3d00f78a4a5a34cc3b17e29c28b4472d93a35c /usr.sbin/faithd | |
parent | dcbae417f8f4365a5eea807290162acd308b720d (diff) | |
download | FreeBSD-src-ce15efb7c04858f00b57c16093d4a3043809048e.zip FreeBSD-src-ce15efb7c04858f00b57c16093d4a3043809048e.tar.gz |
another tcp apps IPv6 updates.(should be make world safe)
ftp, telnet, ftpd, faithd
also telnet related sync with crypto, secure, kerberosIV
Obtained from: KAME project
Diffstat (limited to 'usr.sbin/faithd')
-rw-r--r-- | usr.sbin/faithd/Makefile | 24 | ||||
-rw-r--r-- | usr.sbin/faithd/README | 140 | ||||
-rw-r--r-- | usr.sbin/faithd/faithd.8 | 256 | ||||
-rw-r--r-- | usr.sbin/faithd/faithd.c | 837 | ||||
-rw-r--r-- | usr.sbin/faithd/faithd.h | 70 | ||||
-rw-r--r-- | usr.sbin/faithd/ftp.c | 1139 | ||||
-rw-r--r-- | usr.sbin/faithd/rsh.c | 210 | ||||
-rw-r--r-- | usr.sbin/faithd/tcp.c | 300 | ||||
-rw-r--r-- | usr.sbin/faithd/test/faithd.rb | 312 |
9 files changed, 3288 insertions, 0 deletions
diff --git a/usr.sbin/faithd/Makefile b/usr.sbin/faithd/Makefile new file mode 100644 index 0000000..c40c21d --- /dev/null +++ b/usr.sbin/faithd/Makefile @@ -0,0 +1,24 @@ +# Copyright (c) 1996 WIDE Project. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modifications, are permitted provided that the above copyright notice +# and this paragraph are duplicated in all such forms and that any +# documentation, advertising materials, and other materials related to +# such distribution and use acknowledge that the software was developed +# by the WIDE Project, Japan. The name of the Project may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. THIS SOFTWARE IS PROVIDED ``AS IS'' +# AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT +# LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE. +# $FreeBSD$ + +PROG= faithd +SRCS= faithd.c tcp.c ftp.c rsh.c +MAN8= faithd.8 +#CFLAGS+= -DFAITH4 +CFLAGS+= -Wall +DPADD+= ${LIBUTIL} +LDADD+= -lutil + +.include <bsd.prog.mk> diff --git a/usr.sbin/faithd/README b/usr.sbin/faithd/README new file mode 100644 index 0000000..f8e4753 --- /dev/null +++ b/usr.sbin/faithd/README @@ -0,0 +1,140 @@ +Configuring FAITH IPv6-to-IPv4 TCP relay + +Kazu Yamamoto and Jun-ichiro itojun Hagino +$Id: README,v 1.1.1.1 1999/08/08 23:29:27 itojun Exp $ +$FreeBSD$ + + +Introduction +============ + +FAITH is a IPv6-to-IPv4 TCP relay. It performs tcp relay just as some of +firewall-oriented gateway does, but between IPv6 and IPv4 with address +translation. +TCP connections has to be made from IPv6 node to IPv4 node. FAITH will +not relay connections for the opposite direction. +To perform relays, FAITH daemon needs to be executed on a router between +your local IPv6 site and outside IPv4 network. The daemon needs to be +invoked per each TCP services (TCP port number). + + IPv4 node "dest" = 123.4.5.6 + | + [[[[ outside IPv4 ocean ]]]] + | + node that runs FAITH-daemon (usually a router) + | + ==+=====+===+==== IPv6, or IPv4/v6 network in your site ^ + | | | connection + clients IPv6 node "src" | + +You will have to allocate an IPv6 address prefix to map IPv4 addresses into. +The following description uses 3ffe:0501:1234:ffff:: as example. +Please use a prefix which belongs to your site. +FAITH will make it possible to make a IPv6 TCP connection From IPv6 node +"src", toward IPv4 node "dest", by specifying FAITH-mapped address +3ffe:0501:1234:ffff::123.4.5.6 +(which is, 3ffe:0501:1234:ffff:0000:0000:7b04:0506). +The address mapping can be performed by hand:-), by speical nameserver on +the network, or by special resolver on the source node. + + +Setup +===== + +The following example assumes: +- You have assigned 3ffe:0501:1234:ffff:: as FAITH adderss prefix. +- You are willing to provide IPv6-to IPv4 TCP relay for telnet. + +<<On the translating router on which faithd runs>> + +(1) If you have IPv6 TCP server for the "telnet" service, i.e. telnetd via + inet6d, disable that daemon. Comment out the line from "inet6d.conf" + and send the HUP signal to "inet6d". + +(2) Execute sysctl as root to enable FAITH support in the kernel. + + # sysctl -w net.inet6.ip6.keepfaith=1 + +(3) Route packets toward FAITH prefix into "faith0" interface. + + # ifconfig faith0 up + # route add -inet6 3ffe:0501:1234:ffff:: -prefixlen 64 -interface faith0 + + or, on platforms that has problem with "-interface": + # ifconfig faith0 up + # route add -inet6 3ffe:0501:1234:ffff:: -prefixlen 64 \ + fe80:q::xxxx:yyyy:zzzz:wwww + (the last one is link-local address assigned for faith0) + +(4) Execute "faithd" by root as follows: + + # faithd telnet /usr/local/v6/libexec/telnetd telnetd + + 1st argument is a service name you are willing to provide TCP relay. + (it can be specified either by number "23" or by string "telnet") + 2nd argument is a path name for local IPv6 TCP server. If there is a + connection toward the router itself, this program will be invoked. + 3rd and the following arguments are arguments for the local IPv6 TCP + server. (3rd argument is typically the program name without its path.) + + More examples: + + # faithd login /usr/local/v6/libexec/rlogin rlogind + # faithd shell /usr/local/v6/libexec/rshd rshd + # faithd ftpd /usr/local/v6/libexec/ftpd ftpd -l + # faithd ssh + + +<<Routing>> + +(4) Make sure that packets whose destinations match the prefix can +reach from the IPv6 host to the translating router. + +<<On the IPv6 host>> + +There are two ways to translate IPv4 address to IPv6 address: + (a) Faked by DNS + (b) Faked by /etc/hosts. + +(5.a) Install "newbie" and set up FAITH mode. See kit/ports/newbie of + KAME package. KAME package is obtained from www.kame.net. + +(5.b) Add an entry into /etc/hosts so that you can resolve hostname into +faked IPv6 addrss. For example, add the following line for www.freebsd.org: + + 3ffe:0501:1234:ffff::204.216.27.21 www.freebsd.org + +<<On the translating router on which faithd runs.>> + +(6) To see if "faithd" works, watch "/var/log/daemon". Note: please +setup "/etc/syslog.conf" so that LOG_DAEMON messages are to be stored +in "/var/log/daemon". + + <e.g.> + daemon.* /var/log/daemon + + +Advanced configuration +====================== + +If you would like to restrict IPv4 destination for translation, you may +want to do the following: + + # route add -inet6 3ffe:0501:1234:ffff::123.0.0.0 -prefixlen 104 \ + -interface faith0 + +By this way, you can restrict IPv4 destination to 123.0.0.0/8. +You may also want to reject packets toward 3ffe:0501:1234:ffff::/64 which +is not in 3ffe:0501:1234:ffff::123.0.0.0/104. This will be left as excerside +for the reader. + +By doing this, you will be able to provide your IPv4 web server to outside +IPv6 customers, without risks of unwanted open relays. + + [[[[ IPv6 network outside ]]]] | + | | connection + node that runs FAITH-daemon (usually a router) v + | + ========+======== IPv4/v6 network in your site + | (123.0.0.0/8) + IPv4 web server diff --git a/usr.sbin/faithd/faithd.8 b/usr.sbin/faithd/faithd.8 new file mode 100644 index 0000000..2f62ed3 --- /dev/null +++ b/usr.sbin/faithd/faithd.8 @@ -0,0 +1,256 @@ +.\" Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the project nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $Id: faithd.8,v 1.3 1999/10/07 04:22:14 itojun Exp $ +.\" $FreeBSD$ +.\" +.Dd January 27, 2000 +.Dt FAITHD 8 +.Os KAME +.Sh NAME +.Nm faithd +.Nd FAITH IPv6/v4 translator daemon +.Sh SYNOPSIS +.Nm faithd +.Op Fl dp +.Oo +.Ar service +.Oo +.Ar serverpath +.Op Ar serverargs +.Oc +.Oc +.Sh DESCRIPTION +.Nm +provides IPv6/v4 TCP relay for the specified +.Ar service . +.Pp +.Nm +must be invoked on IPv4/v6 dual stack router. +The router must be configured to capture all the TCP traffic +toward reserved IPv6 address prefix, by using +.Xr route 8 +and +.Xr sysctl 8 +commands. +.Nm +will daemonize itself on invocation. +.Pp +.Nm +will listen to TCPv6 port +.Ar service . +If TCPv6 traffic to port +.Ar service +is found, +.Nm +will relay the TCPv6 traffic to TCPv4. +Destination for relayed TCPv4 connection will be determined by the +last 4 octets of the original IPv6 destination. +For example, if +.Li 3ffe:0501:4819:ffff:: +is reserved for +.Nm faithd , +and the TCPv6 destination address is +.Li 3ffe:0501:4819:ffff::0a01:0101 , +the traffic will be relayed to IPv4 destination +.Li 10.1.1.1 . +.Pp +If +.Ar service +is not given, +.Li telnet +is assumed, and +.Nm +will relay TCP traffic on TCP port +.Li telnet . +With +.Ar service , +.Nm +will work as TCP relaying daemon for specified +.Ar service +as described above. +.Pp +Since +.Nm +listens to TCP port +.Ar service , +it is not possible to run local TCP daemons for port +.Ar service +on the router, using +.Xr inetd 8 +or other standard mechanisms. +By specifying +.Ar serverpath +to +.Nm faithd , +you can run local daemons on the router. +.Nm +will invoke local daemon at +.Ar serverpath +if the destination address is local interface address, +and will perform translation to IPv4 TCP in other cases. +You can also specify +.Ar serverargs +for the arguments for the local daemon. +.Pp +To use +.Nm +translation service, +an IPv6 address prefix must be reserved for mapping IPv4 addresses into. +Kernel must be properly configured to route all the TCP connection +toward the reserved IPv6 address prefix into the +.Dv faith +pseudo interface, by using +.Xr route 8 +command. +Also, +.Xr sysctl 8 +should be used to configure +.Dv net.inet6.ip6.keepfaith +to +.Dv 1 . +.Pp +If +.Fl d +is given, debugging information will be generated using +.Xr syslog 3 . +If +.Fl p +is given, +.Nm +will use privileged TCP port number as source port, +for IPv4 TCP connection toward final destination. +For relaying +.Xr ftp 1 +and +.Xr rlogin 1 , +.Fl p +is not necessary as special program code is supplied. +.Pp +.Nm +will relay both normal and out-of-band TCP data. +It is capable of emulating TCP half close as well. +.Nm +includes special support for protocols used by +.Xr ftp 1 +and +.Xr rlogin 1 . +When translating FTP protocol, +.Nm +translates network level addresses in +.Li PORT/LPRT/EPRT +and +.Li PASV/LPSV/EPSV +commands. +For RLOGIN protocol, +.Nm +will relay back connection from +.Xr rlogind 8 +on the server to +.Xr rlogin 1 +on client. +.Pp +Inactive sessions will be disconnected in 30 minutes, +to avoid stale sessions from chewing up resources. +This may be inappropriate for some of the services +.Po +should this be configurable? +.Pc . +.\" +.Sh EXAMPLES +Before invoking +.Nm faithd , +.Xr faith 4 +interface has to be configured properly. +.Pp +To translate +.Li telnet +service, and provide no local telnet service, invoke +.Nm +as either of the following: +.Bd -literal -offset +# faithd +# faithd telnet +.Ed +.Pp +If you would like to provide local telnet service via +.Xr telnetd 8 +on +.Pa /usr/local/v6/libexec/telnetd , +user the following command line: +.Bd -literal -offset +# faithd telnet /usr/local/v6/libexec/telnetd telnetd +.Ed +.Pp +If you would like to pass extra arguments to the local daemon: +.Bd -literal -offset +# faithd ftpd /usr/local/v6/libexec/ftpd ftpd -l +.Ed +.Pp +Here are some other examples: +.Bd -literal -offset +# faithd login /usr/local/v6/libexec/rlogin rlogind +# faithd shell /usr/local/v6/libexec/rshd rshd +# faithd sshd +.Ed +.\" +.Sh RETURN VALUES +.Nm +exits with +.Dv EXIT_SUCCESS +.Pq 0 +on success, and +.Dv EXIT_FAILURE +.Pq 1 +on error. +.\" +.Sh SEE ALSO +.Xr faith 4 , +.Xr route 8 , +.Xr sysctl 8 +.Rs +.%A Jun-ichiro itojun Hagino +.%A Kazu Yamamoto +.%T "An IPv6-to-IPv4 transport relay translator" +.%R internet draft +.%N draft-ietf-ngtrans-tcpudp-relay-00.txt +.%O work in progress material +.Re +.\" +.Sh SECURITY NOTICE +It is very insecure to use +.Xr rhosts 5 +and other IP-address based authentication, for connections relayed by +.Nm +.Po +and any other TCP relaying services +.Pc . +.\" +.Sh HISTORY +The +.Nm +command first appeared in WIDE Hydrangea IPv6 protocol stack kit. diff --git a/usr.sbin/faithd/faithd.c b/usr.sbin/faithd/faithd.c new file mode 100644 index 0000000..ee6c8df --- /dev/null +++ b/usr.sbin/faithd/faithd.c @@ -0,0 +1,837 @@ +/* + * Copyright (C) 1997 and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * User level translator from IPv6 to IPv4. + * + * Usage: faithd [<port> <progpath> <arg1(progname)> <arg2> ...] + * e.g. faithd telnet /usr/local/v6/sbin/telnetd telnetd + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/sysctl.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <libutil.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <fcntl.h> +#include <termios.h> + +#include <net/if_types.h> +#ifdef IFT_FAITH +# define USE_ROUTE +# include <net/if.h> +# include <net/route.h> +# include <net/if_dl.h> +#endif + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#ifdef FAITH4 +#include <resolv.h> +#include <arpa/nameser.h> +#ifndef FAITH_NS +#define FAITH_NS "FAITH_NS" +#endif +#endif + +#include "faithd.h" + +char *serverpath = NULL; +char *serverarg[MAXARGV + 1]; +static char *faithdname = NULL; +char logname[BUFSIZ]; +char procname[BUFSIZ]; +struct myaddrs { + struct myaddrs *next; + struct sockaddr *addr; +}; +struct myaddrs *myaddrs = NULL; +static char *service; +#ifdef USE_ROUTE +static int sockfd = 0; +#endif +int dflag = 0; +static int pflag = 0; + +int main __P((int, char **)); +static void play_service __P((int)); +static void play_child __P((int, struct sockaddr *)); +static int faith_prefix __P((struct sockaddr *)); +static int map6to4 __P((struct sockaddr_in6 *, struct sockaddr_in *)); +#ifdef FAITH4 +static int map4to6 __P((struct sockaddr_in *, struct sockaddr_in6 *)); +#endif +static void sig_child __P((int)); +static void sig_terminate __P((int)); +static void start_daemon __P((void)); +static unsigned int if_maxindex __P((void)); +static void grab_myaddrs __P((void)); +static void free_myaddrs __P((void)); +static void update_myaddrs __P((void)); +static void usage __P((void)); + +int +main(int argc, char *argv[]) +{ + struct addrinfo hints, *res; + int s_wld, error, i, serverargc, on = 1; + int family = AF_INET6; + int c; +#ifdef FAITH_NS + char *ns; +#endif /* FAITH_NS */ + extern int optind; + extern char *optarg; + + /* + * Initializing stuff + */ + + faithdname = strrchr(argv[0], '/'); + if (faithdname) + faithdname++; + else + faithdname = argv[0]; + + while ((c = getopt(argc, argv, "dp46")) != -1) { + switch (c) { + case 'd': + dflag++; + break; + case 'p': + pflag++; + break; +#ifdef FAITH4 + case '4': + family = AF_INET; + break; + case '6': + family = AF_INET6; + break; +#endif + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + +#ifdef FAITH_NS + if ((ns = getenv(FAITH_NS)) != NULL) { + struct sockaddr_storage ss; + struct addrinfo hints, *res; + char serv[NI_MAXSERV]; + + memset(&ss, 0, sizeof(ss)); + memset(&hints, 0, sizeof(hints)); + snprintf(serv, sizeof(serv), "%u", NAMESERVER_PORT); + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(ns, serv, &hints, &res) == 0) { + res_init(); + memcpy(&_res_ext.nsaddr, res->ai_addr, res->ai_addrlen); + _res.nscount = 1; + } + } +#endif /* FAITH_NS */ + +#ifdef USE_ROUTE + grab_myaddrs(); +#endif + + switch (argc) { + case 0: + serverpath = DEFAULT_PATH; + serverarg[0] = DEFAULT_NAME; + serverarg[1] = NULL; + service = DEFAULT_PORT_NAME; + break; + default: + serverargc = argc - NUMARG; + if (serverargc > MAXARGV) + exit_error("too many augments"); + + serverpath = malloc(strlen(argv[NUMPRG])); + strcpy(serverpath, argv[NUMPRG]); + for (i = 0; i < serverargc; i++) { + serverarg[i] = malloc(strlen(argv[i + NUMARG])); + strcpy(serverarg[i], argv[i + NUMARG]); + } + serverarg[i] = NULL; + /* fall throuth */ + case 1: /* no local service */ + service = argv[NUMPRT]; + break; + } + + /* + * Opening wild card socket for this service. + */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + error = getaddrinfo(NULL, service, &hints, &res); + if (error) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error)); + if (error == EAI_SYSTEM) + exit_error("getaddrinfo: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + s_wld = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s_wld == -1) + exit_error("socket: %s", ERRSTR); + +#ifdef IPV6_FAITH + if (res->ai_family == AF_INET6) { + error = setsockopt(s_wld, IPPROTO_IPV6, IPV6_FAITH, &on, sizeof(on)); + if (error == -1) + exit_error("setsockopt(IPV6_FAITH): %s", ERRSTR); + } +#endif +#ifdef FAITH4 +#ifdef IP_FAITH + if (res->ai_family == AF_INET) { + error = setsockopt(s_wld, IPPROTO_IP, IP_FAITH, &on, sizeof(on)); + if (error == -1) + exit_error("setsockopt(IP_FAITH): %s", ERRSTR); + } +#endif +#endif /* FAITH4 */ + + error = setsockopt(s_wld, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (error == -1) + exit_error("setsockopt(SO_REUSEADDR): %s", ERRSTR); + + error = setsockopt(s_wld, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)); + if (error == -1) + exit_error("setsockopt(SO_OOBINLINE): %s", ERRSTR); + + error = bind(s_wld, (struct sockaddr *)res->ai_addr, res->ai_addrlen); + if (error == -1) + exit_error("bind: %s", ERRSTR); + + error = listen(s_wld, 5); + if (error == -1) + exit_error("listen: %s", ERRSTR); + +#ifdef USE_ROUTE + sockfd = socket(PF_ROUTE, SOCK_RAW, PF_UNSPEC); + if (sockfd < 0) { + exit_error("socket(PF_ROUTE): %s", ERRSTR); + /*NOTREACHED*/ + } +#endif + + /* + * Everything is OK. + */ + + start_daemon(); + + snprintf(logname, sizeof(logname), "faithd %s", service); + snprintf(procname, sizeof(procname), "accepting port %s", service); + openlog(logname, LOG_PID | LOG_NOWAIT, LOG_DAEMON); + syslog(LOG_INFO, "Staring faith daemon for %s port", service); + + play_service(s_wld); + /*NOTRECHED*/ + exit(1); /*pacify gcc*/ +} + +static void +play_service(int s_wld) +{ + struct sockaddr_storage srcaddr; + int len; + int s_src; + pid_t child_pid; + fd_set rfds; + int error; + int maxfd; + + /* + * Wait, accept, fork, faith.... + */ +again: + setproctitle(procname); + + FD_ZERO(&rfds); + FD_SET(s_wld, &rfds); + maxfd = s_wld; +#ifdef USE_ROUTE + if (sockfd) { + FD_SET(sockfd, &rfds); + maxfd = (maxfd < sockfd) ? sockfd : maxfd; + } +#endif + + error = select(maxfd + 1, &rfds, NULL, NULL, NULL); + if (error < 0) { + if (errno == EINTR) + goto again; + exit_failure("select: %s", ERRSTR); + /*NOTREACHED*/ + } + +#ifdef USE_ROUTE + if (FD_ISSET(sockfd, &rfds)) { + update_myaddrs(); + } +#endif + if (FD_ISSET(s_wld, &rfds)) { + len = sizeof(srcaddr); + s_src = accept(s_wld, (struct sockaddr *)&srcaddr, + &len); + if (s_src == -1) + exit_failure("socket: %s", ERRSTR); + + child_pid = fork(); + + if (child_pid == 0) { + /* child process */ + close(s_wld); + closelog(); + openlog(logname, LOG_PID | LOG_NOWAIT, LOG_DAEMON); + play_child(s_src, (struct sockaddr *)&srcaddr); + exit_failure("should never reach here"); + } else { + /* parent process */ + close(s_src); + if (child_pid == -1) + syslog(LOG_ERR, "can't fork"); + } + } + goto again; +} + +static void +play_child(int s_src, struct sockaddr *srcaddr) +{ + struct sockaddr_storage dstaddr6; + struct sockaddr_storage dstaddr4; + char src[MAXHOSTNAMELEN]; + char dst6[MAXHOSTNAMELEN]; + char dst4[MAXHOSTNAMELEN]; + int len = sizeof(dstaddr6); + int s_dst, error, hport, nresvport, on = 1; + struct timeval tv; + struct sockaddr *sa4; + + tv.tv_sec = 1; + tv.tv_usec = 0; + + getnameinfo(srcaddr, srcaddr->sa_len, + src, sizeof(src), NULL, 0, NI_NUMERICHOST); + syslog(LOG_INFO, "accepted a client from %s", src); + + error = getsockname(s_src, (struct sockaddr *)&dstaddr6, &len); + if (error == -1) + exit_failure("getsockname: %s", ERRSTR); + + getnameinfo((struct sockaddr *)&dstaddr6, len, + dst6, sizeof(dst6), NULL, 0, NI_NUMERICHOST); + syslog(LOG_INFO, "the client is connecting to %s", dst6); + + if (!faith_prefix((struct sockaddr *)&dstaddr6)) { + if (serverpath) { + /* + * Local service + */ + syslog(LOG_INFO, "executing local %s", serverpath); + dup2(s_src, 0); + close(s_src); + dup2(0, 1); + dup2(0, 2); + execv(serverpath, serverarg); + syslog(LOG_ERR, "execv %s: %s", serverpath, ERRSTR); + _exit(EXIT_FAILURE); + } else { + close(s_src); + exit_success("no local service for %s", service); + } + } + + /* + * Act as a translator + */ + + switch (((struct sockaddr *)&dstaddr6)->sa_family) { + case AF_INET6: + if (!map6to4((struct sockaddr_in6 *)&dstaddr6, + (struct sockaddr_in *)&dstaddr4)) { + close(s_src); + exit_error("map6to4 failed"); + } + syslog(LOG_INFO, "translating from v6 to v4"); + break; +#ifdef FAITH4 + case AF_INET: + if (!map4to6((struct sockaddr_in *)&dstaddr6, + (struct sockaddr_in6 *)&dstaddr4)) { + close(s_src); + exit_error("map4to6 failed"); + } + syslog(LOG_INFO, "translating from v4 to v6"); + break; +#endif + default: + close(s_src); + exit_error("family not supported"); + /*NOTREACHED*/ + } + + sa4 = (struct sockaddr *)&dstaddr4; + getnameinfo(sa4, sa4->sa_len, + dst4, sizeof(dst4), NULL, 0, NI_NUMERICHOST); + syslog(LOG_INFO, "the translator is connecting to %s", dst4); + + setproctitle("port %s, %s -> %s", service, src, dst4); + + if (sa4->sa_family == AF_INET6) + hport = ntohs(((struct sockaddr_in6 *)&dstaddr4)->sin6_port); + else /* AF_INET */ + hport = ntohs(((struct sockaddr_in *)&dstaddr4)->sin_port); + + switch (hport) { + case RLOGIN_PORT: + case RSH_PORT: + s_dst = rresvport_af(&nresvport, sa4->sa_family); + break; + default: + if (pflag) + s_dst = rresvport_af(&nresvport, sa4->sa_family); + else + s_dst = socket(sa4->sa_family, SOCK_STREAM, 0); + break; + } + if (s_dst == -1) + exit_failure("socket: %s", ERRSTR); + + error = setsockopt(s_dst, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)); + if (error == -1) + exit_error("setsockopt(SO_OOBINLINE): %s", ERRSTR); + + error = setsockopt(s_src, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + if (error == -1) + exit_error("setsockopt(SO_SNDTIMEO): %s", ERRSTR); + error = setsockopt(s_dst, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + if (error == -1) + exit_error("setsockopt(SO_SNDTIMEO): %s", ERRSTR); + + error = connect(s_dst, sa4, sa4->sa_len); + if (error == -1) + exit_failure("connect: %s", ERRSTR); + + switch (hport) { + case FTP_PORT: + ftp_relay(s_src, s_dst); + break; + case RSH_PORT: + rsh_relay(s_src, s_dst); + break; + default: + tcp_relay(s_src, s_dst, service); + break; + } + + /* NOTREACHED */ +} + +/* 0: non faith, 1: faith */ +static int +faith_prefix(struct sockaddr *dst) +{ +#ifndef USE_ROUTE + int mib[4], size; + struct in6_addr faith_prefix; + struct sockaddr_in6 *dst6 = (struct sockaddr_in *)dst; + + if (dst->sa_family != AF_INET6) + return 0; + + mib[0] = CTL_NET; + mib[1] = PF_INET6; + mib[2] = IPPROTO_IPV6; + mib[3] = IPV6CTL_FAITH_PREFIX; + size = sizeof(struct in6_addr); + if (sysctl(mib, 4, &faith_prefix, &size, NULL, 0) < 0) + exit_error("sysctl: %s", ERRSTR); + + if (memcmp(dst, &faith_prefix, + sizeof(struct in6_addr) - sizeof(struct in_addr) == 0) { + return 1; + } + return 0; +#else + struct myaddrs *p; + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin4; + struct sockaddr_in6 *dst6; + struct sockaddr_in *dst4; + struct sockaddr_in dstmap; + + dst6 = (struct sockaddr_in6 *)dst; + if (dst->sa_family == AF_INET6 + && IN6_IS_ADDR_V4MAPPED(&dst6->sin6_addr)) { + /* ugly... */ + memset(&dstmap, 0, sizeof(dstmap)); + dstmap.sin_family = AF_INET; + dstmap.sin_len = sizeof(dstmap); + memcpy(&dstmap.sin_addr, &dst6->sin6_addr.s6_addr[12], + sizeof(dstmap.sin_addr)); + dst = (struct sockaddr *)&dstmap; + } + + dst6 = (struct sockaddr_in6 *)dst; + dst4 = (struct sockaddr_in *)dst; + + for (p = myaddrs; p; p = p->next) { + sin6 = (struct sockaddr_in6 *)p->addr; + sin4 = (struct sockaddr_in *)p->addr; + + if (p->addr->sa_len != dst->sa_len + || p->addr->sa_family != dst->sa_family) + continue; + + switch (dst->sa_family) { + case AF_INET6: + if (sin6->sin6_scope_id == dst6->sin6_scope_id + && IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &dst6->sin6_addr)) + return 0; + break; + case AF_INET: + if (sin4->sin_addr.s_addr == dst4->sin_addr.s_addr) + return 0; + break; + } + } + return 1; +#endif +} + +/* 0: non faith, 1: faith */ +static int +map6to4(struct sockaddr_in6 *dst6, struct sockaddr_in *dst4) +{ + memset(dst4, 0, sizeof(*dst4)); + dst4->sin_len = sizeof(*dst4); + dst4->sin_family = AF_INET; + dst4->sin_port = dst6->sin6_port; + memcpy(&dst4->sin_addr, &dst6->sin6_addr.s6_addr[12], + sizeof(dst4->sin_addr)); + + if (dst4->sin_addr.s_addr == INADDR_ANY + || dst4->sin_addr.s_addr == INADDR_BROADCAST + || IN_MULTICAST(dst4->sin_addr.s_addr)) + return 0; + + return 1; +} + +#ifdef FAITH4 +/* 0: non faith, 1: faith */ +static int +map4to6(struct sockaddr_in *dst4, struct sockaddr_in6 *dst6) +{ + char host[NI_MAXHOST]; + char serv[NI_MAXSERV]; + struct addrinfo hints, *res; + int ai_errno; + + if (getnameinfo((struct sockaddr *)dst4, dst4->sin_len, host, sizeof(host), + serv, sizeof(serv), NI_NAMEREQD|NI_NUMERICSERV) != 0) + return 0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = 0; + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if ((ai_errno = getaddrinfo(host, serv, &hints, &res)) != 0) { + syslog(LOG_INFO, "%s %s: %s", host, serv, gai_strerror(ai_errno)); + if (ai_errno == EAI_SYSTEM) + syslog(LOG_INFO, "%s %s: %s", host, serv, + strerror(errno)); + return 0; + } + + memcpy(dst6, res->ai_addr, res->ai_addrlen); + + freeaddrinfo(res); + + return 1; +} +#endif /* FAITH4 */ + +static void +sig_child(int sig) +{ + int status; + pid_t pid; + + pid = wait3(&status, WNOHANG, (struct rusage *)0); + if (pid && status) + syslog(LOG_WARNING, "child %d exit status 0x%x", pid, status); +} + +void +sig_terminate(int sig) +{ + syslog(LOG_INFO, "Terminating faith daemon"); + exit(EXIT_SUCCESS); +} + +static void +start_daemon(void) +{ + if (daemon(0, 0) == -1) + exit_error("daemon: %s", ERRSTR); + + if (signal(SIGCHLD, sig_child) == SIG_ERR) + exit_failure("signal CHLD: %s", ERRSTR); + + if (signal(SIGTERM, sig_terminate) == SIG_ERR) + exit_failure("signal TERM: %s", ERRSTR); +} + +void +exit_error(const char *fmt, ...) +{ + va_list ap; + char buf[BUFSIZ]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + fprintf(stderr, "%s\n", buf); + exit(EXIT_FAILURE); +} + +void +exit_failure(const char *fmt, ...) +{ + va_list ap; + char buf[BUFSIZ]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + syslog(LOG_ERR, buf); + exit(EXIT_FAILURE); +} + +void +exit_success(const char *fmt, ...) +{ + va_list ap; + char buf[BUFSIZ]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + syslog(LOG_INFO, buf); + exit(EXIT_SUCCESS); +} + +#ifdef USE_ROUTE +static unsigned int +if_maxindex() +{ + struct if_nameindex *p, *p0; + unsigned int max = 0; + + p0 = if_nameindex(); + for (p = p0; p && p->if_index && p->if_name; p++) { + if (max < p->if_index) + max = p->if_index; + } + if_freenameindex(p0); + return max; +} + +static void +grab_myaddrs() +{ + int s; + unsigned int maxif; + struct ifreq *iflist; + struct ifconf ifconf; + struct ifreq *ifr, *ifr_end; + struct myaddrs *p; + struct sockaddr_in6 *sin6; + + maxif = if_maxindex() + 1; + iflist = (struct ifreq *)malloc(maxif * BUFSIZ); /* XXX */ + if (!iflist) { + exit_failure("not enough core"); + /*NOTREACHED*/ + } + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + exit_failure("socket(SOCK_DGRAM)"); + /*NOTREACHED*/ + } + memset(&ifconf, 0, sizeof(ifconf)); + ifconf.ifc_req = iflist; + ifconf.ifc_len = maxif * BUFSIZ; /* XXX */ + if (ioctl(s, SIOCGIFCONF, &ifconf) < 0) { + exit_failure("ioctl(SIOCGIFCONF)"); + /*NOTREACHED*/ + } + close(s); + + /* Look for this interface in the list */ + ifr_end = (struct ifreq *) (ifconf.ifc_buf + ifconf.ifc_len); + for (ifr = ifconf.ifc_req; + ifr < ifr_end; + ifr = (struct ifreq *) ((char *) &ifr->ifr_addr + + ifr->ifr_addr.sa_len)) { + switch (ifr->ifr_addr.sa_family) { + case AF_INET: + case AF_INET6: + p = (struct myaddrs *)malloc(sizeof(struct myaddrs) + + ifr->ifr_addr.sa_len); + if (!p) { + exit_failure("not enough core"); + /*NOTREACHED*/ + } + memcpy(p + 1, &ifr->ifr_addr, ifr->ifr_addr.sa_len); + p->next = myaddrs; + p->addr = (struct sockaddr *)(p + 1); +#ifdef __KAME__ + if (ifr->ifr_addr.sa_family == AF_INET6) { + sin6 = (struct sockaddr_in6 *)p->addr; + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) + || IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) { + sin6->sin6_scope_id = + ntohs(*(u_int16_t *)&sin6->sin6_addr.s6_addr[2]); + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + } + } +#endif + myaddrs = p; + if (dflag) { + char hbuf[NI_MAXHOST]; + getnameinfo(p->addr, p->addr->sa_len, + hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST); + syslog(LOG_INFO, "my interface: %s %s", hbuf, ifr->ifr_name); + } + break; + default: + break; + } + } + + free(iflist); +} + +static void +free_myaddrs() +{ + struct myaddrs *p, *q; + + p = myaddrs; + while (p) { + q = p->next; + free(p); + p = q; + } + myaddrs = NULL; +} + +static void +update_myaddrs() +{ + char msg[BUFSIZ]; + int len; + struct rt_msghdr *rtm; + + len = read(sockfd, msg, sizeof(msg)); + if (len < 0) { + syslog(LOG_ERR, "read(PF_ROUTE) failed"); + return; + } + rtm = (struct rt_msghdr *)msg; + if (len < 4 || len < rtm->rtm_msglen) { + syslog(LOG_ERR, "read(PF_ROUTE) short read"); + return; + } + if (rtm->rtm_version != RTM_VERSION) { + syslog(LOG_ERR, "routing socket version mismatch"); + close(sockfd); + sockfd = 0; + return; + } + switch (rtm->rtm_type) { + case RTM_NEWADDR: + case RTM_DELADDR: + case RTM_IFINFO: + break; + default: + return; + } + /* XXX more filters here? */ + + syslog(LOG_INFO, "update interface address list"); + free_myaddrs(); + grab_myaddrs(); +} +#endif /*USE_ROUTE*/ + +static void +usage() +{ + fprintf(stderr, "usage: %s [-dp] [service [serverpath [serverargs]]]\n", + faithdname); + exit(0); +} diff --git a/usr.sbin/faithd/faithd.h b/usr.sbin/faithd/faithd.h new file mode 100644 index 0000000..55566ed --- /dev/null +++ b/usr.sbin/faithd/faithd.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 1997 and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +extern char logname[]; +extern int dflag; + +extern void tcp_relay __P((int, int, const char *)); +extern void ftp_relay __P((int, int)); +extern int ftp_active __P((int, int, int *, int *)); +extern int ftp_passive __P((int, int, int *, int *)); +extern void rsh_relay __P((int, int)); +extern void rsh_dual_relay __P((int, int)); +extern void exit_error __P((const char *fmt, ...)); +extern void exit_success __P((const char *fmt, ...)); +extern void exit_failure __P((const char *fmt, ...)); + +#define DEFAULT_PORT_NAME "telnet" +#define DEFAULT_PATH "/usr/local/v6/libexec/telnetd" +#define DEFAULT_NAME "telnetd" + +#define FTP_PORT 21 +#define RLOGIN_PORT 513 +#define RSH_PORT 514 + +#define RETURN_SUCCESS 0 +#define RETURN_FAILURE 1 + +#define YES 1 +#define NO 0 + +#define MSS 2048 +#define MAXARGV 20 + +#define NUMPRT 0 +#define NUMPRG 1 +#define NUMARG 2 + +#define UC(b) (((int)b)&0xff) + +#define ERRSTR strerror(errno) + +#define FAITH_TIMEOUT (30 * 60) /*second*/ diff --git a/usr.sbin/faithd/ftp.c b/usr.sbin/faithd/ftp.c new file mode 100644 index 0000000..fcb25e3 --- /dev/null +++ b/usr.sbin/faithd/ftp.c @@ -0,0 +1,1139 @@ +/* + * Copyright (C) 1997 and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/time.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "faithd.h" + +static char rbuf[MSS]; +static char sbuf[MSS]; +static int passivemode = 0; +static int wport4 = -1; /* listen() to active */ +static int wport6 = -1; /* listen() to passive */ +static int port4 = -1; /* active: inbound passive: outbound */ +static int port6 = -1; /* active: outbound passive: inbound */ +static struct sockaddr_storage data4; /* server data address */ +static struct sockaddr_storage data6; /* client data address */ +static int epsvall = 0; + +#ifdef FAITH4 +enum state { NONE, LPRT, EPRT, PORT, LPSV, EPSV, PASV }; +#else +enum state { NONE, LPRT, EPRT, LPSV, EPSV }; +#endif + +static int ftp_activeconn __P((void)); +static int ftp_passiveconn __P((void)); +static int ftp_copy __P((int, int)); +static int ftp_copyresult __P((int, int, enum state)); +static int ftp_copycommand __P((int, int, enum state *)); + +void +ftp_relay(int ctl6, int ctl4) +{ + fd_set readfds; + int error; + enum state state = NONE; + struct timeval tv; + + syslog(LOG_INFO, "starting ftp control connection"); + + for (;;) { + FD_ZERO(&readfds); + FD_SET(ctl4, &readfds); + FD_SET(ctl6, &readfds); + if (0 <= port4) + FD_SET(port4, &readfds); + if (0 <= port6) + FD_SET(port6, &readfds); +#if 0 + if (0 <= wport4) + FD_SET(wport4, &readfds); + if (0 <= wport6) + FD_SET(wport6, &readfds); +#endif + tv.tv_sec = FAITH_TIMEOUT; + tv.tv_usec = 0; + + error = select(256, &readfds, NULL, NULL, &tv); + if (error == -1) + exit_failure("select: %s", ERRSTR); + else if (error == 0) + exit_failure("connection timeout"); + + /* + * The order of the following checks does (slightly) matter. + * It is important to visit all checks (do not use "continue"), + * otherwise some of the pipe may become full and we cannot + * relay correctly. + */ + if (FD_ISSET(ctl6, &readfds)) { + /* + * copy control connection from the client. + * command translation is necessary. + */ + error = ftp_copycommand(ctl6, ctl4, &state); + + switch (error) { + case -1: + goto bad; + case 0: + close(ctl4); + close(ctl6); + exit_success("terminating ftp control connection"); + /*NOTREACHED*/ + default: + break; + } + } + if (FD_ISSET(ctl4, &readfds)) { + /* + * copy control connection from the server + * translation of result code is necessary. + */ + error = ftp_copyresult(ctl4, ctl6, state); + + switch (error) { + case -1: + goto bad; + case 0: + close(ctl4); + close(ctl6); + exit_success("terminating ftp control connection"); + /*NOTREACHED*/ + default: + break; + } + } + if (0 <= port4 && 0 <= port6 && FD_ISSET(port4, &readfds)) { + /* + * copy data connection. + * no special treatment necessary. + */ + if (FD_ISSET(port4, &readfds)) + error = ftp_copy(port4, port6); + switch (error) { + case -1: + goto bad; + case 0: + close(port4); + close(port6); + port4 = port6 = -1; + syslog(LOG_INFO, "terminating data connection"); + break; + default: + break; + } + } + if (0 <= port4 && 0 <= port6 && FD_ISSET(port6, &readfds)) { + /* + * copy data connection. + * no special treatment necessary. + */ + if (FD_ISSET(port6, &readfds)) + error = ftp_copy(port6, port4); + switch (error) { + case -1: + goto bad; + case 0: + close(port4); + close(port6); + port4 = port6 = -1; + syslog(LOG_INFO, "terminating data connection"); + break; + default: + break; + } + } +#if 0 + if (wport4 && FD_ISSET(wport4, &readfds)) { + /* + * establish active data connection from the server. + */ + ftp_activeconn(); + } + if (wport6 && FD_ISSET(wport6, &readfds)) { + /* + * establish passive data connection from the client. + */ + ftp_passiveconn(); + } +#endif + } + + bad: + exit_failure(ERRSTR); +} + +static int +ftp_activeconn() +{ + int n; + int error; + fd_set set; + struct timeval timeout; + struct sockaddr *sa; + + /* get active connection from server */ + FD_ZERO(&set); + FD_SET(wport4, &set); + timeout.tv_sec = 120; + timeout.tv_usec = -1; + n = sizeof(data4); + if (select(wport4 + 1, &set, NULL, NULL, &timeout) == 0 + || (port4 = accept(wport4, (struct sockaddr *)&data4, &n)) < 0) { + close(wport4); + wport4 = -1; + syslog(LOG_INFO, "active mode data connection failed"); + return -1; + } + + /* ask active connection to client */ + sa = (struct sockaddr *)&data6; + port6 = socket(sa->sa_family, SOCK_STREAM, 0); + if (port6 == -1) { + close(port4); + close(wport4); + port4 = wport4 = -1; + syslog(LOG_INFO, "active mode data connection failed"); + return -1; + } + error = connect(port6, sa, sa->sa_len); + if (port6 == -1) { + close(port6); + close(port4); + close(wport4); + port6 = port4 = wport4 = -1; + syslog(LOG_INFO, "active mode data connection failed"); + return -1; + } + + syslog(LOG_INFO, "active mode data connection established"); + return 0; +} + +static int +ftp_passiveconn() +{ + int n; + int error; + fd_set set; + struct timeval timeout; + struct sockaddr *sa; + + /* get passive connection from client */ + FD_ZERO(&set); + FD_SET(wport6, &set); + timeout.tv_sec = 120; + timeout.tv_usec = 0; + n = sizeof(data6); + if (select(wport6 + 1, &set, NULL, NULL, &timeout) == 0 + || (port6 = accept(wport6, (struct sockaddr *)&data6, &n)) < 0) { + close(wport6); + wport6 = -1; + syslog(LOG_INFO, "passive mode data connection failed"); + return -1; + } + + /* ask passive connection to server */ + sa = (struct sockaddr *)&data4; + port4 = socket(sa->sa_family, SOCK_STREAM, 0); + if (port4 == -1) { + close(wport6); + close(port6); + wport6 = port6 = -1; + syslog(LOG_INFO, "passive mode data connection failed"); + return -1; + } + error = connect(port4, sa, sa->sa_len); + if (port4 == -1) { + close(wport6); + close(port4); + close(port6); + wport6 = port4 = port6 = -1; + syslog(LOG_INFO, "passive mode data connection failed"); + return -1; + } + + syslog(LOG_INFO, "passive mode data connection established"); + return 0; +} + +static int +ftp_copy(int src, int dst) +{ + int error, atmark; + int n; + + /* OOB data handling */ + error = ioctl(src, SIOCATMARK, &atmark); + if (error != -1 && atmark == 1) { + n = read(src, rbuf, 1); + if (n == -1) + goto bad; + send(dst, rbuf, n, MSG_OOB); +#if 0 + n = read(src, rbuf, sizeof(rbuf)); + if (n == -1) + goto bad; + write(dst, rbuf, n); + return n; +#endif + } + + n = read(src, rbuf, sizeof(rbuf)); + switch (n) { + case -1: + case 0: + return n; + default: + write(dst, rbuf, n); + return n; + } + + bad: + exit_failure(ERRSTR); + /*NOTREACHED*/ + return 0; /* to make gcc happy */ +} + +static int +ftp_copyresult(int src, int dst, enum state state) +{ + int error, atmark; + int n; + char *param; + int code; + + /* OOB data handling */ + error = ioctl(src, SIOCATMARK, &atmark); + if (error != -1 && atmark == 1) { + n = read(src, rbuf, 1); + if (n == -1) + goto bad; + send(dst, rbuf, n, MSG_OOB); +#if 0 + n = read(src, rbuf, sizeof(rbuf)); + if (n == -1) + goto bad; + write(dst, rbuf, n); + return n; +#endif + } + + n = read(src, rbuf, sizeof(rbuf)); + if (n <= 0) + return n; + rbuf[n] = '\0'; + + /* + * parse argument + */ + { + char *p; + int i; + + p = rbuf; + for (i = 0; i < 3; i++) { + if (!isdigit(*p)) { + /* invalid reply */ + write(dst, rbuf, n); + return n; + } + p++; + } + if (!isspace(*p)) { + /* invalid reply */ + write(dst, rbuf, n); + return n; + } + code = atoi(rbuf); + param = p; + /* param points to first non-command token, if any */ + while (*param && isspace(*param)) + param++; + if (!*param) + param = NULL; + } + + switch (state) { + case NONE: + if (!passivemode && rbuf[0] == '1') { + if (ftp_activeconn() < 0) { + n = snprintf(rbuf, sizeof(rbuf), + "425 Cannot open data connetion\r\n"); + } + } + write(dst, rbuf, n); + return n; + case LPRT: + case EPRT: + /* expecting "200 PORT command successful." */ + if (code == 200) { + char *p; + + p = strstr(rbuf, "PORT"); + if (p) { + p[0] = (state == LPRT) ? 'L' : 'E'; + p[1] = 'P'; + } + } else { + close(wport4); + wport4 = -1; + } + write(dst, rbuf, n); + return n; +#ifdef FAITH4 + case PORT: + /* expecting "200 EPRT command successful." */ + if (code == 200) { + char *p; + + p = strstr(rbuf, "EPRT"); + if (p) { + p[0] = 'P'; + p[1] = 'O'; + } + } else { + close(wport4); + wport4 = -1; + } + write(dst, rbuf, n); + return n; +#endif + case LPSV: + case EPSV: + /* expecting "227 Entering Passive Mode (x,x,x,x,x,x,x)" */ + if (code != 227) { +passivefail0: + close(wport6); + wport6 = -1; + write(dst, rbuf, n); + return n; + } + + { + unsigned int ho[4], po[2]; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + u_short port; + char *p; + + /* + * PASV result -> LPSV/EPSV result + */ + p = param; + while (*p && *p != '(') + p++; + if (!*p) + goto passivefail0; /*XXX*/ + p++; + n = sscanf(p, "%u,%u,%u,%u,%u,%u", + &ho[0], &ho[1], &ho[2], &ho[3], &po[0], &po[1]); + if (n != 6) + goto passivefail0; /*XXX*/ + + /* keep PORT parameter */ + memset(&data4, 0, sizeof(data4)); + sin = (struct sockaddr_in *)&data4; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = 0; + for (n = 0; n < 4; n++) { + sin->sin_addr.s_addr |= + htonl((ho[n] & 0xff) << ((3 - n) * 8)); + } + sin->sin_port = htons(((po[0] & 0xff) << 8) | (po[1] & 0xff)); + + /* get ready for passive data connection */ + memset(&data6, 0, sizeof(data6)); + sin6 = (struct sockaddr_in6 *)&data6; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + wport6 = socket(sin6->sin6_family, SOCK_STREAM, 0); + if (wport6 == -1) { +passivefail: + n = snprintf(sbuf, sizeof(sbuf), + "500 could not translate from PASV\r\n"); + write(src, sbuf, n); + return n; + } +#ifdef IPV6_FAITH + { + int on = 1; + error = setsockopt(wport6, IPPROTO_IPV6, IPV6_FAITH, + &on, sizeof(on)); + if (error == -1) + exit_error("setsockopt(IPV6_FAITH): %s", ERRSTR); + } +#endif + error = bind(wport6, (struct sockaddr *)sin6, sin6->sin6_len); + if (error == -1) { + close(wport6); + wport6 = -1; + goto passivefail; + } + error = listen(wport6, 1); + if (error == -1) { + close(wport6); + wport6 = -1; + goto passivefail; + } + + /* transmit LPSV or EPSV */ + /* + * addr from dst, port from wport6 + */ + n = sizeof(data6); + error = getsockname(wport6, (struct sockaddr *)&data6, &n); + if (error == -1) { + close(wport6); + wport6 = -1; + goto passivefail; + } + sin6 = (struct sockaddr_in6 *)&data6; + port = sin6->sin6_port; + + n = sizeof(data6); + error = getsockname(dst, (struct sockaddr *)&data6, &n); + if (error == -1) { + close(wport6); + wport6 = -1; + goto passivefail; + } + sin6 = (struct sockaddr_in6 *)&data6; + sin6->sin6_port = port; + + if (state == LPSV) { + char *a, *p; + + a = (char *)&sin6->sin6_addr; + p = (char *)&sin6->sin6_port; + n = snprintf(sbuf, sizeof(sbuf), +"228 Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)\r\n", + 6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]), + UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]), + UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]), + 2, UC(p[0]), UC(p[1])); + write(dst, sbuf, n); + passivemode = 1; + return n; + } else { + n = snprintf(sbuf, sizeof(sbuf), +"229 Entering Extended Passive Mode (|||%d|)\r\n", + ntohs(sin6->sin6_port)); + write(dst, sbuf, n); + passivemode = 1; + return n; + } + } +#ifdef FAITH4 + case PASV: + /* expecting "229 Entering Extended Passive Mode (|||x|)" */ + if (code != 229) { +passivefail1: + close(wport6); + wport6 = -1; + write(dst, rbuf, n); + return n; + } + + { + u_short port; + char *p; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + /* + * EPSV result -> PORT result + */ + p = param; + while (*p && *p != '(') + p++; + if (!*p) + goto passivefail1; /*XXX*/ + p++; + n = sscanf(p, "|||%hu|", &port); + if (n != 1) + goto passivefail1; /*XXX*/ + + /* keep EPRT parameter */ + n = sizeof(data4); + error = getpeername(src, (struct sockaddr *)&data4, &n); + if (error == -1) + goto passivefail1; /*XXX*/ + sin6 = (struct sockaddr_in6 *)&data4; + sin6->sin6_port = htons(port); + + /* get ready for passive data connection */ + memset(&data6, 0, sizeof(data6)); + sin = (struct sockaddr_in *)&data6; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + wport6 = socket(sin->sin_family, SOCK_STREAM, 0); + if (wport6 == -1) { +passivefail2: + n = snprintf(sbuf, sizeof(sbuf), + "500 could not translate from EPSV\r\n"); + write(src, sbuf, n); + return n; + } +#ifdef IP_FAITH + { + int on = 1; + error = setsockopt(wport6, IPPROTO_IP, IP_FAITH, + &on, sizeof(on)); + if (error == -1) + exit_error("setsockopt(IP_FAITH): %s", ERRSTR); + } +#endif + error = bind(wport6, (struct sockaddr *)sin, sin->sin_len); + if (error == -1) { + close(wport6); + wport6 = -1; + goto passivefail2; + } + error = listen(wport6, 1); + if (error == -1) { + close(wport6); + wport6 = -1; + goto passivefail2; + } + + /* transmit PORT */ + /* + * addr from dst, port from wport6 + */ + n = sizeof(data6); + error = getsockname(wport6, (struct sockaddr *)&data6, &n); + if (error == -1) { + close(wport6); + wport6 = -1; + goto passivefail2; + } + sin = (struct sockaddr_in *)&data6; + port = sin->sin_port; + + n = sizeof(data6); + error = getsockname(dst, (struct sockaddr *)&data6, &n); + if (error == -1) { + close(wport6); + wport6 = -1; + goto passivefail2; + } + sin = (struct sockaddr_in *)&data6; + sin->sin_port = port; + + { + char *a, *p; + + a = (char *)&sin->sin_addr; + p = (char *)&sin->sin_port; + n = snprintf(sbuf, sizeof(sbuf), +"227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n", + UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + UC(p[0]), UC(p[1])); + write(dst, sbuf, n); + passivemode = 1; + return n; + } + } +#endif /* FAITH4 */ + } + + bad: + exit_failure(ERRSTR); + /*NOTREACHED*/ + return 0; /* to make gcc happy */ +} + +static int +ftp_copycommand(int src, int dst, enum state *state) +{ + int error, atmark; + int n; + unsigned int af, hal, ho[16], pal, po[2]; + char *a, *p; + char cmd[5], *param; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + enum state nstate; + char ch; + + /* OOB data handling */ + error = ioctl(src, SIOCATMARK, &atmark); + if (error != -1 && atmark == 1) { + n = read(src, rbuf, 1); + if (n == -1) + goto bad; + send(dst, rbuf, n, MSG_OOB); +#if 0 + n = read(src, rbuf, sizeof(rbuf)); + if (n == -1) + goto bad; + write(dst, rbuf, n); + return n; +#endif + } + + n = read(src, rbuf, sizeof(rbuf)); + if (n <= 0) + return n; + rbuf[n] = '\0'; + + if (n < 4) { + write(dst, rbuf, n); + return n; + } + + /* + * parse argument + */ + { + char *p, *q; + int i; + + p = rbuf; + q = cmd; + for (i = 0; i < 4; i++) { + if (!isalpha(*p)) { + /* invalid command */ + write(dst, rbuf, n); + return n; + } + *q++ = islower(*p) ? toupper(*p) : *p; + p++; + } + if (!isspace(*p)) { + /* invalid command */ + write(dst, rbuf, n); + return n; + } + *q = '\0'; + param = p; + /* param points to first non-command token, if any */ + while (*param && isspace(*param)) + param++; + if (!*param) + param = NULL; + } + + *state = NONE; + + if (strcmp(cmd, "LPRT") == 0 && param) { + /* + * LPRT -> PORT + */ + nstate = LPRT; + + close(wport4); + close(wport6); + close(port4); + close(port6); + wport4 = wport6 = port4 = port6 = -1; + + if (epsvall) { + n = snprintf(sbuf, sizeof(sbuf), "501 %s disallowed in EPSV ALL\r\n", + cmd); + write(src, sbuf, n); + return n; + } + + n = sscanf(param, +"%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u", + &af, &hal, &ho[0], &ho[1], &ho[2], &ho[3], + &ho[4], &ho[5], &ho[6], &ho[7], + &ho[8], &ho[9], &ho[10], &ho[11], + &ho[12], &ho[13], &ho[14], &ho[15], + &pal, &po[0], &po[1]); + if (n != 21 || af != 6 || hal != 16|| pal != 2) { + n = snprintf(sbuf, sizeof(sbuf), + "501 illegal parameter to LPRT\r\n"); + write(src, sbuf, n); + return n; + } + + /* keep LPRT parameter */ + memset(&data6, 0, sizeof(data6)); + sin6 = (struct sockaddr_in6 *)&data6; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + for (n = 0; n < 16; n++) + sin6->sin6_addr.s6_addr[n] = ho[n]; + sin6->sin6_port = htons(((po[0] & 0xff) << 8) | (po[1] & 0xff)); + +sendport: + /* get ready for active data connection */ + n = sizeof(data4); + error = getsockname(dst, (struct sockaddr *)&data4, &n); + if (error == -1) { +lprtfail: + n = snprintf(sbuf, sizeof(sbuf), + "500 could not translate to PORT\r\n"); + write(src, sbuf, n); + return n; + } + if (((struct sockaddr *)&data4)->sa_family != AF_INET) + goto lprtfail; + sin = (struct sockaddr_in *)&data4; + sin->sin_port = 0; + wport4 = socket(sin->sin_family, SOCK_STREAM, 0); + if (wport4 == -1) + goto lprtfail; + error = bind(wport4, (struct sockaddr *)sin, sin->sin_len); + if (error == -1) { + close(wport4); + wport4 = -1; + goto lprtfail; + } + error = listen(wport4, 1); + if (error == -1) { + close(wport4); + wport4 = -1; + goto lprtfail; + } + + /* transmit PORT */ + n = sizeof(data4); + error = getsockname(wport4, (struct sockaddr *)&data4, &n); + if (error == -1) { + close(wport4); + wport4 = -1; + goto lprtfail; + } + if (((struct sockaddr *)&data4)->sa_family != AF_INET) { + close(wport4); + wport4 = -1; + goto lprtfail; + } + sin = (struct sockaddr_in *)&data4; + a = (char *)&sin->sin_addr; + p = (char *)&sin->sin_port; + n = snprintf(sbuf, sizeof(sbuf), "PORT %d,%d,%d,%d,%d,%d\r\n", + UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + UC(p[0]), UC(p[1])); + write(dst, sbuf, n); + *state = nstate; + passivemode = 0; + return n; + } else if (strcmp(cmd, "EPRT") == 0 && param) { + /* + * EPRT -> PORT + */ + char *afp, *hostp, *portp; + struct addrinfo hints, *res; + + nstate = EPRT; + + close(wport4); + close(wport6); + close(port4); + close(port6); + wport4 = wport6 = port4 = port6 = -1; + + if (epsvall) { + n = snprintf(sbuf, sizeof(sbuf), "501 %s disallowed in EPSV ALL\r\n", + cmd); + write(src, sbuf, n); + return n; + } + + p = param; + ch = *p++; /* boundary character */ + afp = p; + while (*p && *p != ch) + p++; + if (!*p) { +eprtparamfail: + n = snprintf(sbuf, sizeof(sbuf), + "501 illegal parameter to EPRT\r\n"); + write(src, sbuf, n); + return n; + } + *p++ = '\0'; + hostp = p; + while (*p && *p != ch) + p++; + if (!*p) + goto eprtparamfail; + *p++ = '\0'; + portp = p; + while (*p && *p != ch) + p++; + if (!*p) + goto eprtparamfail; + *p++ = '\0'; + + n = sscanf(afp, "%d", &af); + if (n != 1 || af != 2) { + n = snprintf(sbuf, sizeof(sbuf), + "501 unsupported address family to EPRT\r\n"); + write(src, sbuf, n); + return n; + } + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + error = getaddrinfo(hostp, portp, &hints, &res); + if (error) { + n = snprintf(sbuf, sizeof(sbuf), + "501 EPRT: %s", gai_strerror(error)); + if (error == EAI_SYSTEM) + n += snprintf(sbuf, sizeof(sbuf), + ": %s", strerror(errno)); + n += snprintf(sbuf, sizeof(sbuf), "\r\n"); + + write(src, sbuf, n); + return n; + } + if (res->ai_next) { + n = snprintf(sbuf, sizeof(sbuf), + "501 EPRT: %s resolved to multiple addresses\r\n", hostp); + write(src, sbuf, n); + return n; + } + + memcpy(&data6, res->ai_addr, res->ai_addrlen); + + goto sendport; + } else if (strcmp(cmd, "LPSV") == 0 && !param) { + /* + * LPSV -> PASV + */ + nstate = LPSV; + + close(wport4); + close(wport6); + close(port4); + close(port6); + wport4 = wport6 = port4 = port6 = -1; + + if (epsvall) { + n = snprintf(sbuf, sizeof(sbuf), "501 %s disallowed in EPSV ALL\r\n", + cmd); + write(src, sbuf, n); + return n; + } + + /* transmit PASV */ + n = snprintf(sbuf, sizeof(sbuf), "PASV\r\n"); + write(dst, sbuf, n); + *state = LPSV; + passivemode = 0; /* to be set to 1 later */ + return n; + } else if (strcmp(cmd, "EPSV") == 0 && !param) { + /* + * EPSV -> PASV + */ + close(wport4); + close(wport6); + close(port4); + close(port6); + wport4 = wport6 = port4 = port6 = -1; + + n = snprintf(sbuf, sizeof(sbuf), "PASV\r\n"); + write(dst, sbuf, n); + *state = EPSV; + passivemode = 0; /* to be set to 1 later */ + return n; + } else if (strcmp(cmd, "EPSV") == 0 && param + && strncasecmp(param, "ALL", 3) == 0 && isspace(param[3])) { + /* + * EPSV ALL + */ + epsvall = 1; + n = snprintf(sbuf, sizeof(sbuf), "200 EPSV ALL command successful.\r\n"); + write(src, sbuf, n); + return n; +#ifdef FAITH4 + } else if (strcmp(cmd, "PORT") == 0 && param) { + /* + * PORT -> EPRT + */ + char host[NI_MAXHOST], serv[NI_MAXSERV]; + + nstate = PORT; + + close(wport4); + close(wport6); + close(port4); + close(port6); + wport4 = wport6 = port4 = port6 = -1; + + p = param; + n = sscanf(p, "%u,%u,%u,%u,%u,%u", + &ho[0], &ho[1], &ho[2], &ho[3], &po[0], &po[1]); + if (n != 6) { + n = snprintf(sbuf, sizeof(sbuf), + "501 illegal parameter to PORT\r\n"); + write(src, sbuf, n); + return n; + } + + memset(&data6, 0, sizeof(data6)); + sin = (struct sockaddr_in *)&data6; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = htonl( + ((ho[0] & 0xff) << 24) | ((ho[1] & 0xff) << 16) | + ((ho[2] & 0xff) << 8) | (ho[3] & 0xff)); + sin->sin_port = htons(((po[0] & 0xff) << 8) | (po[1] & 0xff)); + + /* get ready for active data connection */ + n = sizeof(data4); + error = getsockname(dst, (struct sockaddr *)&data4, &n); + if (error == -1) { +portfail: + n = snprintf(sbuf, sizeof(sbuf), + "500 could not translate to EPRT\r\n"); + write(src, sbuf, n); + return n; + } + if (((struct sockaddr *)&data4)->sa_family != AF_INET6) + goto portfail; + + ((struct sockaddr_in6 *)&data4)->sin6_port = 0; + sa = (struct sockaddr *)&data4; + wport4 = socket(sa->sa_family, SOCK_STREAM, 0); + if (wport4 == -1) + goto portfail; + error = bind(wport4, sa, sa->sa_len); + if (error == -1) { + close(wport4); + wport4 = -1; + goto portfail; + } + error = listen(wport4, 1); + if (error == -1) { + close(wport4); + wport4 = -1; + goto portfail; + } + + /* transmit EPRT */ + n = sizeof(data4); + error = getsockname(wport4, (struct sockaddr *)&data4, &n); + if (error == -1) { + close(wport4); + wport4 = -1; + goto portfail; + } + af = 2; + sa = (struct sockaddr *)&data4; + if (getnameinfo(sa, sa->sa_len, host, sizeof(host), + serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV)) { + close(wport4); + wport4 = -1; + goto portfail; + } + n = snprintf(sbuf, sizeof(sbuf), "EPRT |%d|%s|%s|\r\n", af, host, serv); + write(dst, sbuf, n); + *state = nstate; + passivemode = 0; + return n; + } else if (strcmp(cmd, "PASV") == 0 && !param) { + /* + * PASV -> EPSV + */ + + nstate = PASV; + + close(wport4); + close(wport6); + close(port4); + close(port6); + wport4 = wport6 = port4 = port6 = -1; + + /* transmit EPSV */ + n = snprintf(sbuf, sizeof(sbuf), "EPSV\r\n"); + write(dst, sbuf, n); + *state = PASV; + passivemode = 0; /* to be set to 1 later */ + return n; +#else /* FAITH4 */ + } else if (strcmp(cmd, "PORT") == 0 || strcmp(cmd, "PASV") == 0) { + /* + * reject PORT/PASV + */ + n = snprintf(sbuf, sizeof(sbuf), "502 %s not implemented.\r\n", cmd); + write(src, sbuf, n); + return n; +#endif /* FAITH4 */ + } else if (passivemode + && (strcmp(cmd, "STOR") == 0 + || strcmp(cmd, "STOU") == 0 + || strcmp(cmd, "RETR") == 0 + || strcmp(cmd, "LIST") == 0 + || strcmp(cmd, "NLST") == 0 + || strcmp(cmd, "APPE") == 0)) { + /* + * commands with data transfer. need to care about passive + * mode data connection. + */ + + if (ftp_passiveconn() < 0) { + n = snprintf(sbuf, sizeof(sbuf), "425 Cannot open data connetion\r\n"); + write(src, sbuf, n); + } else { + /* simply relay the command */ + write(dst, rbuf, n); + } + + *state = NONE; + return n; + } else { + /* simply relay it */ + *state = NONE; + write(dst, rbuf, n); + return n; + } + + bad: + exit_failure(ERRSTR); + /*NOTREACHED*/ + return 0; /* to make gcc happy */ +} diff --git a/usr.sbin/faithd/rsh.c b/usr.sbin/faithd/rsh.c new file mode 100644 index 0000000..c6e8357 --- /dev/null +++ b/usr.sbin/faithd/rsh.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 1997 and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/time.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <errno.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "faithd.h" + +char rshbuf[MSS]; + +int s_ctl, s_ctl6, s_rcv, s_snd; +int half; + +void +rsh_relay(int s_src, int s_dst) +{ + ssize_t n; + fd_set readfds; + int error; + struct timeval tv; + + FD_ZERO(&readfds); + FD_SET(s_src, &readfds); + tv.tv_sec = FAITH_TIMEOUT; + tv.tv_usec = 0; + error = select(256, &readfds, NULL, NULL, &tv); + if (error == -1) + exit_failure("select %d: %s", s_src, ERRSTR); + else if (error == 0) + exit_failure("connecion timeout"); + + n = read(s_src, rshbuf, sizeof(rshbuf)); + if (rshbuf[0] != 0) { + rsh_dual_relay(s_src, s_dst); + /* NOTREACHED */ + } + write(s_dst, rshbuf, n); + tcp_relay(s_src, s_dst, "rsh"); + /* NOTREACHED */ +} + +static void +relay(int src, int dst) +{ + int error; + ssize_t n; + int atmark; + + error = ioctl(s_rcv, SIOCATMARK, &atmark); + if (error != -1 && atmark == 1) { + n = read(s_rcv, rshbuf, 1); + if (n == 1) + send(s_snd, rshbuf, 1, MSG_OOB); + return; + } + + n = read(s_rcv, rshbuf, sizeof(rshbuf)); + + switch (n) { + case -1: + exit_failure(ERRSTR); + case 0: + if (s_rcv == src) { + /* half close */ + shutdown(dst, 1); + half = YES; + break; + } + close(src); + close(dst); + close(s_ctl); + close(s_ctl6); + exit_success("terminating rsh/contorol connections"); + break; + default: + write(s_snd, rshbuf, n); + } +} + +void +rsh_dual_relay(int s_src, int s_dst) +{ + fd_set readfds; + int len, s_wld, error; + struct sockaddr_storage ctladdr6; + struct sockaddr_storage ctladdr; + int port6 = 0, lport, lport6; + char *p; + struct timeval tv; + struct sockaddr *sa; + + half = NO; + s_rcv = s_src; + s_snd = s_dst; + syslog(LOG_INFO, "starting rsh connection"); + + for (p = rshbuf; *p; p++) + port6 = port6 * 10 + *p - '0'; + + len = sizeof(ctladdr6); + getpeername(s_src, (struct sockaddr *)&ctladdr6, &len); + if (((struct sockaddr *)&ctladdr6)->sa_family == AF_INET6) + ((struct sockaddr_in6 *)&ctladdr6)->sin6_port = htons(port6); + else + ((struct sockaddr_in *)&ctladdr6)->sin_port = htons(port6); + + s_wld = rresvport(&lport); + if (s_wld == -1) goto bad; + error = listen(s_wld, 1); + if (error == -1) goto bad; + snprintf(rshbuf, sizeof(rshbuf), "%d", lport); + write(s_dst, rshbuf, strlen(rshbuf)+1); + + len = sizeof(ctladdr); + s_ctl = accept(s_wld, (struct sockaddr *)&ctladdr, &len); + if (s_ctl == -1) goto bad; + close(s_wld); + + sa = (struct sockaddr *)&ctladdr6; + s_ctl6 = rresvport_af(&lport6, sa->sa_family); + if (s_ctl6 == -1) goto bad; + error = connect(s_ctl6, sa, sa->sa_len); + if (error == -1) goto bad; + + syslog(LOG_INFO, "starting rsh control connection"); + + for (;;) { + FD_ZERO(&readfds); + if (half == NO) + FD_SET(s_src, &readfds); + FD_SET(s_dst, &readfds); + FD_SET(s_ctl, &readfds); + FD_SET(s_ctl6, &readfds); + tv.tv_sec = FAITH_TIMEOUT; + tv.tv_usec = 0; + + error = select(256, &readfds, NULL, NULL, &tv); + if (error == -1) + exit_failure("select 4 sockets: %s", ERRSTR); + else if (error == 0) + exit_failure("connecion timeout"); + + if (half == NO && FD_ISSET(s_src, &readfds)) { + s_rcv = s_src; + s_snd = s_dst; + relay(s_src, s_dst); + } + if (FD_ISSET(s_dst, &readfds)) { + s_rcv = s_dst; + s_snd = s_src; + relay(s_src, s_dst); + } + if (FD_ISSET(s_ctl, &readfds)) { + s_rcv = s_ctl; + s_snd = s_ctl6; + relay(s_src, s_dst); + } + if (FD_ISSET(s_ctl6, &readfds)) { + s_rcv = s_ctl6; + s_snd = s_ctl; + relay(s_src, s_dst); + } + } + /* NOTREACHED */ + + bad: + exit_failure(ERRSTR); +} diff --git a/usr.sbin/faithd/tcp.c b/usr.sbin/faithd/tcp.c new file mode 100644 index 0000000..e1e1b32 --- /dev/null +++ b/usr.sbin/faithd/tcp.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 1997 and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "faithd.h" + +static char tcpbuf[16*1024]; + /* bigger than MSS and may be lesser than window size */ +static int tblen, tboff, oob_exists; +static fd_set readfds, writefds, exceptfds; +static char atmark_buf[2]; +static pid_t cpid = (pid_t)0; +static pid_t ppid = (pid_t)0; +static time_t child_lastactive = (time_t)0; +static time_t parent_lastactive = (time_t)0; + +static void sig_ctimeout __P((int)); +static void sig_child __P((int)); +static void notify_inactive __P((void)); +static void notify_active __P((void)); +static void send_data __P((int, int, const char *, int)); +static void relay __P((int, int, const char *, int)); + +/* + * Inactivity timer: + * - child side (ppid != 0) will send SIGUSR1 to parent every (FAITH_TIMEOUT/4) + * second if traffic is active. if traffic is inactive, don't send SIGUSR1. + * - parent side (ppid == 0) will check the last SIGUSR1 it have seen. + */ +static void +sig_ctimeout(int sig) +{ + /* parent side: record notification from the child */ + if (dflag) + syslog(LOG_DEBUG, "activity timer from child"); + child_lastactive = time(NULL); +} + +/* parent will terminate if child dies. */ +static void +sig_child(int sig) +{ + int status; + pid_t pid; + + pid = wait3(&status, WNOHANG, (struct rusage *)0); + if (pid && status) + syslog(LOG_WARNING, "child %d exit status 0x%x", pid, status); + exit_failure("terminate connection due to child termination"); +} + +static void +notify_inactive() +{ + time_t t; + + /* only on parent side... */ + if (ppid) + return; + + /* parent side should check for timeout. */ + t = time(NULL); + if (dflag) { + syslog(LOG_DEBUG, "parent side %sactive, child side %sactive", + (FAITH_TIMEOUT < t - parent_lastactive) ? "in" : "", + (FAITH_TIMEOUT < t - child_lastactive) ? "in" : ""); + } + + if (FAITH_TIMEOUT < t - child_lastactive + && FAITH_TIMEOUT < t - parent_lastactive) { + /* both side timeouted */ + signal(SIGCHLD, SIG_DFL); + kill(cpid, SIGTERM); + wait(NULL); + exit_failure("connection timeout"); + /* NOTREACHED */ + } +} + +static void +notify_active() +{ + if (ppid) { + /* child side: notify parent of active traffic */ + time_t t; + t = time(NULL); + if (FAITH_TIMEOUT / 4 < t - child_lastactive) { + if (kill(ppid, SIGUSR1) < 0) { + exit_failure("terminate connection due to parent termination"); + /* NOTREACHED */ + } + child_lastactive = t; + } + } else { + /* parent side */ + parent_lastactive = time(NULL); + } +} + +static void +send_data(int s_rcv, int s_snd, const char *service, int direction) +{ + int cc; + + if (oob_exists) { + cc = send(s_snd, atmark_buf, 1, MSG_OOB); + if (cc == -1) + goto retry_or_err; + oob_exists = 0; + FD_SET(s_rcv, &exceptfds); + } + + for (; tboff < tblen; tboff += cc) { + cc = write(s_snd, tcpbuf + tboff, tblen - tboff); + if (cc < 0) + goto retry_or_err; + } +#ifdef DEBUG + if (tblen) { + if (tblen >= sizeof(tcpbuf)) + tblen = sizeof(tcpbuf) - 1; + tcpbuf[tblen] = '\0'; + syslog(LOG_DEBUG, "from %s (%dbytes): %s", + direction == 1 ? "client" : "server", tblen, tcpbuf); + } +#endif /* DEBUG */ + tblen = 0; tboff = 0; + FD_CLR(s_snd, &writefds); + FD_SET(s_rcv, &readfds); + return; + retry_or_err: + if (errno != EAGAIN) + exit_failure("writing relay data failed: %s", ERRSTR); + FD_SET(s_snd, &writefds); +} + +static void +relay(int s_rcv, int s_snd, const char *service, int direction) +{ + int atmark, error, maxfd; + struct timeval tv; + fd_set oreadfds, owritefds, oexceptfds; + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + fcntl(s_snd, F_SETFD, O_NONBLOCK); + oreadfds = readfds; owritefds = writefds; oexceptfds = exceptfds; + FD_SET(s_rcv, &readfds); FD_SET(s_rcv, &exceptfds); + oob_exists = 0; + maxfd = (s_rcv > s_snd) ? s_rcv : s_snd; + + for (;;) { + tv.tv_sec = FAITH_TIMEOUT / 4; + tv.tv_usec = 0; + oreadfds = readfds; + owritefds = writefds; + oexceptfds = exceptfds; + error = select(maxfd + 1, &readfds, &writefds, &exceptfds, &tv); + if (error == -1) { + if (errno == EINTR) + continue; + exit_failure("select: %s", ERRSTR); + } else if (error == 0) { + readfds = oreadfds; + writefds = owritefds; + exceptfds = oexceptfds; + notify_inactive(); + continue; + } + + /* activity notification */ + notify_active(); + + if (FD_ISSET(s_rcv, &exceptfds)) { + error = ioctl(s_rcv, SIOCATMARK, &atmark); + if (error != -1 && atmark == 1) { + int cc; + oob_read_retry: + cc = read(s_rcv, atmark_buf, 1); + if (cc == 1) { + FD_CLR(s_rcv, &exceptfds); + FD_SET(s_snd, &writefds); + oob_exists = 1; + } else if (cc == -1) { + if (errno == EINTR) + goto oob_read_retry; + exit_failure("reading oob data failed" + ": %s", + ERRSTR); + } + } + } + if (FD_ISSET(s_rcv, &readfds)) { + relaydata_read_retry: + tblen = read(s_rcv, tcpbuf, sizeof(tcpbuf)); + tboff = 0; + + switch (tblen) { + case -1: + if (errno == EINTR) + goto relaydata_read_retry; + exit_failure("reading relay data failed: %s", + ERRSTR); + /* NOTREACHED */ + case 0: + /* to close opposite-direction relay process */ + shutdown(s_snd, 0); + + close(s_rcv); + close(s_snd); + exit_success("terminating %s relay", service); + /* NOTREACHED */ + default: + FD_CLR(s_rcv, &readfds); + FD_SET(s_snd, &writefds); + break; + } + } + if (FD_ISSET(s_snd, &writefds)) + send_data(s_rcv, s_snd, service, direction); + } +} + +void +tcp_relay(int s_src, int s_dst, const char *service) +{ + syslog(LOG_INFO, "starting %s relay", service); + + child_lastactive = parent_lastactive = time(NULL); + + cpid = fork(); + switch (cpid) { + case -1: + exit_failure("tcp_relay: can't fork grand child: %s", ERRSTR); + /* NOTREACHED */ + case 0: + /* child process: relay going traffic */ + ppid = getppid(); + /* this is child so reopen log */ + closelog(); + openlog(logname, LOG_PID | LOG_NOWAIT, LOG_DAEMON); + relay(s_src, s_dst, service, 1); + /* NOTREACHED */ + default: + /* parent process: relay coming traffic */ + ppid = (pid_t)0; + signal(SIGUSR1, sig_ctimeout); + signal(SIGCHLD, sig_child); + relay(s_dst, s_src, service, 0); + /* NOTREACHED */ + } +} diff --git a/usr.sbin/faithd/test/faithd.rb b/usr.sbin/faithd/test/faithd.rb new file mode 100644 index 0000000..683b504 --- /dev/null +++ b/usr.sbin/faithd/test/faithd.rb @@ -0,0 +1,312 @@ +# faithd, ruby version. requires v6-enabled ruby. +# +# highly experimental (not working right at all) and very limited +# functionality. +# +# $Id: faithd.rb,v 1.1.1.1 1999/08/08 23:29:31 itojun Exp $ +# $FreeBSD$ + +require "socket" +require "thread" + +# XXX should be derived from system headers +IPPROTO_IPV6 = 41 +IPV6_FAITH = 29 +DEBUG = true +DEBUG_LOOPBACK = true + +# TODO: OOB data handling +def tcpcopy(s1, s2, m) + STDERR.print "tcpcopy #{s1} #{s2}\n" if DEBUG + buf = "" + while TRUE + begin + buf = s1.sysread(100) + s2.syswrite(buf) + rescue EOFError + break + rescue IOError + break + end + end + STDERR.print "tcpcopy #{s1} #{s2} finished\n" if DEBUG + s1.shutdown(0) + s2.shutdown(1) +end + +def relay_ftp_passiveconn(s6, s4, dport6, dport4) + Thread.start do + d6 = TCPserver.open("::", dport6).accept + d4 = TCPsocket.open(s4.getpeer[3], dport4) + t = [] + t[0] = Thread.start do + tcpcopy(d6, d4) + end + t[1] = Thread.start do + tcpcopy(d4, d6) + end + for i in t + i.join + end + d4.close + d6.close + end +end + +def ftp_parse_2428(line) + if (line[0] != line[line.length - 1]) + return nil + end + t = line.split(line[0 .. 0]) # as string + if (t.size != 4 || t[1] !~ /^[12]$/ || t[3] !~ /^\d+$/) + return nil + end + return t[1 .. 3] +end + +def relay_ftp_command(s6, s4, state) + STDERR.print "relay_ftp_command start\n" if DEBUG + while TRUE + begin + STDERR.print "s6.gets\n" if DEBUG + line = s6.gets + STDERR.print "line is #{line}\n" if DEBUG + if line == nil + return nil + end + + # translate then copy + STDERR.print "line is #{line}\n" if DEBUG + if (line =~ /^EPSV\r\n/i) + STDERR.print "EPSV -> PASV\n" if DEBUG + line = "PASV\n" + state = "EPSV" + elsif (line =~ /^EPRT\s+(.+)\r\n/i) + t = ftp_parse_2428($1) + if t == nil + s6.puts "501 illegal parameter to EPRT\r\n" + next + end + + # some tricks should be here + s6.puts "501 illegal parameter to EPRT\r\n" + next + end + STDERR.print "fail: send #{line} as is\n" if DEBUG + s4.puts(line) + break + rescue EOFError + return nil + rescue IOError + return nil + end + end + STDERR.print "relay_ftp_command finish\n" if DEBUG + return state +end + +def relay_ftp_status(s4, s6, state) + STDERR.print "relay_ftp_status start\n" if DEBUG + while TRUE + begin + line = s4.gets + if line == nil + return nil + end + + # translate then copy + s6.puts(line) + + next if line =~ /^\d\d\d-/ + next if line !~ /^\d/ + + # special post-processing + case line + when /^221 / # result to QUIT + s4.shutdown(0) + s6.shutdown(1) + end + + break if (line =~ /^\d\d\d /) + rescue EOFError + return nil + rescue IOError + return nil + end + end + STDERR.print "relay_ftp_status finish\n" if DEBUG + return state +end + +def relay_ftp(sock, name) + STDERR.print "relay_ftp(#{sock}, #{name})\n" if DEBUG + while TRUE + STDERR.print "relay_ftp(#{sock}, #{name}) accepting\n" if DEBUG + s = sock.accept + STDERR.print "relay_ftp(#{sock}, #{name}) accepted #{s}\n" if DEBUG + Thread.start do + threads = [] + STDERR.print "accepted #{s} -> #{Thread.current}\n" if DEBUG + s6 = s + dest6 = s.addr[3] + if !DEBUG_LOOPBACK + t = s.getsockname.unpack("x8 x12 C4") + dest4 = "#{t[0]}.#{t[1]}.#{t[2]}.#{t[3]}" + port4 = s.addr[1] + else + dest4 = "127.0.0.1" + port4 = "ftp" + end + if DEBUG + STDERR.print "IPv6 dest: #{dest6} IPv4 dest: #{dest4}\n" if DEBUG + end + STDERR.print "connect to #{dest4} #{port4}\n" if DEBUG + s4 = TCPsocket.open(dest4, port4) + STDERR.print "connected to #{dest4} #{port4}, #{s4.addr[1]}\n" if DEBUG + state = 0 + while TRUE + # translate status line + state = relay_ftp_status(s4, s6, state) + break if state == nil + # translate command line + state = relay_ftp_command(s6, s4, state) + break if state == nil + end + STDERR.print "relay_ftp(#{sock}, #{name}) closing s4\n" if DEBUG + s4.close + STDERR.print "relay_ftp(#{sock}, #{name}) closing s6\n" if DEBUG + s6.close + STDERR.print "relay_ftp(#{sock}, #{name}) done\n" if DEBUG + end + end + STDERR.print "relay_ftp(#{sock}, #{name}) finished\n" if DEBUG +end + +def relay_tcp(sock, name) + STDERR.print "relay_tcp(#{sock}, #{name})\n" if DEBUG + while TRUE + STDERR.print "relay_tcp(#{sock}, #{name}) accepting\n" if DEBUG + s = sock.accept + STDERR.print "relay_tcp(#{sock}, #{name}) accepted #{s}\n" if DEBUG + Thread.start do + threads = [] + STDERR.print "accepted #{s} -> #{Thread.current}\n" if DEBUG + s6 = s + dest6 = s.addr[3] + if !DEBUG_LOOPBACK + t = s.getsockname.unpack("x8 x12 C4") + dest4 = "#{t[0]}.#{t[1]}.#{t[2]}.#{t[3]}" + port4 = s.addr[1] + else + dest4 = "127.0.0.1" + port4 = "telnet" + end + if DEBUG + STDERR.print "IPv6 dest: #{dest6} IPv4 dest: #{dest4}\n" if DEBUG + end + STDERR.print "connect to #{dest4} #{port4}\n" if DEBUG + s4 = TCPsocket.open(dest4, port4) + STDERR.print "connected to #{dest4} #{port4}, #{s4.addr[1]}\n" if DEBUG + [0, 1].each do |i| + threads[i] = Thread.start do + if (i == 0) + tcpcopy(s6, s4) + else + tcpcopy(s4, s6) + end + end + end + STDERR.print "relay_tcp(#{sock}, #{name}) wait\n" if DEBUG + for i in threads + STDERR.print "relay_tcp(#{sock}, #{name}) wait #{i}\n" if DEBUG + i.join + STDERR.print "relay_tcp(#{sock}, #{name}) wait #{i} done\n" if DEBUG + end + STDERR.print "relay_tcp(#{sock}, #{name}) closing s4\n" if DEBUG + s4.close + STDERR.print "relay_tcp(#{sock}, #{name}) closing s6\n" if DEBUG + s6.close + STDERR.print "relay_tcp(#{sock}, #{name}) done\n" if DEBUG + end + end + STDERR.print "relay_tcp(#{sock}, #{name}) finished\n" if DEBUG +end + +def usage() + STDERR.print "usage: #{$0} [-f] port...\n" +end + +#------------------------------------------------------------ + +$mode = "tcp" + +while ARGV[0] =~ /^-/ do + case ARGV[0] + when /^-f/ + $mode = "ftp" + else + usage() + exit 0 + end + ARGV.shift +end + +if ARGV.length == 0 + usage() + exit 1 +end + +ftpport = Socket.getservbyname("ftp") + +res = [] +for port in ARGV + t = Socket.getaddrinfo(nil, port, Socket::PF_INET6, Socket::SOCK_STREAM, + nil, Socket::AI_PASSIVE) + if (t.size <= 0) + STDERR.print "FATAL: getaddrinfo failed (port=#{port})\n" + exit 1 + end + res += t +end + +sockpool = [] +names = [] +listenthreads = [] + +res.each do |i| + s = TCPserver.new(i[3], i[1]) + n = Socket.getnameinfo(s.getsockname, Socket::NI_NUMERICHOST|Socket::NI_NUMERICSERV).join(" port ") + if i[6] == IPPROTO_IPV6 + s.setsockopt(i[6], IPV6_FAITH, 1) + end + s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1) + sockpool.push s + names.push n +end + +if DEBUG + (0 .. sockpool.size - 1).each do |i| + STDERR.print "listen[#{i}]: #{sockpool[i]} #{names[i]}\n" if DEBUG + end +end + +(0 .. sockpool.size - 1).each do |i| + listenthreads[i] = Thread.start do + if DEBUG + STDERR.print "listen[#{i}]: thread #{Thread.current}\n" if DEBUG + end + STDERR.print "listen[#{i}]: thread #{Thread.current}\n" if DEBUG + case $mode + when "tcp" + relay_tcp(sockpool[i], names[i]) + when "ftp" + relay_ftp(sockpool[i], names[i]) + end + end +end + +for i in listenthreads + i.join +end + +exit 0 |