File Coverage

blib/lib/AnyEvent/SSH2.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # $Id: SSH2.pm,v 1.47 2009/01/26 01:50:38 turnstep Exp $
2             package AnyEvent::SSH2;
3 1     1   12876 use strict;
  1         1  
  1         31  
4 1     1   403 use AE;
  1         4311  
  1         20  
5 1     1   605 use AnyEvent::Handle;
  1         13202  
  1         28  
6 1     1   208 use Net::SSH::Perl::Kex;
  0            
  0            
7             use Net::SSH::Perl::ChannelMgr;
8             use Net::SSH::Perl::Packet;
9             use Net::SSH::Perl::Buffer;
10             use Net::SSH::Perl::Constants qw( :protocol :msg2 :compat :hosts :channels :proposal :kex
11             CHAN_INPUT_CLOSED CHAN_INPUT_WAIT_DRAIN );
12             use Net::SSH::Perl::Cipher;
13             use Net::SSH::Perl::AuthMgr;
14             use Net::SSH::Perl::Comp;
15             use Net::SSH::Perl::Util qw(:hosts);
16             use Scalar::Util qw(blessed weaken);
17             use Carp qw( croak );
18             use Smart::Comments;
19              
20             use base qw( Net::SSH::Perl );
21             our $VERSION = '0.01';
22              
23             use Errno qw( EAGAIN EWOULDBLOCK );
24             use vars qw( $VERSION $CONFIG $HOSTNAME @PROPOSAL );
25             use vars qw( @PROPOSAL );
26             @PROPOSAL = (
27             KEX_DEFAULT_KEX,
28             KEX_DEFAULT_PK_ALG,
29             KEX_DEFAULT_ENCRYPT,
30             KEX_DEFAULT_ENCRYPT,
31             KEX_DEFAULT_MAC,
32             KEX_DEFAULT_MAC,
33             KEX_DEFAULT_COMP,
34             KEX_DEFAULT_COMP,
35             KEX_DEFAULT_LANG,
36             KEX_DEFAULT_LANG,
37             );
38              
39             $CONFIG = {};
40              
41             BEGIN {
42             use Net::SSH::Perl::Packet;
43             no warnings qw(redefine);
44             *Net::SSH::Perl::Packet::send_ssh2 = sub {
45             my $pack = shift;
46             my $buffer = shift || $pack->{data};
47             my $ssh = $pack->{ssh};
48              
49             my $kex = $ssh->kex;
50             my($ciph, $mac, $comp);
51             if ($kex) {
52             $ciph = $kex->send_cipher;
53             $mac = $kex->send_mac;
54             $comp = $kex->send_comp;
55             }
56             my $block_size = 8;
57              
58             if ($comp && $comp->enabled) {
59             my $compressed = $comp->compress($buffer->bytes);
60             $buffer->empty;
61             $buffer->append($compressed);
62             }
63              
64             my $len = $buffer->length + 4 + 1;
65             my $padlen = $block_size - ($len % $block_size);
66             $padlen += $block_size if $padlen < 4;
67             my $junk = $ciph ? (join '', map chr rand 255, 1..$padlen) : ("\0" x $padlen);
68             $buffer->append($junk);
69              
70             my $packet_len = $buffer->length + 1;
71             $buffer->bytes(0, 0, pack("N", $packet_len) . pack("c", $padlen));
72              
73             my($macbuf);
74             if ($mac && $mac->enabled) {
75             $macbuf = $mac->hmac(pack("N", $ssh->{session}{seqnr_out}) . $buffer->bytes);
76             }
77             my $output = Net::SSH::Perl::Buffer->new( MP => 'SSH2' );
78             $output->append( $ciph && $ciph->enabled ? $ciph->encrypt($buffer->bytes) : $buffer->bytes );
79             $output->append($macbuf) if $mac && $mac->enabled;
80              
81             $ssh->{session}{seqnr_out}++;
82              
83             my $handle = $ssh->sock;
84             my $stat = $handle->push_write($output->bytes);
85             };
86             *Net::SSH::Perl::Packet::read_expect = sub {
87             my $class = shift;
88             my($ssh, $type, $cb) = @_;
89             my $pack = $class->read($ssh, sub{
90             my ($ssh, $pack) = @_;
91             if ($pack->type != $type) {
92             $ssh->fatal_disconnect(sprintf
93             "Protocol error: expected packet type %d, got %d",
94             $type, $pack->type);
95             }
96             $cb->($ssh, $pack);
97             });
98             };
99              
100             *Net::SSH::Perl::Packet::read = sub {
101             my $class = shift;
102             my $ssh = shift;
103             my $cb = shift;
104             my $sock = $ssh->sock;
105             if (my $packet = $class->read_poll($ssh)) {
106             $cb->($ssh, $packet);
107             }
108             else {
109             $sock->push_read(chunk => 4 => sub {
110             my ($hdl, $buf) = @_;
111             if (length($buf) == 0) {
112             croak "Connection closed by remote host." if !$buf;
113             }
114             if (!defined $buf) {
115             next if $! == EAGAIN || $! == EWOULDBLOCK;
116             croak "Read from socket failed: $!";
117             }
118             # Untaint data read from sshd. This is binary data,
119             # so there's nothing to taint-check against/for.
120             ($buf) = $buf =~ /(.*)/s;
121             $ssh->incoming_data->append($buf);
122             $class->read($ssh, $cb);
123             })
124             }
125             };
126             use Net::SSH::Perl::Kex;
127             *Net::SSH::Perl::Kex::exchange_kexinit = sub {
128             my $kex = shift;
129             my $ssh = $kex->{ssh};
130             my $received_packet = shift;
131             my $cb = shift;
132             my $packet;
133            
134             $packet = $ssh->packet_start(SSH2_MSG_KEXINIT);
135             $packet->put_chars($kex->client_kexinit->bytes);
136             $packet->send;
137            
138             if ( defined $received_packet ) {
139             $ssh->debug("Received key-exchange init (KEXINIT), sent response.");
140             $packet = $received_packet;
141             }
142             else {
143             $ssh->debug("Sent key-exchange init (KEXINIT), wait response.");
144             Net::SSH::Perl::Packet->read_expect($ssh, SSH2_MSG_KEXINIT, sub{
145             my ($ssh, $packet) = @_;
146             $kex->{server_kexinit} = $packet->data;
147            
148             $packet->get_char for 1..16;
149             my @s_props = map $packet->get_str, 1..10;
150             $packet->get_int8;
151             $packet->get_int32;
152             $cb->($ssh, \@s_props);
153             });
154             }
155             };
156             *Net::SSH::Perl::Kex::exchange = sub {
157             my $kex = shift;
158             my $ssh = $kex->{ssh};
159             my $packet = shift;
160             my $cb = shift;
161            
162             my @proposal = @PROPOSAL;
163             if (!$ssh->config->get('ciphers')) {
164             if (my $c = $ssh->config->get('cipher')) {
165             $ssh->config->set('ciphers', $c);
166             }
167             }
168             if (my $cs = $ssh->config->get('ciphers')) {
169             # SSH2 cipher names are different; for compatibility, we'll map
170             # valid SSH1 ciphers to the SSH2 equivalent names
171             if($ssh->protocol eq PROTOCOL_SSH2) {
172             my %ssh2_cipher = reverse %Net::SSH::Perl::Cipher::CIPHERS_SSH2;
173             $cs = join ',', map $ssh2_cipher{$_} || $_, split(/,/, $cs);
174             }
175             $proposal[ PROPOSAL_CIPH_ALGS_CTOS ] =
176             $proposal[ PROPOSAL_CIPH_ALGS_STOC ] = $cs;
177             }
178             if ($ssh->config->get('compression')) {
179             $proposal[ PROPOSAL_COMP_ALGS_CTOS ] =
180             $proposal[ PROPOSAL_COMP_ALGS_STOC ] = "zlib";
181             }
182             else {
183             $proposal[ PROPOSAL_COMP_ALGS_CTOS ] =
184             $proposal[ PROPOSAL_COMP_ALGS_STOC ] = "none";
185             }
186             if ($ssh->config->get('host_key_algorithms')) {
187             $proposal[ PROPOSAL_SERVER_HOST_KEY_ALGS ] =
188             $ssh->config->get('host_key_algorithms');
189             }
190            
191             $kex->{client_kexinit} = $kex->kexinit(\@proposal);
192             $kex->exchange_kexinit($packet, sub{
193             my ($ssh, $sprop) = @_;
194             $kex->choose_conf(\@proposal, $sprop);
195             $ssh->debug("Algorithms, c->s: " .
196             "$kex->{ciph_name}[0] $kex->{mac_name}[0] $kex->{comp_name}[0]");
197             $ssh->debug("Algorithms, s->c: " .
198             "$kex->{ciph_name}[1] $kex->{mac_name}[1] $kex->{comp_name}[1]");
199            
200             bless $kex, $kex->{class_name};
201             $kex->exchange(sub{
202             my $ssh = shift;
203             $ssh->debug("Waiting for NEWKEYS message.");
204             Net::SSH::Perl::Packet->read_expect($ssh, SSH2_MSG_NEWKEYS, sub{
205             my ($ssh, $packet) = @_;
206             $ssh->debug("Send NEWKEYS.");
207             $packet = $ssh->packet_start(SSH2_MSG_NEWKEYS);
208             $packet->send;
209            
210             $ssh->debug("Enabling encryption/MAC/compression.");
211             $ssh->{kex} = $kex;
212             for my $att (qw( mac ciph comp )) {
213             $kex->{$att}[0]->enable if $kex->{$att}[0];
214             $kex->{$att}[1]->enable if $kex->{$att}[1];
215             }
216             $cb->($ssh);
217             });
218             });
219            
220             });
221             };
222             use Net::SSH::Perl::Kex::DH1;
223             no strict "subs";
224             *Net::SSH::Perl::Kex::DH1::exchange = sub {
225             package Net::SSH::Perl::Kex::DH1;
226             my $kex = shift;
227             my $ssh = $kex->{ssh};
228             my $packet;
229             my $dh = _dh_new_group1;
230             my $cb = shift;
231              
232             $ssh->debug("Entering Diffie-Hellman Group 1 key exchange.");
233             $packet = $ssh->packet_start(SSH2_MSG_KEXDH_INIT);
234             $packet->put_mp_int($dh->pub_key);
235             $packet->send;
236              
237             $ssh->debug("Sent DH public key, waiting for reply.");
238             Net::SSH::Perl::Packet->read_expect($ssh,
239             SSH2_MSG_KEXDH_REPLY, sub {
240             my ($ssh, $packet) = @_;
241             my $host_key_blob = $packet->get_str;
242             my $s_host_key = Net::SSH::Perl::Key->new_from_blob($host_key_blob,
243             \$ssh->{datafellows});
244             $ssh->debug("Received host key, type '" . $s_host_key->ssh_name . "'.");
245              
246             $ssh->check_host_key($s_host_key);
247              
248             my $dh_server_pub = $packet->get_mp_int;
249             my $signature = $packet->get_str;
250              
251             $ssh->fatal_disconnect("Bad server public DH value")
252             unless _pub_is_valid($dh, $dh_server_pub);
253              
254             $ssh->debug("Computing shared secret key.");
255             my $shared_secret = $dh->compute_key($dh_server_pub);
256              
257             my $hash = $kex->kex_hash(
258             $ssh->client_version_string,
259             $ssh->server_version_string,
260             $kex->client_kexinit,
261             $kex->server_kexinit,
262             $host_key_blob,
263             $dh->pub_key,
264             $dh_server_pub,
265             $shared_secret);
266              
267             $ssh->debug("Verifying server signature.");
268             croak "Key verification failed for server host key"
269             unless $s_host_key->verify($signature, $hash);
270              
271             $ssh->session_id($hash);
272              
273             $kex->derive_keys($hash, $shared_secret, $ssh->session_id);
274             $cb->($ssh);
275             });
276             };
277             use Net::SSH::Perl::AuthMgr;
278             no warnings qw(redefine);
279             #no strict "refs";
280             *Net::SSH::Perl::AuthMgr::new = sub {
281             my $class = shift;
282             my $ssh = shift;
283             my $amgr = bless { ssh => $ssh }, $class;
284             weaken $amgr->{ssh};
285             $amgr;
286             };
287             *Net::SSH::Perl::AuthMgr::run = sub {
288             my $amgr = shift;
289             my $cb = pop @_;
290             my($end, @args) = @_;
291             Net::SSH::Perl::Packet->read($amgr->{ssh}, sub{
292             my ($ssh, $packet) = @_;
293             my $code = $amgr->handler_for($packet->type);
294             unless (defined $code) {
295             $code = $amgr->error_handler ||
296             sub { croak "Protocol error: received type ", $packet->type };
297             }
298             $code->($amgr, $packet, @args);
299             if ($$end) {
300             $cb->($amgr);
301             return;
302             }
303             $amgr->run($end, $cb);
304             });
305             };
306             *Net::SSH::Perl::AuthMgr::authenticate = sub {
307             package Net::SSH::Perl::AuthMgr;
308             my $amgr = shift;
309             my $cb = shift;
310             $amgr->init(sub{
311             my ($ssh, $amgr) = @_;
312             my($packet);
313            
314             my $valid = 0;
315             $amgr->{_done} = 0;
316             $amgr->register_handler(SSH2_MSG_USERAUTH_SUCCESS, sub {
317             $valid++;
318             $amgr->{_done}++
319             });
320             $amgr->register_handler(SSH2_MSG_USERAUTH_BANNER, sub {
321             my $amgr = shift;
322             my($packet) = @_;
323             if ($amgr->{ssh}->config->get('interactive')) {
324             print $packet->get_str, "\n";
325             }
326             });
327             $amgr->register_handler(SSH2_MSG_USERAUTH_FAILURE, \&auth_failure);
328             $amgr->register_error(
329             sub { croak "userauth error: bad message during auth" } );
330             $amgr->run( \$amgr->{_done}, sub{
331             my ($amgr) = shift;
332             $amgr->{agent}->close_socket if $amgr->{agent};
333            
334             $cb->($ssh, $amgr, $valid);
335             } );
336            
337             });
338             };
339              
340             *Net::SSH::Perl::AuthMgr::init = sub {
341             package Net::SSH::Perl::AuthMgr;
342             my $amgr = shift;
343             my $cb = shift;
344             my $ssh = $amgr->{ssh};
345             my($packet);
346            
347             $ssh->debug("Sending request for user-authentication service.");
348             $packet = $ssh->packet_start(SSH2_MSG_SERVICE_REQUEST);
349             $packet->put_str("ssh-userauth");
350             $packet->send;
351            
352             Net::SSH::Perl::Packet->read($ssh, sub {
353             my ($ssh, $packet) = @_;
354             croak "Server denied SSH2_MSG_SERVICE_ACCEPT: ", $packet->type
355             unless $packet->type == SSH2_MSG_SERVICE_ACCEPT;
356             $ssh->debug("Service accepted: " . $packet->get_str . ".");
357            
358             $amgr->{agent} = Net::SSH::Perl::Agent->new(2);
359             $amgr->{service} = "ssh-connection";
360            
361             $amgr->send_auth_none;
362             $cb->($ssh, $amgr);
363             });
364            
365             };
366             };
367             use Carp qw( croak );
368              
369             sub VERSION { $VERSION }
370              
371             sub new {
372             my $class = shift;
373             my $host = shift;
374             croak "usage: ", __PACKAGE__, "->new(\$host)"
375             unless defined $host;
376             my $ssh = bless { host => $host }, $class;
377             my %p = @_;
378             $ssh->{_test} = delete $p{_test};
379             $ssh->_init(%p);
380             $ssh;
381             }
382              
383             sub _init {
384             my $ssh = shift;
385              
386             my %arg = @_;
387             my $user_config = delete $arg{user_config} || "$ENV{HOME}/.ssh/config";
388             my $sys_config = delete $arg{sys_config} || "/etc/ssh_config";
389              
390             my $directives = delete $arg{options} || [];
391              
392             if (my $proto = delete $arg{protocol}) {
393             push @$directives, "Protocol $proto";
394             }
395              
396             my $cfg = Net::SSH::Perl::Config->new($ssh->{host}, %arg);
397             $ssh->{config} = $cfg;
398              
399             # Merge config-format directives given through "options"
400             # (just like -o option to ssh command line). Do this before
401             # reading config files so we override files.
402             for my $d (@$directives) {
403             $cfg->merge_directive($d);
404             }
405              
406             for my $f (($user_config, $sys_config)) {
407             $ssh->debug("Reading configuration data $f");
408             $cfg->read_config($f);
409             }
410              
411             if (my $real_host = $ssh->{config}->get('hostname')) {
412             $ssh->{host} = $real_host;
413             }
414              
415             my $user = _current_user();
416             if ($user && $user eq "root" &&
417             !defined $ssh->{config}->get('privileged')) {
418             $ssh->{config}->set('privileged', 1);
419             }
420              
421             unless ($ssh->{config}->get('protocol')) {
422             $ssh->{config}->set('protocol',
423             PROTOCOL_SSH1 | PROTOCOL_SSH2 | PROTOCOL_SSH1_PREFERRED);
424             }
425              
426             unless (defined $ssh->{config}->get('password_prompt_login')) {
427             $ssh->{config}->set('password_prompt_login', 1);
428             }
429             unless (defined $ssh->{config}->get('password_prompt_host')) {
430             $ssh->{config}->set('password_prompt_host', 1);
431             }
432              
433             unless (defined $ssh->{config}->get('number_of_password_prompts')) {
434             $ssh->{config}->set('number_of_password_prompts', 3);
435             }
436              
437             # login
438             if (!defined $ssh->{config}->get('user')) {
439             $ssh->{config}->set('user',
440             defined $arg{user} ? $arg{user} : _current_user());
441             }
442             if (!defined $arg{pass} && exists $CONFIG->{ssh_password}) {
443             $arg{pass} = $CONFIG->{ssh_password};
444             }
445             $ssh->{config}->set('pass', $arg{pass});
446              
447             #my $suppress_shell = $_[2];
448             }
449              
450             sub _current_user {
451             my $user;
452             eval { $user = scalar getpwuid $> };
453             return $user;
454             }
455              
456             sub set_protocol {
457             my $ssh = shift;
458             my $proto = shift;
459             $ssh->{use_protocol} = $proto;
460             $ssh->debug($ssh->version_string);
461             $ssh->_proto_init;
462             }
463              
464              
465             sub _dup {
466             my($fh, $mode) = @_;
467             my $dup = Symbol::gensym;
468             my $str = "${mode}&$fh";
469             open ($dup, $str) or die "Could not dupe: $!\n"; ## no critic
470             $dup;
471             }
472              
473             sub version_string {
474             my $class = shift;
475             sprintf "Net::SSH::Perl Version %s, protocol version %s.%s.",
476             $class->VERSION, PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2;
477             }
478              
479             sub _exchange_identification {
480             my $ssh = shift;
481             my $remote_id = $ssh->_read_version(@_);
482             ($ssh->{server_version_string} = $remote_id) =~ s/\cM?$//;
483             my($remote_major, $remote_minor, $remote_version) = $remote_id =~
484             /^SSH-(\d+)\.(\d+)-([^\n]+)$/;
485             $ssh->debug("Remote protocol version $remote_major.$remote_minor, remote software version $remote_version");
486              
487             my $proto = $ssh->config->get('protocol');
488             my($mismatch, $set_proto);
489             if ($remote_major == 1) {
490             if ($remote_minor == 99 && $proto & PROTOCOL_SSH2 &&
491             !($proto & PROTOCOL_SSH1_PREFERRED)) {
492             $set_proto = PROTOCOL_SSH2;
493             }
494             elsif (!($proto & PROTOCOL_SSH1)) {
495             $mismatch = 1;
496             }
497             else {
498             $set_proto = PROTOCOL_SSH1;
499             }
500             }
501             elsif ($remote_major == 2) {
502             if ($proto & PROTOCOL_SSH2) {
503             $set_proto = PROTOCOL_SSH2;
504             }
505             }
506             if ($mismatch) {
507             croak sprintf "Protocol major versions differ: %d vs. %d",
508             ($proto & PROTOCOL_SSH2) ? PROTOCOL_MAJOR_2 :
509             PROTOCOL_MAJOR_1, $remote_major;
510             }
511             my $compat20 = $set_proto == PROTOCOL_SSH2;
512             my $buf = sprintf "SSH-%d.%d-%s\n",
513             $compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1,
514             $compat20 ? PROTOCOL_MINOR_2 : PROTOCOL_MINOR_1,
515             $VERSION;
516             $ssh->{client_version_string} = substr $buf, 0, -1;
517             my $handle = $ssh->{session}{sock};
518             $handle->push_write($buf);
519             $ssh->set_protocol($set_proto);
520             $ssh->_compat_init($remote_version);
521             }
522              
523             sub _proto_init {
524             my $ssh = shift;
525             my $home = $ENV{HOME} || (getpwuid($>))[7];
526             unless ($ssh->{config}->get('user_known_hosts')) {
527             defined $home or croak "Cannot determine home directory, please set the environment variable HOME";
528             $ssh->{config}->set('user_known_hosts', "$home/.ssh/known_hosts2");
529             }
530             unless ($ssh->{config}->get('global_known_hosts')) {
531             $ssh->{config}->set('global_known_hosts', "/etc/ssh_known_hosts2");
532             }
533             unless (my $if = $ssh->{config}->get('identity_files')) {
534             defined $home or croak "Cannot determine home directory, please set the environment variable HOME";
535             $ssh->{config}->set('identity_files', [ "$home/.ssh/id_dsa" ]);
536             }
537              
538             for my $a (qw( password dsa kbd_interactive )) {
539             $ssh->{config}->set("auth_$a", 1)
540             unless defined $ssh->{config}->get("auth_$a");
541             }
542             }
543              
544             sub kex { $_[0]->{kex} }
545              
546             sub register_handler {
547             my($ssh, $type, $sub, @extra) = @_;
548             $ssh->{client_handlers}{$type} = { code => $sub, extra => \@extra };
549             }
550              
551             sub connect {
552             my $ssh = shift;
553             my($type, @args) = @_;
554             $ssh->{session}{sock} = new AnyEvent::Handle
555             connect => [
556             $ssh->{host} => $ssh->{config}->get('port') || 'ssh'
557             ],
558             on_error => sub {
559             my ($hdl, $fatal, $msg) = @_;
560             $ssh->debug("Can't connect to $ssh->{host}, port $ssh->{config}->get('port'): $msg");
561             $hdl->destroy;
562             },
563             on_connect_error => sub {
564             $ssh->debug("Can't connect to $ssh->{host}, port $ssh->{config}->get('port'): $!");
565             },
566             on_eof => sub {
567             shift->destroy; # explicitly destroy handle
568             };
569             $ssh->{session}{sock}->push_read( line => sub {
570             my ($handle, $line) = @_;
571             $ssh->_exchange_identification($line);
572             $ssh->debug("Connection established.");
573             $ssh->_login();
574              
575              
576             });
577             }
578              
579             sub _login {
580             my $ssh = shift;
581              
582             my $kex = Net::SSH::Perl::Kex->new($ssh);
583             $kex->exchange(undef, sub{
584             my $ssh = shift;
585             my $amgr = Net::SSH::Perl::AuthMgr->new($ssh);
586             $amgr->authenticate(sub{
587             my ($ssh, $amgr, $valid) = @_;
588             $ssh->debug("Login completed, opening dummy shell channel.");
589             my $cmgr = $ssh->channel_mgr;
590             my $channel = $cmgr->new_channel(
591             ctype => 'session', local_window => 0,
592             local_maxpacket => 0, remote_name => 'client-session');
593             $channel->open;
594              
595             Net::SSH::Perl::Packet->read_expect($ssh,
596             SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, sub{
597             my ($ssh, $packet) = @_;
598             $cmgr->input_open_confirmation($packet);
599              
600             #my $suppress_shell = $_[2];
601             #unless ($suppress_shell) {
602             # $ssh->debug("Got channel open confirmation, requesting shell.");
603             # $channel->request("shell", 0);
604             #}
605              
606             $ssh->emit('_cmd');
607             $ssh->emit('_shell');
608             $ssh->client_loop;
609             });
610             });
611             })
612             }
613              
614             sub emit {
615             my ($self, $name) = (shift, shift);
616              
617             if (my $s = $self->{events}{$name}) {
618             $self->debug("-- Emit $name in @{[blessed $self]} (@{[scalar @$s]})\n");
619             for my $arg (@$s) {
620             $self->$name(@$arg)
621             }
622             }
623             else {
624             $self->debug("-- Emit $name in @{[blessed $self]} (0)\n");
625             die "@{[blessed $self]}: $_[0]" if $name eq 'error';
626             }
627              
628             return $self;
629             }
630              
631             sub _session_channel {
632             my $ssh = shift;
633             my $cmgr = $ssh->channel_mgr;
634              
635             my $channel = $cmgr->new_channel(
636             ctype => 'session', local_window => 32*1024,
637             local_maxpacket => 16*1024, remote_name => 'client-session',
638             rfd => _dup('STDIN', '<'), wfd => _dup('STDOUT', '>'),
639             efd => _dup('STDERR', '>'));
640              
641             $channel;
642             }
643              
644             sub _make_input_channel_req {
645             my($r_exit) = @_;
646             return sub {
647             my($channel, $packet) = @_;
648             my $rtype = $packet->get_str;
649             my $reply = $packet->get_int8;
650             $channel->{ssh}->debug("input_channel_request: rtype $rtype reply $reply");
651             if ($rtype eq "exit-status") {
652             $$r_exit = $packet->get_int32;
653             }
654             if ($reply) {
655             my $r_packet = $channel->{ssh}->packet_start(SSH2_MSG_CHANNEL_SUCCESS);
656             $r_packet->put_int($channel->{remote_id});
657             $r_packet->send;
658             }
659             };
660             }
661              
662             sub on { push @{$_[0]->{events}{$_[1]}}, [$_[-2], $_[-1]] }
663              
664             sub cmd {
665             my ($ssh, $cmd, $cb) = @_;
666             $ssh->on(_cmd => $cmd => $cb);
667             $ssh;
668             }
669              
670             sub _cmd {
671             my $ssh = shift;
672             my($cmd, $cb) = @_;
673              
674             my $cmgr = $ssh->channel_mgr;
675             my $channel = $ssh->_session_channel;
676             $channel->open;
677              
678              
679             $channel->register_handler(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, sub {
680             my($channel, $packet) = @_;
681              
682             ## Experimental pty support:
683             if ($ssh->{config}->get('use_pty')) {
684             $ssh->debug("Requesting pty.");
685              
686             my $packet = $channel->request_start('pty-req', 0);
687              
688             my($term) = $ENV{TERM} =~ /(\w+)/;
689             $packet->put_str($term);
690             my $foundsize = 0;
691             if (eval "require Term::ReadKey") {
692             my @sz = Term::ReadKey::GetTerminalSize($ssh->sock);
693             if (defined $sz[0]) {
694             $foundsize = 1;
695             $packet->put_int32($sz[1]); # height
696             $packet->put_int32($sz[0]); # width
697             $packet->put_int32($sz[2]); # xpix
698             $packet->put_int32($sz[3]); # ypix
699             }
700             }
701             if (!$foundsize) {
702             $packet->put_int32(0) for 1..4;
703             }
704              
705             # Array used to build Pseudo-tty terminal modes; fat commas separate opcodes from values for clarity.
706             my $terminal_mode_string;
707             if(!defined($ssh->{config}->get('terminal_mode_string'))) {
708             my @terminal_modes = (
709             5 => 0,0,0,4, # VEOF => 0x04 (^d)
710             0 # string must end with a 0 opcode
711             );
712             for my $char (@terminal_modes) {
713             $terminal_mode_string .= chr($char);
714             }
715             }
716             else {
717             $terminal_mode_string = $ssh->{config}->get('terminal_mode_string');
718             }
719             $packet->put_str($terminal_mode_string);
720             $packet->send;
721             }
722              
723             my $r_packet = $channel->request_start("exec", 0);
724             $r_packet->put_str($cmd);
725             $r_packet->send;
726              
727             });
728              
729             my($exit);
730             $channel->register_handler(SSH2_MSG_CHANNEL_REQUEST,
731             _make_input_channel_req(\$exit));
732              
733             my $h = $ssh->{client_handlers};
734             my($stdout, $stderr);
735             if (my $r = $h->{stdout}) {
736             $channel->register_handler("_output_buffer",
737             $r->{code}, @{ $r->{extra} });
738             }
739             else {
740             $channel->register_handler("_output_buffer", sub {
741             $stdout .= $_[1]->bytes;
742             });
743             }
744             if (my $r = $h->{stderr}) {
745             $channel->register_handler("_extended_buffer",
746             $r->{code}, @{ $r->{extra} });
747             }
748             else {
749             $channel->register_handler("_extended_buffer", sub {
750             $stderr .= $_[1]->bytes;
751             });
752             }
753              
754             $ssh->debug("Entering interactive session.");
755             $channel->{cb} = sub {
756             my $ssh = shift;
757             $cb->($ssh, $stdout, $stderr);
758             }
759            
760             }
761             sub shell {
762             my $ssh = shift;
763             my $cb = shift;
764             $ssh->on(_shell => '');
765             $ssh->on(on_fininsh => $cb);
766             $ssh;
767             }
768             sub _shell {
769             my $ssh = shift;
770             my $cmgr = $ssh->channel_mgr;
771             my $channel = $ssh->_session_channel;
772             $channel->open;
773              
774             $channel->register_handler(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, sub {
775             my($channel, $packet) = @_;
776             my $r_packet = $channel->request_start('pty-req', 0);
777             my($term) = $ENV{TERM} =~ /(\S+)/;
778             $r_packet->put_str($term);
779             my $foundsize = 0;
780             if (eval "require Term::ReadKey") {
781             my @sz = Term::ReadKey::GetTerminalSize($ssh->sock);
782             if (defined $sz[0]) {
783             $foundsize = 1;
784             $r_packet->put_int32($sz[1]); # height
785             $r_packet->put_int32($sz[0]); # width
786             $r_packet->put_int32($sz[2]); # xpix
787             $r_packet->put_int32($sz[3]); # ypix
788             }
789             }
790             if (!$foundsize) {
791             $r_packet->put_int32(0) for 1..4;
792             }
793             $r_packet->put_str("");
794             $r_packet->send;
795             $channel->{ssh}->debug("Requesting shell.");
796             $channel->request("shell", 0);
797             });
798              
799             my($exit);
800             $channel->register_handler(SSH2_MSG_CHANNEL_REQUEST,
801             _make_input_channel_req(\$exit));
802              
803             $channel->register_handler("_output_buffer", sub {
804             syswrite STDOUT, $_[1]->bytes;
805             });
806             $channel->register_handler("_extended_buffer", sub {
807             syswrite STDERR, $_[1]->bytes;
808             });
809              
810             $ssh->debug("Entering interactive session.");
811             }
812              
813             #sub open2 {
814             # my $ssh = shift;
815             # my($cmd) = @_;
816             #
817             # require Net::SSH::Perl::Handle::SSH2;
818             #
819             # my $cmgr = $ssh->channel_mgr;
820             # my $channel = $ssh->_session_channel;
821             # $channel->open;
822             #
823             # $channel->register_handler(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, sub {
824             # my($channel, $packet) = @_;
825             # $channel->{ssh}->debug("Sending command: $cmd");
826             # my $r_packet = $channel->request_start("exec", 1);
827             # $r_packet->put_str($cmd);
828             # $r_packet->send;
829             # });
830             #
831             # my $exit;
832             # $channel->register_handler(SSH2_MSG_CHANNEL_REQUEST, sub {
833             # my($channel, $packet) = @_;
834             # my $rtype = $packet->get_str;
835             # my $reply = $packet->get_int8;
836             # $channel->{ssh}->debug("input_channel_request: rtype $rtype reply $reply");
837             # if ($rtype eq "exit-status") {
838             # $exit = $packet->get_int32;
839             # }
840             # if ($reply) {
841             # my $r_packet = $channel->{ssh}->packet_start(SSH2_MSG_CHANNEL_SUCCESS);
842             # $r_packet->put_int($channel->{remote_id});
843             # $r_packet->send;
844             # }
845             # });
846             #
847             # my $reply = sub {
848             # my($channel, $packet) = @_;
849             # if ($packet->type == SSH2_MSG_CHANNEL_FAILURE) {
850             # $channel->{ssh}->fatal_disconnect("Request for " .
851             # "exec failed on channel '" . $packet->get_int32 . "'");
852             # }
853             # $channel->{ssh}->break_client_loop;
854             # };
855             #
856             # $cmgr->register_handler(SSH2_MSG_CHANNEL_FAILURE, $reply);
857             # $cmgr->register_handler(SSH2_MSG_CHANNEL_SUCCESS, $reply);
858             #
859             # $ssh->client_loop;
860             #
861             # my $read = Symbol::gensym;
862             # my $write = Symbol::gensym;
863             # tie *$read, 'Net::SSH::Perl::Handle::SSH2', 'r', $channel, \$exit;
864             # tie *$write, 'Net::SSH::Perl::Handle::SSH2', 'w', $channel, \$exit;
865             #
866             # return ($read, $write);
867             #}
868              
869             sub break_client_loop { $_[0]->{ek_client_loopcl_quit_pending} = 1 }
870             sub restore_client_loop { $_[0]->{_cl_quit_pending} = 0 }
871             sub _quit_pending { $_[0]->{_cl_quit_pending} }
872              
873             sub client_loop {
874             my $ssh = shift;
875             $ssh->{_cl_quit_pending} = 0;
876             my $cmgr = $ssh->channel_mgr;
877             my $h = $cmgr->handlers;
878             $ssh->event($cmgr, $h);
879             }
880              
881             sub event {
882             my ($ssh, $cmgr, $h, $cb) = @_;
883             return $ssh->emit('on_finish') if $ssh->_quit_pending;
884             while (my $packet = Net::SSH::Perl::Packet->read_poll($ssh)) {
885             if (my $code = $h->{ $packet->type }) {
886             $code->($cmgr, $packet);
887             }
888             else {
889             $ssh->debug("Warning: ignore packet type " . $packet->type);
890             }
891             }
892              
893             return $ssh->emit('on_finish') if $ssh->_quit_pending;
894              
895             $cmgr->process_output_packets;
896              
897             # 如果处理完了. 关掉所有的连接
898             # 之所以在这进行这个操作是因为主 channel 也需要操作
899             for my $c (@{ $cmgr->{channels} }) {
900             next unless defined $c;
901             if ($c->{wfd} &&
902             $c->{extended}->length == 0 &&
903             $c->{output}->length == 0 &&
904             $c->{ostate} == CHAN_OUTPUT_WAIT_DRAIN ) {
905             $c->obuf_empty;
906             }
907             # 上面 obuf_empty 会给 ostate 变成 CHAN_OUTPUT_CLOSED
908             # 下面这个就会发关闭给远程
909             if ($c->delete_if_full_closed) {
910             defined $c->{cb} ? $c->{cb}->() : '';
911             $cmgr->remove($c->{id});
912             }
913             }
914            
915             my $oc = grep { defined } @{ $cmgr->{channels} };
916             return $ssh->emit('on_finish') unless $oc > 1;
917              
918             my $cv = AE::cv sub {
919             my $result = shift->recv;
920             delete $ssh->{watcher};
921             $ssh->event($cmgr, $h, $cb);
922             };
923              
924             # 这是处理频道上的输出, 客户端的输入
925             for my $c (@{ $cmgr->{channels} }) {
926             next unless defined $c;
927             my $id = $c->{id};
928             if ($c->{rfd} && $c->{istate} == CHAN_INPUT_OPEN &&
929             $c->{remote_window} > 0 &&
930             $c->{input}->length < $c->{remote_window}) {
931             $ssh->{watcher}{$id}{rfd} = AE::io $c->{rfd}, 0, sub {
932             # 顺序记录 - 频道 - rfd
933             my $buf;
934             sysread $c->{rfd}, $buf, 8192;
935             ($buf) = $buf =~ /(.*)/s;
936             $c->send_data($buf);
937             $cv->send('rfd');
938             delete $ssh->{watcher}{$id}{rfd}
939             };
940             }
941              
942             # 给内容输出
943             if (defined $c->{wfd} &&
944             $c->{ostate} == CHAN_OUTPUT_OPEN ||
945             $c->{ostate} == CHAN_OUTPUT_WAIT_DRAIN) {
946             if ($c->{output} and $c->{output}->length > 0) {
947             $ssh->{watcher}{$id}{wfd} = AE::io $c->{wfd}, 1, sub {
948             if (my $r = $c->{handlers}{"_output_buffer"}) {
949             $r->{code}->( $c, $c->{output}, @{ $r->{extra} } );
950             }
951             $c->{local_consumed} += $c->{output}->length;
952             $c->{output}->empty;
953             $cv->send('wfd');
954             delete $ssh->{watcher}{$id}{wfd}
955             }
956             }
957             }
958            
959             if ($c->{efd} && $c->{extended}->length > 0) {
960             my $c->{watcher}{$id}{efd} = AE::io $c->{efd}, 1, sub {
961             if (my $r = $c->{handlers}{"_extended_buffer"}) {
962             $r->{code}->( $c, $c->{extended}, @{ $r->{extra} } );
963             }
964             $c->{local_consumed} += $c->{extended}->length;
965             $c->{extended}->empty;
966             $cv->send('efd');
967             delete $ssh->{watcher}{$id}{efd}
968             };
969             }
970              
971            
972             # 原进程
973             $c->check_window;
974             if ($c->delete_if_full_closed) {
975             defined $c->{cb} ? $c->{cb}->() : '';
976             $cmgr->remove($c->{id});
977             }
978             }
979              
980              
981             # 这是主连接的句柄
982             my $handle = $ssh->{session}{sock};
983             $handle->push_read( chunk => 4 => sub {
984             my ($handle, $buf) = @_;
985             if (!length($buf)) {
986             croak "Connection failed: $!\n";
987             }
988             $ssh->break_client_loop if length($buf) == 0;
989             ($buf) = $buf =~ /(.*)/s; ## Untaint data. Anything allowed.
990             $ssh->incoming_data->append($buf);
991             $cv->send('main');
992             });
993             }
994              
995             sub channel_mgr {
996             my $ssh = shift;
997             unless (defined $ssh->{channel_mgr}) {
998             $ssh->{channel_mgr} = Net::SSH::Perl::ChannelMgr->new($ssh);
999             }
1000             $ssh->{channel_mgr};
1001             }
1002             sub _read_version {
1003             my $ssh = shift;
1004             my $line = shift;;
1005             my $len = length $line;
1006             unless(defined($len)) {
1007             next if $! == EAGAIN || $! == EWOULDBLOCK;
1008             croak "Read from socket failed: $!";
1009             }
1010             croak "Connection closed by remote host" if $len == 0;
1011             croak "Version line too long: $line"
1012             if substr($line, 0, 4) eq "SSH-" and length($line) > 255;
1013             croak "Pre-version line too long: $line" if length($line) > 4*1024;
1014             if (substr($line, 0, 4) ne "SSH-") {
1015             $ssh->debug("Remote version string: $line");
1016             }
1017             return $line;
1018             }
1019             sub sock { $_[0]->{session}{sock} }
1020              
1021             1;
1022             __END__