chiark / gitweb /
@@ -1,3 +1,12 @@
[userv-utils.git] / ipif / udptunnel
1 #!/usr/bin/perl
2 # Simple tunnel for userv-ipif tunnels.
3 #
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 #
8 # usage:
9 #  udptunnel
10 #        [ -l[<local-command/arg>] ... .
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)
15 #        | -Dcrypto  (debug crypto - use with care, prints keys, packets &c on screen!)
16 #        | -f<path-to-udptunnel-forwarder>
17 #          ...
18 #        ]
19 #            <public-local-addr>,<public-local-port>
20 #            <public-remote-addr>,<public-remote-port>
21 #            <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
22 #            <keepalive>,<timeout>[,<reannounce>]
23 #            <extra-nets-for-local-cmd> <extra-nets-for-remote-cmd>
24 #          [ <remote-command> [<remote-args> ...] ]
25 #
26 # proto may be slip or cslip
27 #
28 # Any <..-addr> may also be hostname
29 #
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.
37 #
38 # If <remote-command> is specified it should run udptunnel at the
39 # remote end; it will be invoked as
40 #    <remote-command> [ <-e arguments passed along> ]
41 #                     <public-remote-addr'>,<public-remote-port'>
42 #                     <public-local-addr'>,<public-local-port'>
43 #                     <private-remote-addr>,<private-local-addr>,<mtu>,<proto>
44 #                     <keepalive>,<timeout>[,<reannounce>]
45 #                     <extra-nets-for-remote-cmd> <extra-nets-for-local-cmd>
46 #
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'
70 #   `Print'              the chosen address      `Wait'
71 #   `Any'                `Wait' for addr,        `Wait'
72 #                         chosen port for port
73 #
74 # udptunnel will userv ipif locally, as
75 #    userv root ipif <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
76 #                    <extra-nets-for-local-cmd>
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.
89
90 # Copyright (C) 1999-2000 Ian Jackson
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 #
106 # $Id$
107
108 use Socket;
109 use POSIX;
110 use Fcntl;
111
112 $progname= $0; $progname =~ s,.*/,,;
113 $|=1;
114
115 chomp($hostname= `uname -n`);
116 $? and die "$progname: cannot get hostname (uname failed with code $?)\n";
117
118 sub quit ($) { die "$progname - $hostname: fatal error: $_[0]\n"; }
119 sub debug ($) { print "$progname - $hostname: debug: $_[0]\n"; }
120 sub fail ($) { quit("unexpected system call failure: $_[0]: $!"); }
121 sub warning ($) { warn "$progname - $hostname: $_[0]\n"; }
122
123 sub eat_addr_port ($) {
124     my ($x) = @_;
125     @ARGV or quit("<addr>,<port> missing");
126     $_= shift(@ARGV);
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)' : ''));
132     return ($1,$2);
133 }
134 sub conv_host_addr ($) {
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));
143     return $r;
144 }
145 sub conv_port_number ($) {
146     my ($s,$r) = @_;
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");
150     return $r;
151 }
152 sub show_addr ($) {
153     my ($s,@s) = @_;
154     @s= unpack_sockaddr_in($s);
155     return inet_ntoa($s[1]);
156 }
157 sub show_port ($) {
158     my ($s,@s) = @_;
159     @s= unpack_sockaddr_in($s);
160     return $s[0];
161 }
162 sub show_addr_port ($) {
163     my ($s) = @_;
164     return show_addr($s).','.show_port($s);
165 }
166 sub arg_value ($$) {
167     my ($val,$opt) = @_;
168     $_= '-';
169     return $val if length $val;
170     @ARGV or quit("$opt needs value");
171     return shift @ARGV;
172 }
173
174 @lcmd= ();
175 @encryption= ();
176 $masq= 0;
177 $dump= 0;
178 $fcmd= 'udptunnel-forwarder';
179 $xfwdopts= '';
180
181 while ($ARGV[0] =~ m/^-/) {
182     $_= shift @ARGV;
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');
193             push @remoteopts, "-e$encrarg";
194             @thisencryption= split m#/#, $encrarg;
195             $thisencryption[0] =~ s/^/\|/;
196             push @encryption, @thisencryption;
197         } elsif (s/^-m/-/) {
198             $masq= 1;
199         } elsif (s/^-d/-/) {
200             $dump= 1;
201         } elsif (s/^-Dcrypto$/-/) {
202             $xfwdopts.= 'K';
203             push @remoteopts, '-Dcrypto';
204         } else {
205             quit("unknown option \`$_'");
206         }
207     }
208 }
209
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');
215 $la= conv_host_addr($las);
216 $lp= conv_port_number($lps);
217 $ls= pack_sockaddr_in $lp,$la;
218
219 ($ras,$rps)= eat_addr_port('Wait|Command');
220 $ra= conv_host_addr($ras);
221 $rp= conv_port_number($rps);
222 $rs= pack_sockaddr_in $rp,$ra;
223
224 $_= shift @ARGV;
225 m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/
226     or quit("lvaddr,rvaddr,mtu,proto missing or bad syntax or proto not [c]slip");
227 ($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4);
228
229 $_= shift @ARGV;
230 if (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 }
240 $keepalive && ($timeout > $keepalive*2) or quit("timeout must be < 2*keepalive")
241     if $timeout;
242
243 # Variables \$[lr]exn
244 # Local/Remote Extra Nets
245 $lexn= shift @ARGV;
246 $rexn= shift @ARGV;
247
248 defined($udp= getprotobyname('udp')) or fail("getprotobyname udp");
249
250 socket(L,PF_INET,SOCK_DGRAM,$udp) or fail("socket");
251 bind(L,$ls) or quit("bind failed: $!");
252 defined($ls= getsockname(L)) or fail("getsockname");
253 $lad= show_addr($ls);
254 $lpd= show_port($ls);
255 $lapd= "$lad,$lpd";
256
257 print "$lapd\n" or fail("print addr/port") if ($las eq 'Print' || $lps eq 'Print');
258
259 $rapcmd= ($ras eq 'Command' || $rps eq 'Command');
260 quit("need remote-command if Command for remote addr/port") if $rapcmd && !@ARGV;
261
262 sub 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
269 if (@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,
276             @remoteopts,
277             "$rad,$rpd",
278             $masq ? 'Wait,Wait' : $las eq 'Any' ? "Wait,$lpd" : $lapd,
279             "$rva,$lva,$mtu,$proto",
280             $ka_to_ra,
281             $rexn, $lexn);
282     debug("remote command @rcmd");
283
284     if ($rapcmd) {
285         pipe(RAPREAD,RCMDREADSUB) or fail("pipe");
286     }
287     pipe(RCMDWRITESUB,DUMPKEYS) or fail("pipe");
288     defined($c_rcmd= fork) or fail("fork for remote");
289     if (!$c_rcmd) {
290         open STDIN, "<&RCMDWRITESUB" or fail("reopen stdin for remote command");
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]");
299     }
300     close RCMDWRITESUB;
301     
302     if ($rapcmd) {
303         close RCMDREADSUB if $rapcmd;
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);
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");
326             close DUMPKEYS;
327             close L;
328             exec "cat"; fail("execute cat");
329         }
330         close RAPREAD;
331     }
332 } elsif ($dump) {
333     open DUMPKEYS, ">&STDOUT" or fail("reopen stdout for key material");
334     $dump= 1;
335 } else {
336     open DUMPKEYS, "<&STDIN" or fail("reopen stdout for key material");
337 }
338
339 $rs= pack_sockaddr_in $rp,$ra;
340
341 if ($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 }
348 @lcmd= qw(userv root ipif) unless @lcmd;
349
350 debug("using remote $rapd local $lapd");
351 push @lcmd, ("$lva,$rva,$mtu,$proto",$lexn);
352 debug("local command @lcmd.");
353
354 pipe(UR,UW) or fail("up pipe");
355 pipe(DR,DW) or fail("down pipe");
356
357 defined($c_lcmd= fork) or fail("fork for local command");
358 if (!$c_lcmd) {
359     close UR; close DW;
360     open(STDIN,"<&DR") or fail("reopen stdin for packets");
361     open(STDOUT,">&UW") or fail("reopen stdout for packets");
362     exec @lcmd;
363     quit("cannot execute $lcmd[0]: $!");
364 }
365 close UW;
366 close DR;
367
368 $xfwdopts.= 'w' if $dump;
369
370 @fcmd= ($fcmd, $xfwdopts,
371         fileno(L), fileno(DW), fileno(UR), fileno(DUMPKEYS),
372         $mtu, $keepalive, $timeout, $reannounce,
373         @rapf,
374         @encryption);
375 debug("forwarding command @fcmd.");
376
377 defined($c_fwd= fork) or fail("fork for udptunnel-forwarder");
378 if (!$c_fwd) {
379     foreach $fd (qw(L DW UR DUMPKEYS)) {
380         fcntl($fd, F_SETFD, 0) or fail("set no-close-on-exec $fd");
381     }
382     exec @fcmd; fail("cannot execute $fcmd[0]");
383 }
384
385 close L;
386 close DUMPKEYS;
387 close UR;
388 close DW;
389
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;
394
395 $estatus= 0;
396
397 while (keys %procs) {
398     ($c= wait) >0 or
399         fail("wait failed (expecting ". join('; ',keys %procs). ")");
400     $status= $?;
401     warning("unexpected child reaped: pid $c, code $status"), next
402         unless exists $procs{$c};
403     $str= $procs{$c};
404     delete $procs{$c};
405     $status ? warning("subprocess $str failed with code $status")
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);
409     }
410     $estatus=1 unless $c == $c_catremdebug;
411 }
412
413 debug("all processes terminated, exiting with status $estatus");
414
415 exit $estatus;