lighty.io initializer
[transportpce.git] / debug_tools / netconf_TCP_SSH_hijackingproxy.pl
1 #!/usr/bin/env perl
2 ##############################################################################
3 #Copyright (c) 2017 Orange, Inc. and others.  All rights reserved.
4 #
5 # This program and the accompanying materials are made available under the
6 # terms of the Eclipse Public License v1.0 which accompanies this distribution,
7 # and is available at http://www.eclipse.org/legal/epl-v10.html
8 ##############################################################################
9 #
10 # debian dependecies: apt-get install libnet-openssh-perl libio-pty-perl
11 #
12
13 use strict;
14 use warnings;
15 #use diagnostics;  #uncomment this line for more details when encountering warnings
16 use Net::OpenSSH;
17 use FileHandle;
18 use Getopt::Long qw(:config no_ignore_case bundling);
19
20 use IO::Socket;
21 use Net::hostent;
22
23 my ($host, $help, $usage,  $proxy_port, $login, $password, $kidpid, $ssh_subsocket, $simpleproxy,
24     $pid, $ssh_handle, $client, $server, $capabilities, $hello_message, $verbose);
25
26 GetOptions (
27     "h|help" =>\$help,
28     "p|port=i"=>\$proxy_port,
29     "s|simpleproxy" =>\$simpleproxy,
30     "v|verbose" =>\$verbose,
31     "C|capabilities=s"=>\$capabilities
32 );
33 $usage = "
34 USAGE: netconf_TCP_SSH_hijackproxy.pl [-h|--help] [-p|--port <port_number>] [-s|--simpleproxy] [-v|--verbose] [-C|--capabilities <custom_hello_file.xml>] <[login[:password]@]host[:port]> [login] [password]
35
36 Netconf SSH to TCP proxy to debug netconf exchanges.
37 It listens to connections in clear TCP to the given port. When a TCP connection demand is received,
38 it establishes a netconf SSH encrypted connection to the host in argument. Netconf rpcs and replies
39 are then proxified between both ends.
40 By default, exchanges are altered according to the rules specified inside this script and easily 
41 modifiable. This behaviour can be disabled with the '-s' option.
42 For more convenience, the server hello handshake can also alternatively be replaced by the content
43 of an external file rather instead of writing specific rules.
44
45 OPTIONS :
46
47         -h or --help             print this help
48         -p or --port             use the given port number for listening TCP clients, default=9000
49         -s or --simpleproxy      simple proxy mode, do not alter any exchanges
50         -v or --verbose          display exchanges to STDOUT
51         -C or --capabilities     do not relay the real server hello message to the client
52                                  but replace it by the one provided in the following file
53
54
55 ";
56
57 if ($help) {
58     print $usage;
59     exit(0);
60 }
61
62 unless (@ARGV >= 1) {
63     print $usage;
64     exit(0);
65 }
66
67 ($host, $login, $password) = @ARGV;
68
69 #netconf default port is no 22 but 830
70 if ($host !~ /:[0-9]+$/) { $host.=':830'; }
71
72 if (!defined($proxy_port)) { $proxy_port = 9000; }
73
74 my $connection_string=$host;
75 if ($password) {
76    $connection_string=$login.":".$password."@".$connection_string;
77 } elsif ($login) {
78    $connection_string=$login."@".$connection_string;
79 }
80
81 #retrieving hello custom file if any
82 if ((!defined ($simpleproxy))&&(defined ($capabilities))) {
83     open(CAPABILITIES,'<',$capabilities) or die ("can not open $capabilities") ;
84     while (<CAPABILITIES>) {
85         $hello_message .= $_;
86     }
87     chop $hello_message; # removing EOF
88     $hello_message.="]]>]]>";
89     close(CAPABILITIES);
90 }
91
92 # the following regex are used to modify some part of the server messages relayed to the client
93 # you can adapt it to your needs, some examples have been commented.
94 my %regex_hash=(
95 # replace oo-device v1.2 by v1.2.1
96 #   'module=org-openroadm-device&amp;revision=2016-10-14.*<\/capability>'=>'s/&amp;revision=2016-10-14/&amp;revision=2017-02-06/',
97 #   '<schema><identifier>org-openroadm-device<\/identifier><version>2016-10-14'=>'s@<schema><identifier>org-openroadm-device</identifier><version>2016-10-14@<schema><identifier>org-openroadm-device</identifier><version>2017-02-06@',
98 # remove all deviations found
99 #   '&amp;deviations=.*<\/capability>'=>'s@&amp;deviations=.*</capability>@</capability>@',
100 # add the ietf-netconf capability to the hello handshake - without it, ODL netconf mountpoints can not work
101 #    '<\/capabilities>'=>'s@</capabilities>@\n<capability>urn:ietf:params:xml:ns:yang:ietf-netconf?module=ietf-netconf&amp;revision=2011-06-01</capability>\n</capabilities>@',
102 # add the right notifications capabilities to the hello handshake + provide another solution for the ietf-netconf capability
103     '<\/capabilities>'=>'s@</capabilities>@\n<capability>urn:ietf:params:xml:ns:netmod:notification?module=nc-notifications&amp;revision=2008-07-14</capability>\n<capability>urn:ietf:params:xml:ns:netconf:notification:1.0?module=notifications&amp;revision=2008-07-14</capability>\n<capability>urn:ietf:params:xml:ns:netconf:base:1.0?module=ietf-netconf&amp;revision=2011-06-01</capability>\n</capabilities>@'
104 );
105
106 if (defined ($simpleproxy)) { %regex_hash=(); }
107
108 my %compiled_regex_hash;
109 foreach my $keyword (keys %regex_hash){
110     eval ('$compiled_regex_hash{$keyword}= qr/'.$keyword.'/;');
111 }
112
113 $server = IO::Socket::INET->new( Proto     => 'tcp',
114                                  LocalPort => $proxy_port,
115                                  Listen    => SOMAXCONN,
116                                  Reuse     => 1);
117 die "can't setup server" unless $server;
118 print STDERR "[Proxy server $0 accepting clients: Ctrl-C to stop]\n";
119
120
121 while ($client = $server->accept()) {
122   $client->autoflush(1);
123   my $hostinfo = gethostbyaddr($client->peeraddr);
124   printf STDERR "[Incoming connection from %s]\n", $hostinfo->name || $client->peerhost;
125
126
127 print STDERR "[relaying to ".$connection_string."]\n";
128
129 $ssh_handle = Net::OpenSSH->new($connection_string,
130                                 master_opts => [-o => 'StrictHostKeyChecking=no'],
131                                 timeout => 500, kill_ssh_on_timeout => 500);
132
133 #netconf requires a specific socket
134 ($ssh_subsocket, $pid) = $ssh_handle->open2socket({ssh_opts => '-s'}, 'netconf');
135 die "can't establish connection: exiting\n" unless defined($ssh_subsocket);
136
137 print STDERR "[Connected]\n";
138
139 # split the program into two processes, identical twins
140 die "can't fork: $!" unless defined($kidpid = fork());
141
142 $|=1;
143
144 # the if{} block runs only in the parent process (server output relayed to the client)
145 if (!$kidpid) {
146
147     # copy the socket to standard output
148     my $buf;
149     
150     if (defined ($hello_message)) {
151         #retrieve the server hello but do not relay it
152         while (my $nread = sysread($ssh_subsocket,$buf,400)) {
153             $ssh_subsocket->flush();
154             if ($buf =~ /]]>]]>/) { last };
155         };
156         #send a custom hello message instead
157         print $client $hello_message;
158         if (defined($verbose))  { print STDOUT  $hello_message; }
159     }
160
161     #while (<$ssh_subsocket>) {
162     #buffer seems not totally flushed when using the usual syntax above (nor when using autoflush)
163     while (my $nread = sysread($ssh_subsocket,$buf,400)) {
164         foreach my $keyword (keys %regex_hash){
165            if($buf =~ $compiled_regex_hash{$keyword}){
166                print STDERR 'found regex '.$keyword.": replacing '\n".$buf."\n' by '\n";
167                eval ('$buf =~ '.$regex_hash{$keyword}.';');
168                print STDERR $buf."\n'\n";
169            }
170         }
171         print $client $buf;
172         $ssh_subsocket->flush();
173         if (defined($verbose))  { print STDOUT  $buf; }
174                
175     };
176  
177     kill("TERM", $kidpid);              # send SIGTERM to child
178 }
179 # the else{} block runs only in the child process (client input relayed to the server)
180 else {
181
182    $ssh_subsocket->autoflush(1);
183    sleep 1;                             # wait needed for ensuring STDOUT buffer is not melt
184    my $buf;
185
186    #while (defined (my $buf = <$client>)) {
187    #usual syntax above used in verbose mode results into flush problems
188    while (my $nread = sysread($client,$buf,400)) {
189       print $ssh_subsocket $buf;
190       $client->flush();
191       if (defined($verbose))  { print STDOUT  $buf; }
192    }continue {}
193
194    close $client;
195
196 }
197
198 $|=0;
199
200 sleep 2;
201 kill("TERM", $kidpid);                  # send SIGTERM to child
202
203 }
204
205 exit;