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