add faithd, IPv6-to-IPv4 tcp relay translator.

utilizes pseudo-device "faith".
This commit is contained in:
itojun 1999-07-13 22:16:48 +00:00
parent 1e44889b75
commit e5db40b6de
9 changed files with 2911 additions and 3 deletions

View File

@ -1,4 +1,4 @@
# $NetBSD: Makefile,v 1.119 1999/07/12 22:12:06 thorpej Exp $
# $NetBSD: Makefile,v 1.120 1999/07/13 22:16:48 itojun Exp $
# from: @(#)Makefile 5.20 (Berkeley) 6/12/93
# XXX Temporary for NO_SENDMAIL and BUILD_POSTFIX
@ -25,8 +25,8 @@ SUBDIR= ac accton amd apm apmd arp bad144 bind bootp catman \
zdump zic
# IPv6
SUBDIR+=gifconfig ifmcstat mld6query ndp rip6query route6d rtadvd setkey \
traceroute6
SUBDIR+=faithd gifconfig ifmcstat mld6query ndp rip6query route6d rtadvd \
setkey traceroute6
# ATM PVC
SUBDIR+=pvcsif pvctxctl

9
usr.sbin/faithd/Makefile Normal file
View File

@ -0,0 +1,9 @@
# $NetBSD: Makefile,v 1.1 1999/07/13 22:16:49 itojun Exp $
PROG= faithd
SRCS= faithd.c tcp.c ftp.c rsh.c
MAN= faithd.8
#CFLAGS+= -DFAITH4
.include <bsd.prog.mk>

139
usr.sbin/faithd/README Normal file
View File

@ -0,0 +1,139 @@
Configuring FAITH IPv6-to-IPv4 TCP relay
Kazu Yamamoto and Jun-ichiro itojun Hagino
$NetBSD: README,v 1.1 1999/07/13 22:16:49 itojun Exp $
KAME Id: README,v 1.1.2.5.2.2.10.3 1999/03/31 04:40:42 itojun Exp
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
address translation.
TCP connections has to be made from IPv6 node to 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 sshd
<<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.
(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.netbsd.org:
3ffe:0501:1234:ffff::140.160.140.252 www.netbsd.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

242
usr.sbin/faithd/faithd.8 Normal file
View File

@ -0,0 +1,242 @@
.\" 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.
.\"
.\" $NetBSD: faithd.8,v 1.1 1999/07/13 22:16:49 itojun Exp $
.\" KAME Id: faithd.8,v 1.1.2.6.4.5.2.5 1999/03/31 04:40:46 itojun Exp
.\"
.Dd May 17, 1998
.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 rogind 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
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
.Li /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 route 8 ,
.Xr sysctl 8
.\"
.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.

810
usr.sbin/faithd/faithd.c Normal file
View File

@ -0,0 +1,810 @@
/* $NetBSD: faithd.c,v 1.1 1999/07/13 22:16:49 itojun Exp $ */
/*
* 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.
*/
/*
* User level translator from IPv6 to IPv4.
*
* Usage: faithd [<port> <progpath> <arg1(progname)> <arg2> ...]
* e.g. faithd telnet /usr/local/v6/sbin/telnetd telnetd
*/
#define ss_len __ss_len
#define ss_family __ss_family
#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>
#ifdef __FreeBSD__
#include <libutil.h>
#endif
#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];
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));
sprintf(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)
exit_error("gaddrinfo: %s", gai_strerror(error));
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: %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: %s", ERRSTR);
}
#endif
#endif /* FAITH4 */
error = setsockopt(s_wld, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (error == -1)
exit_error("setsockopt: %s", ERRSTR);
error = setsockopt(s_wld, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on));
if (error == -1)
exit_error("setsockopt: %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();
sprintf(logname, "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(logname);
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;
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, dstaddr6.__ss_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
*/
if (dstaddr6.__ss_family == 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");
#ifdef FAITH4
} else if (dstaddr6.__ss_family == 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");
#endif
} else {
close(s_src);
exit_error("family not supported");
}
getnameinfo((struct sockaddr *)&dstaddr4, dstaddr4.__ss_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 (dstaddr4.__ss_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, dstaddr4.__ss_family);
break;
default:
if (pflag)
s_dst = rresvport_af(&nresvport, dstaddr4.__ss_family);
else
s_dst = socket(dstaddr4.__ss_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: %s", ERRSTR);
error = setsockopt(s_src, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
if (error == -1)
exit_error("setsockopt: %s", ERRSTR);
error = setsockopt(s_dst, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
if (error == -1)
exit_error("setsockopt: %s", ERRSTR);
error = connect(s_dst, (struct sockaddr *)&dstaddr4, dstaddr4.__ss_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 (dst->sin6_addr.s6_addr32[0] == faith_prefix.s6_addr32[0]
&& dst->sin6_addr.s6_addr32[1] == faith_prefix.s6_addr32[1]
&& dst->sin6_addr.s6_addr32[2] == faith_prefix.s6_addr32[2])
return 1;
return 0;
#else
struct myaddrs *p;
struct sockaddr_in6 *sin6;
struct sockaddr_in *sin4;
for (p = myaddrs; p; p = p->next) {
sin6 = (struct sockaddr_in6 *)p->addr;
sin4 = (struct sockaddr_in *)p->addr;
/* ugly! */
if (p->addr->sa_len == dst->sa_len
&& p->addr->sa_family == dst->sa_family) {
struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst;
struct sockaddr_in *dst4 = (struct sockaddr_in *)dst;
switch (dst->sa_family) {
case AF_INET6:
if (sin6->sin6_scope_id == dst6->sin6_scope_id
&& memcmp(&sin6->sin6_addr, &dst6->sin6_addr, 16) == 0)
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;
dst4->sin_addr.s_addr = dst6->sin6_addr.s6_addr32[3];
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));
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);
vsprintf(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);
vsprintf(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);
vsprintf(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(sin6->sin6_addr.s6_addr16[1]);
sin6->sin6_addr.s6_addr16[1] = 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);
}

70
usr.sbin/faithd/faithd.h Normal file
View File

@ -0,0 +1,70 @@
/* $NetBSD: faithd.h,v 1.1 1999/07/13 22:16:49 itojun Exp $ */
/*
* 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.
*/
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*/

1130
usr.sbin/faithd/ftp.c Normal file

File diff suppressed because it is too large Load Diff

208
usr.sbin/faithd/rsh.c Normal file
View File

@ -0,0 +1,208 @@
/* $NetBSD: rsh.c,v 1.1 1999/07/13 22:16:49 itojun Exp $ */
/*
* 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.
*/
#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;
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 (ctladdr6.__ss_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);
s_ctl6 = rresvport_af(&lport6, ctladdr6.__ss_family);
if (s_ctl6 == -1) goto bad;
error = connect(s_ctl6, (struct sockaddr *)&ctladdr6, ctladdr6.__ss_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);
}

300
usr.sbin/faithd/tcp.c Normal file
View File

@ -0,0 +1,300 @@
/* $NetBSD: tcp.c,v 1.1 1999/07/13 22:16:49 itojun Exp $ */
/*
* 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.
*/
#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 */
}
}