chiark / gitweb /
Update VERSION
[userv-utils] / ipif / udptunnel
CommitLineData
46ab1c83 1#!/usr/bin/perl
2# Simple tunnel for userv-ipif tunnels.
3#
ae5f9b01 4# Example test invocation
5#
6# ./udptunnel -e nonce -e timestamp/10/30 -e pkcs5/8 -e blowfish-cbcmac/128 -e blowfish-cbc/128 -m -f ./udptunnel-forwarder davenant,Any anarres,Command 172.30.206.1,172.30.206.2,1000,cslip 15,70 '' '' rsh anarres things/userv-utils/ipif/udptunnel -f things/userv-utils/ipif/udptunnel-forwarder
7#
46ab1c83 8# usage:
9# udptunnel
84f87e82 10# [ -l[<local-command/arg>] ... .
62ccb81a 11# | -e <encryption-mech>[/<encryption-parameter>...]
12# | -m (`masquerade support': subcommand gets `Wait' instead of our addr/port)
13# | -d (`dump keys': when no subcmd, spew keys rather than reading them;
14# we always send keys to our subcmd if there is one)
d31674ca 15# | -Dcrypto (debug crypto - use with care, prints keys, packets &c on screen!)
62ccb81a 16# | -f<path-to-udptunnel-forwarder>
84f87e82 17# ...
18# ]
19# <public-local-addr>,<public-local-port>
20# <public-remote-addr>,<public-remote-port>
46ab1c83 21# <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
40950b54 22# <keepalive>,<timeout>[,<reannounce>]
4fa63592 23# <extra-nets-for-local-cmd> <extra-nets-for-remote-cmd>
46ab1c83 24# [ <remote-command> [<remote-args> ...] ]
25#
62ccb81a 26# proto may be slip or cslip
84f87e82 27#
62ccb81a 28# Any <..-addr> may also be hostname
84f87e82 29#
62ccb81a 30# Local addr's and ports may also be:
31# `Print' choose one ourselves and print both port and addr
32# `Any' choose one ourselves and do not print it
33# Remote addr's and ports may also be:
34# `Wait' wait to receive a packet before assigning address
35# `Command' run a subcommand and wait for it to tell us the values
36# When any addr or port is `Command' then <remote-command> must be specified.
46ab1c83 37#
62ccb81a 38# If <remote-command> is specified it should run udptunnel at the
46ab1c83 39# remote end; it will be invoked as
62ccb81a 40# <remote-command> [ <-e arguments passed along> ]
41# <public-remote-addr'>,<public-remote-port'>
42# <public-local-addr'>,<public-local-port'>
46ab1c83 43# <private-remote-addr>,<private-local-addr>,<mtu>,<proto>
40950b54 44# <keepalive>,<timeout>[,<reannounce>]
4fa63592 45# <extra-nets-for-remote-cmd> <extra-nets-for-local-cmd>
46ab1c83 46#
62ccb81a 47
48# If it was given Print for <public-remote-foo'>, this command's first
49# stdout output should be the real
50# <public-remote-addr>,<public-remote-port> pair (and of course this
51# udptunnel's output will be). It may then produce more stdout which,
52# if any, will be forwarded to the local end's stdout as debugging info.
53#
54# After this, if any encryption was specified, the encryption
55# parameters will be fed into its stdin. See the documentation in the
56# mech-*.c files for details of the parameters. udptunnel will
57# arrange to feed the keys fd of udptunnel-forwarder into the stdin of
58# the remote command.
59#
60# <public-remote-foo'> is as follows:
61# <public-remote-foo> <public-remote-foo'>
62# actual addr/port that addr/port
63# `Command' `Print'
64# `Wait' `Any'
65#
66# <public-local-foo'> is as follows:
67# <public-local-foo> <public-local-foo'> <public-local-foo'>
68# (-m not specified) (-m specified)
69# actual addr/port that addr/port `Wait'
62ccb81a 70# `Print' the chosen address `Wait'
4fa63592 71# `Any' `Wait' for addr, `Wait'
72# chosen port for port
62ccb81a 73#
46ab1c83 74# udptunnel will userv ipif locally, as
217afbe6 75# userv root ipif <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
4fa63592 76# <extra-nets-for-local-cmd>
62ccb81a 77# or, if -l was given, userv root ipif is replaced with the argument(s)
78# following -l option(s) until `.'.
79#
80# udptunnel will also run udptunnel-forwarder with appropriate options
81#
82# recommended encryption parameters are:
83# -e nonce (prepend 32 bit counter)
84# -e timestamp/<max-skew>/<max-age> (prepend 32 bit time_t, and check on receipt)
85# -e pkcs5/8 (pad as per PKCS#5 to 8-byte boundary)
86# -e blowfish-cbcmac/128 (prepend CBC MAC with random IV and 128 bit key)
87# -e blowfish-cbc/128 (encrypt with CBC, random IV and 128 bit key)
88# where <max-skew> is perhaps 10 and <max-age> perhaps 30.
46ab1c83 89
62ccb81a 90# Copyright (C) 1999-2000 Ian Jackson
12ecfeab 91#
92# This is free software; you can redistribute it and/or modify it
93# under the terms of the GNU General Public License as published by
94# the Free Software Foundation; either version 2 of the License, or
95# (at your option) any later version.
96#
97# This program is distributed in the hope that it will be useful, but
98# WITHOUT ANY WARRANTY; without even the implied warranty of
99# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
100# General Public License for more details.
101#
102# You should have received a copy of the GNU General Public License
103# along with userv-utils; if not, write to the Free Software
104# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
105#
40950b54 106# $Id: udptunnel,v 1.18 2000/06/09 20:27:14 ian Exp $
12ecfeab 107
46ab1c83 108use Socket;
109use POSIX;
110use Fcntl;
111
112$progname= $0; $progname =~ s,.*/,,;
113$|=1;
114
115chomp($hostname= `uname -n`);
116$? and die "$progname: cannot get hostname (uname failed with code $?)\n";
117
118sub quit ($) { die "$progname - $hostname: fatal error: $_[0]\n"; }
119sub debug ($) { print "$progname - $hostname: debug: $_[0]\n"; }
62ccb81a 120sub fail ($) { quit("unexpected system call failure: $_[0]: $!"); }
46ab1c83 121sub warning ($) { warn "$progname - $hostname: $_[0]\n"; }
122
123sub eat_addr_port ($) {
124 my ($x) = @_;
84f87e82 125 @ARGV or quit("<addr>,<port> missing");
46ab1c83 126 $_= shift(@ARGV);
62ccb81a 127 (m/^$x,/i && m/^[a-z]/ || m/,$x$/i && m/,[a-z]/)
128 and warning("$_: use Mixed Case for special values");
129 m/^([0-9a-z][0-9a-z-+.]+|$x)\,(\d+|$x)$/
130 or quit("$_: <host/addr>,<port> bad syntax".
131 (m/[A-Z]/ ? ' (use lowercase for hostnames)' : ''));
46ab1c83 132 return ($1,$2);
133}
134sub conv_host_addr ($) {
62ccb81a 135 my ($s,$r,@h) = @_;
136 return INADDR_ANY() if $s =~ m/^[A-Z][a-z]/;
137 return $r if defined($r= inet_aton($s));
138 @h= gethostbyname($s) or quit("$s: cannot get address");
139 $h[2] eq &AF_INET or quit("$s: address is not IPv4");
140 @h < 5 or quit("$s: name maps to no addresses");
141 $r= $h[4];
142 @h == 5 or warning("$s: name has several addresses, using ".inet_ntoa($r));
46ab1c83 143 return $r;
144}
217afbe6 145sub conv_port_number ($) {
46ab1c83 146 my ($s,$r) = @_;
62ccb81a 147 return 0 if $s =~ m/^[A-Z][a-z]/;
148 $r= $s+0;
149 $r>0 && $r<65536 or quit("$s: port out of range");
46ab1c83 150 return $r;
151}
62ccb81a 152sub show_addr ($) {
153 my ($s,@s) = @_;
154 @s= unpack_sockaddr_in($s);
155 return inet_ntoa($s[1]);
156}
157sub show_port ($) {
46ab1c83 158 my ($s,@s) = @_;
159 @s= unpack_sockaddr_in($s);
62ccb81a 160 return $s[0];
161}
162sub show_addr_port ($) {
163 my ($s) = @_;
164 return show_addr($s).','.show_port($s);
46ab1c83 165}
62ccb81a 166sub arg_value ($$) {
244a0e99 167 my ($val,$opt) = @_;
62ccb81a 168 $_= '-';
169 return $val if length $val;
170 @ARGV or quit("$opt needs value");
171 return shift @ARGV;
172}
173
abe75cda 174@lcmd= ();
62ccb81a 175@encryption= ();
176$masq= 0;
177$dump= 0;
178$fcmd= 'udptunnel-forwarder';
d31674ca 179$xfwdopts= '';
abe75cda 180
181while ($ARGV[0] =~ m/^-/) {
182 $_= shift @ARGV;
62ccb81a 183 last if m/^--?$/;
184 while (!m/^-$/) {
185 if (s/^-l//) {
186 push @lcmd,$_ if length;
187 while (@ARGV && ($_= shift @ARGV) ne '.') { push @lcmd, $_; }
188 $_= '-'
189 } elsif (s/^-f//) {
190 $fcmd= arg_value($_,'-f');
191 } elsif (s/^-e//) {
192 $encrarg= arg_value($_,'-e');
244a0e99 193 push @remoteopts, "-e$encrarg";
194 @thisencryption= split m#/#, $encrarg;
8bb9d875 195 $thisencryption[0] =~ s/^/\|/;
244a0e99 196 push @encryption, @thisencryption;
62ccb81a 197 } elsif (s/^-m/-/) {
198 $masq= 1;
199 } elsif (s/^-d/-/) {
200 $dump= 1;
d31674ca 201 } elsif (s/^-Dcrypto$/-/) {
202 $xfwdopts.= 'K';
244a0e99 203 push @remoteopts, '-Dcrypto';
62ccb81a 204 } else {
205 quit("unknown option \`$_'");
206 }
abe75cda 207 }
208}
209
62ccb81a 210# Variables \$[lr]a?p?(|s|d|r)
211# Local/Remote Address&/Port
212# actualvalue/Specified/Displaypassdown/fromRemote/passtoForwarder
213#
214($las,$lps)= eat_addr_port('Print|Any');
46ab1c83 215$la= conv_host_addr($las);
216$lp= conv_port_number($lps);
217$ls= pack_sockaddr_in $lp,$la;
218
62ccb81a 219($ras,$rps)= eat_addr_port('Wait|Command');
220$ra= conv_host_addr($ras);
46ab1c83 221$rp= conv_port_number($rps);
62ccb81a 222$rs= pack_sockaddr_in $rp,$ra;
46ab1c83 223
224$_= shift @ARGV;
225m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/
226 or quit("lvaddr,rvaddr,mtu,proto missing or bad syntax or proto not [c]slip");
217afbe6 227($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4);
46ab1c83 228
229$_= shift @ARGV;
40950b54 230if (m/^(\d+),(\d+)$/) {
231 ($keepalive,$timeout,$reannounce)= ($1+0,$2+0,0);
232 $ka_to_ra= "$keepalive,$timeout";
233} elsif (m/^(\d+),(\d+),(\d+)$/) {
234 ($keepalive,$timeout,$reannounce)= ($1+0,$2+0,$3);
235 "$keepalive,$timeout",
236 $ka_to_ra= "$keepalive,$timeout,$reannounce";
237} else {
238 quit("keepalive,timeout missing or bad syntax");
239}
46ab1c83 240$keepalive && ($timeout > $keepalive*2) or quit("timeout must be < 2*keepalive")
241 if $timeout;
242
62ccb81a 243# Variables \$[lr]exn
244# Local/Remote Extra Nets
245$lexn= shift @ARGV;
246$rexn= shift @ARGV;
46ab1c83 247
248defined($udp= getprotobyname('udp')) or fail("getprotobyname udp");
249
250socket(L,PF_INET,SOCK_DGRAM,$udp) or fail("socket");
251bind(L,$ls) or quit("bind failed: $!");
252defined($ls= getsockname(L)) or fail("getsockname");
62ccb81a 253$lad= show_addr($ls);
254$lpd= show_port($ls);
255$lapd= "$lad,$lpd";
256
257print "$lapd\n" or fail("print addr/port") if ($las eq 'Print' || $lps eq 'Print');
46ab1c83 258
62ccb81a 259$rapcmd= ($ras eq 'Command' || $rps eq 'Command');
260quit("need remote-command if Command for remote addr/port") if $rapcmd && !@ARGV;
261
262sub xform_remote ($$) {
263 my ($showed,$spec) = @_;
264 return 'Print' if $spec eq 'Command';
265 return 'Any' if $spec eq 'Wait';
266 return $showed;
267}
268
269if (@ARGV) {
270 warning("-d specified with remote command, ignoring") if $dump;
271 $dump= 1;
272
273 $rad= xform_remote(show_addr($rs),$ras);
274 $rpd= xform_remote(show_port($rs),$rps);
275 @rcmd= (@ARGV,
244a0e99 276 @remoteopts,
62ccb81a 277 "$rad,$rpd",
4fa63592 278 $masq ? 'Wait,Wait' : $las eq 'Any' ? "Wait,$lpd" : $lapd,
62ccb81a 279 "$rva,$lva,$mtu,$proto",
40950b54 280 $ka_to_ra,
62ccb81a 281 $rexn, $lexn);
217afbe6 282 debug("remote command @rcmd");
62ccb81a 283
284 if ($rapcmd) {
285 pipe(RAPREAD,RCMDREADSUB) or fail("pipe");
46ab1c83 286 }
244a0e99 287 pipe(RCMDWRITESUB,DUMPKEYS) or fail("pipe");
62ccb81a 288 defined($c_rcmd= fork) or fail("fork for remote");
289 if (!$c_rcmd) {
244a0e99 290 open STDIN, "<&RCMDWRITESUB" or fail("reopen stdin for remote command");
62ccb81a 291 open STDOUT, ">&RCMDREADSUB" or fail("reopen stdout for remote command")
292 if $rapcmd;
293 close RAPREAD if $rapcmd;
294 close DUMPKEYS;
295 close RCMDWRITESUB;
296 close RCMDREADSUB;
297 close L;
298 exec @rcmd; fail("failed to execute remote command $rcmd[0]");
46ab1c83 299 }
62ccb81a 300 close RCMDWRITESUB;
301
302 if ($rapcmd) {
303 close RCMDREADSUB if $rapcmd;
d32c2ba0 304 $_= '';
305 while (!m/\n/) {
306 $!=0;
307 defined($nread= sysread(RAPREAD,$_,1,length))
308 or fail("read from remote command");
309 if (!$nread) {
310 close DUMPKEYS;
311 close RAPREAD;
312 waitpid $c_rcmd,0 or fail("wait for remote command");
313 quit($? ? "remote command failed (code $?)" :
314 "no details received from remote");
315 }
316 }
317 chomp;
318 m/^([.0-9]+)\,(\d+)$/ or quit("invalid details from remote end: \`$_'");
319 ($rar,$rpr) = ($1,$2);
320 $ra= conv_host_addr($rar);
321 $rp= conv_port_number($rpr);
62ccb81a 322
323 defined($c_catremdebug= fork) or fail("fork for cat remote debug");
324 if (!$c_catremdebug) {
325 open(STDIN,"<&RAPREAD") or fail("redirect remote debug");
62ccb81a 326 close DUMPKEYS;
327 close L;
328 exec "cat"; fail("execute cat");
329 }
330 close RAPREAD;
46ab1c83 331 }
62ccb81a 332} elsif ($dump) {
333 open DUMPKEYS, ">&STDOUT" or fail("reopen stdout for key material");
334 $dump= 1;
46ab1c83 335} else {
62ccb81a 336 open DUMPKEYS, "<&STDIN" or fail("reopen stdout for key material");
46ab1c83 337}
338
339$rs= pack_sockaddr_in $rp,$ra;
46ab1c83 340
62ccb81a 341if ($ras eq 'Wait' || $rps eq 'Wait') {
342 @rapf= ('');
343 $rapd= ('Wait,Wait');
344} else {
345 @rapf= (show_addr($rs), show_port($rs));
346 $rapd= show_addr_port($rs);
347}
abe75cda 348@lcmd= qw(userv root ipif) unless @lcmd;
349
62ccb81a 350debug("using remote $rapd local $lapd");
351push @lcmd, ("$lva,$rva,$mtu,$proto",$lexn);
352debug("local command @lcmd.");
46ab1c83 353
354pipe(UR,UW) or fail("up pipe");
355pipe(DR,DW) or fail("down pipe");
356
62ccb81a 357defined($c_lcmd= fork) or fail("fork for local command");
358if (!$c_lcmd) {
46ab1c83 359 close UR; close DW;
217afbe6 360 open(STDIN,"<&DR") or fail("reopen stdin for packets");
361 open(STDOUT,">&UW") or fail("reopen stdout for packets");
abe75cda 362 exec @lcmd;
363 quit("cannot execute $lcmd[0]: $!");
46ab1c83 364}
365close UW;
366close DR;
367
d31674ca 368$xfwdopts.= 'w' if $dump;
369
370@fcmd= ($fcmd, $xfwdopts,
371 fileno(L), fileno(DW), fileno(UR), fileno(DUMPKEYS),
40950b54 372 $mtu, $keepalive, $timeout, $reannounce,
62ccb81a 373 @rapf,
62ccb81a 374 @encryption);
375debug("forwarding command @fcmd.");
46ab1c83 376
62ccb81a 377defined($c_fwd= fork) or fail("fork for udptunnel-forwarder");
378if (!$c_fwd) {
244a0e99 379 foreach $fd (qw(L DW UR DUMPKEYS)) {
62ccb81a 380 fcntl($fd, F_SETFD, 0) or fail("set no-close-on-exec $fd");
381 }
382 exec @fcmd; fail("cannot execute $fcmd[0]");
46ab1c83 383}
384
62ccb81a 385close L;
386close DUMPKEYS;
387close UR;
388close DW;
46ab1c83 389
62ccb81a 390%procs= ($c_fwd, 'forwarder',
391 $c_lcmd, 'local command');
392$procs{$c_rcmd}= 'remote command' if $c_rcmd;
393$procs{$c_catremdebug}= 'debug cat' if $c_catremdebug;
46ab1c83 394
62ccb81a 395$estatus= 0;
217afbe6 396
62ccb81a 397while (keys %procs) {
398 ($c= wait) >0 or
399 fail("wait failed (expecting ". join('; ',keys %procs). ")");
244a0e99 400 $status= $?;
401 warning("unexpected child reaped: pid $c, code $status"), next
62ccb81a 402 unless exists $procs{$c};
403 $str= $procs{$c};
404 delete $procs{$c};
244a0e99 405 $status ? warning("subprocess $str failed with code $status")
62ccb81a 406 : debug("subprocess $str finished");
407 if ($c==$c_lcmd || $c==$c_fwd || $c==$c_rcmd) {
408 kill 15, grep (exists $procs{$_}, $c_fwd, $c_rcmd);
46ab1c83 409 }
62ccb81a 410 $estatus=1 unless $c == $c_catremdebug;
46ab1c83 411}
62ccb81a 412
413debug("all processes terminated, exiting with status $estatus");
414
415exit $estatus;