File Coverage

blib/lib/Net/SSH2/Cisco.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             package Net::SSH2::Cisco;
2              
3             ##################################################
4             # Michael Vincent
5             # www.VinsWorld.com
6             ##################################################
7              
8             # NOTE: This module is basically a cut/paste of:
9             # 70% Net::Telnet
10             # 20% Net::Telnet::Cisco
11             # 5% Net::SSH2(::Channel)
12             # 5% original hack to make it all work together
13             #
14             # - I tried to create a child class of Net::SSH2 to no avail due to
15             # the C-type inside-out object it returns and my lack of experience.
16             #
17             # - I tried to pass a Net::SSH2 connection to Net::Telnet(::Cisco) fhopen()
18             # method, but it returned:
19             #
20             # Not a GLOB reference at [...]/perl/vendor/lib/Net/Telnet.pm line 679.
21             #
22             # - I tried to use Net::Telnet in my @ISA with AUTOLOAD to leverage the
23             # accessors and code already written, but I'm not creating a Net::Telnet
24             # object and I couldn't get it to work.
25             #
26             # That left me the (?only?) option - to write this Franken-module "liberally
27             # borrowing" from much smarter, more talented programmers than I.
28              
29 1     1   10390 use strict;
  1         2  
  1         23  
30 1     1   3 use warnings;
  1         1  
  1         36  
31              
32             our $VERSION = '0.03';
33             our @ISA;
34              
35 1     1   367 use version;
  1         1362  
  1         4  
36 1     1   870 use Net::SSH2 0.51;
  0            
  0            
37             use Socket qw(inet_ntoa AF_INET IPPROTO_TCP);
38             my $HAVE_IO_Socket_IP = 0;
39             eval "use IO::Socket::IP -register";
40             if(!$@) {
41             $HAVE_IO_Socket_IP = 1;
42             push @ISA, "IO::Socket::IP"
43             } else {
44             require IO::Socket::INET;
45             push @ISA, "IO::Socket::INET"
46             }
47              
48             my $AF_INET6 = eval { Socket::AF_INET6() };
49             my $AF_UNSPEC = eval { Socket::AF_UNSPEC() };
50             my $AI_NUMERICHOST = eval { Socket::AI_NUMERICHOST() };
51             my $NI_NUMERICHOST = eval { Socket::NI_NUMERICHOST() };
52              
53             $|++;
54              
55             ##################################################
56             # Start Public Module
57             ##################################################
58              
59             sub new {
60             my $self = shift;
61             my $class = ref($self) || $self;
62              
63             my ($fh_open, $host);
64             my %params = (
65             always_waitfor_prompt => 1,
66             autopage => 1,
67             bin_mode => 0,
68             blocking => 0,
69             cmd_prompt => '/(?m:^(?:[\w.\/]+\:)?[\w.-]+\s?(?:\(config[^\)]*\))?\s?[\$#>]\s?(?:\(enable\))?\s*$)/',
70             cmd_rm_mode => "auto",
71             dumplog => '',
72             eofile => 1,
73             errormode => "die",
74             errormsg => "",
75             fh_open => undef,
76             host => "localhost",
77             ignore_warnings => 0,
78             inputlog => '',
79             last_cmd => '',
80             last_prompt => '',
81             maxbufsize => 1_048_576,
82             more_prompt => '/(?m:^\s*--More--)/',
83             normalize_cmd => 1,
84             ofs => "",
85             opened => '',
86             outputlog => '',
87             ors => "\n",
88             peer_family => 'ipv4',
89             port => 22,
90             rs => "\n",
91             send_wakeup => 0,
92             time_out => 10,
93             timedout => '',
94             waitfor_clear => 1,
95             waitfor_pause => 0.2,
96             warnings => '/(?mx:^% Unknown VPN
97             |^%IP routing table VRF.* does not exist. Create first$
98             |^%No CEF interface information
99             |^%No matching route to delete$
100             |^%Not all config may be removed and may reappear after reactivating/
101             )/',
102             );
103              
104             $params{_SSH_} = Net::SSH2->new() or return;
105             $self = bless \%params, $class;
106              
107             my %args;
108             if (@_ == 1) {
109             ($host) = @_
110             } else {
111             %args = @_;
112             for (keys(%args)) {
113             if (/^-?always_waitfor_prompt$/i) {
114             $self->always_waitfor_prompt($args{$_});
115             } elsif (/^-?autopage$/i) {
116             $self->autopage($args{$_})
117             } elsif (/^-?binmode$/i) {
118             $self->binmode($args{$_})
119             } elsif (/^-?blocking$/i) {
120             $self->blocking($args{$_})
121             } elsif (/^-?cmd_remove_mode$/i) {
122             $self->cmd_remove_mode($args{$_})
123             } elsif (/^-?dump_log$/i) {
124             $self->dump_log($args{$_})
125             or return;
126             } elsif (/^-?errmode$/i) {
127             $self->errmode($args{$_})
128             } elsif (/^-?family$/i) {
129             $self->family($args{$_})
130             } elsif (/^-?fhopen$/i) {
131             $fh_open = $args{$_}
132             } elsif (/^-?host$/i) {
133             $host = $args{$_}
134             } elsif (/^-?ignore_warnings$/i) {
135             $self->{ignore_warnings} = $args{$_}
136             } elsif (/^-?input_log$/i) {
137             $self->input_log($args{$_})
138             or return;
139             } elsif (/^-?input_record_separator$/i or /^-?rs$/i) {
140             $self->input_record_separator($args{$_})
141             } elsif (/^-?max_buffer_length$/i) {
142             $self->max_buffer_length($args{$_})
143             } elsif (/^-?normalize_cmd$/i) {
144             $self->normalize_cmd($args{$_})
145             } elsif (/^-?output_field_separator$/i or /^-?ofs$/i) {
146             $self->output_field_separator($args{$_})
147             } elsif (/^-?output_log$/i) {
148             $self->output_log($args{$_})
149             or return;
150             } elsif (/^-?output_record_separator$/i or /^-?ors$/i) {
151             $self->output_record_separator($args{$_})
152             } elsif (/^-?port$/i) {
153             $self->port($args{$_})
154             } elsif (/^-?prompt$/i) {
155             $self->prompt($args{$_})
156             } elsif (/^-?send_wakeup$/i) {
157             $self->send_wakeup($args{$_})
158             } elsif (/^-?timeout$/i) {
159             $self->timeout($args{$_})
160             } elsif (/^-?waitfor_clear$/i) {
161             $self->waitfor_clear($args{$_})
162             } elsif (/^-?waitfor_pause$/i) {
163             $self->waitfor_pause($args{$_})
164             } elsif (/^-?warnings$/i) {
165             $self->{warnings} = $args{$_}
166             } else {
167             # pass through
168             #$params{$_} = $args{$_}
169             &_croak($self, "bad named parameter \"$_\" given " .
170             "to " . ref($self) . "::new()");
171             }
172             }
173             }
174              
175             # $self->open in the if statement so open() not called
176             # if neither 'fh' nor 'host' are provided.
177             if (defined $fh_open) {
178             $self->fhopen($fh_open);
179             $self->open or return
180             } elsif (defined $host) {
181             $self->host($host);
182             $self->open or return
183             }
184              
185             return $self
186             }
187              
188             sub always_waitfor_prompt {
189             my ($self, $arg) = @_;
190             $self->{always_waitfor_prompt} = $arg if defined $arg;
191             return $self->{always_waitfor_prompt}
192             }
193              
194             sub autopage {
195             my ($self, $arg) = @_;
196             $self->{autopage} = $arg if defined $arg;
197             return $self->{autopage}
198             }
199              
200             sub binmode {
201             my ($self, $arg) = @_;
202             $self->{bin_mode} = $arg if defined $arg;
203             return $self->{bin_mode}
204             }
205              
206             sub blocking {
207             my ($self, $arg) = @_;
208             $self->{blocking} = $arg if defined $arg;
209             return $self->{blocking}
210             }
211              
212             sub close {
213             my ($self) = @_;
214              
215             $self->{eofile} = 1;
216             $self->{opened} = '';
217             if (defined $self->{_SSH_CHAN_}) {
218             $self->{_SSH_CHAN_}->close
219             }
220             if (defined $self->{_SSH_}) {
221             $self->{_SSH_}->disconnect
222             }
223             delete $self->{_SSH_CHAN_};
224             delete $self->{_SSH_};
225             1
226             }
227              
228             sub cmd {
229             my ($self, @args) = @_;
230              
231             my $string = '';
232             my $chan = $self->{_SSH_CHAN_};
233             my $clear = $self->{waitfor_clear};
234             my $normal = $self->{normalize_cmd};
235             my $ors = $self->{ors};
236             my $pause = $self->{waitfor_pause};
237             my $prompt = $self->{cmd_prompt};
238             my $rm = $self->{cmd_rm_mode};
239             my $rs = $self->{rs};
240             my $timeout = $self->{time_out};
241              
242             if (!defined $chan) {
243             &_croak($self, "no login " .
244             "for " . ref($self) . "::cmd()");
245             }
246              
247             my $ok = 1;
248             $self->{timedout} = '';
249              
250             my $arg_errmode = &_extract_arg_errmode($self, \@args);
251             local $self->{errormode} = $arg_errmode if $arg_errmode;
252              
253             my $output = [];
254             my $output_ref;
255             if (@_ == 2) {
256             ($string) = ($_[1])
257             } else {
258             my %args = @args;
259             for (keys(%args)) {
260             if (/^-?string$/i) {
261             $string = $args{$_}
262             } elsif (/^-?cmd_remove_mode$/i) {
263             $rm = _parse_cmd_remove_mode($self, $args{$_})
264             } elsif (/^-?input_record_separator$/i or /^-?rs$/i) {
265             $rs = $args{$_}
266             } elsif (/^-?normalize_cmd$/i) {
267             $normal = $args{$_}
268             } elsif (/^-?output$/i) {
269             $output_ref = $args{$_};
270             if (defined($output_ref) and ref($output_ref) eq "ARRAY") {
271             $output = $output_ref;
272             }
273             } elsif (/^-?output_record_separator$/i or /^-?ors$/i) {
274             $ors = $args{$_}
275             } elsif (/^-?prompt$/i) {
276             $prompt = &_parse_prompt($self, $args{$_})
277             or return;
278             } elsif (/^-?timeout$/i) {
279             $timeout = _parse_timeout($self, $args{$_})
280             } elsif (/^-?waitfor_clear$/i) {
281             $clear = $args{$_}
282             } elsif (/^-?waitfor_pause$/i) {
283             $pause = _parse_waitfor_pause($self, $args{$_})
284             or return
285             } else {
286             # pass through
287             #$params{$_} = $args{$_}
288             &_croak($self, "bad named parameter \"$_\" given " .
289             "to " . ref($self) . "::cmd()");
290             }
291             }
292             }
293              
294             #prep
295             local $self->{time_out} = $timeout;
296             local $self->{waitfor_clear} = $clear;
297             $self->errmsg("");
298             chomp $string;
299             $self->{last_cmd} = $string;
300              
301             my ($lines, $last_prompt);
302             {
303             local $self->{errormode} = "return";
304              
305             #send
306             $self->put($string . $ors);
307              
308             #wait
309             select(undef,undef,undef,$pause); # sleep
310              
311             #read
312             ($lines, $last_prompt) = $self->waitfor($prompt);
313             }
314              
315             return $self->error("command timed-out") if $self->timed_out;
316             return $self->error($self->errmsg) if $self->errmsg ne "";
317              
318             ## Split lines into an array, keeping record separator at end of line.
319             my $firstpos = 0;
320             my $rs_len = length $rs;
321             while ((my $lastpos = index($lines, $rs, $firstpos)) > -1) {
322             push(@$output, substr($lines, $firstpos, $lastpos - $firstpos + $rs_len));
323             $firstpos = $lastpos + $rs_len
324             }
325             if ($firstpos < length $lines) {
326             push @$output, substr($lines, $firstpos)
327             }
328              
329             # clean up
330             if ($rm eq "auto") {
331             if ((defined @$output[0]) and (@$output[0] =~ /^$string(?:\r)?(?:\n)?/)) {
332             shift @$output
333             }
334             } else {
335             while ($rm--) {
336             shift @$output
337             }
338             }
339             ## Ensure at least a null string when there's no command output - so
340             ## "true" is returned in a list context.
341             unless (@$output) {
342             @$output = ("")
343             }
344              
345             # Look for errors in output
346             for ( my ($i, $lastline) = (0, '');
347             $i <= $#{$output};
348             $lastline = $output->[$i++] ) {
349              
350             # This may have to be a pattern match instead.
351             if ( ( substr $output->[$i], 0, 1 ) eq '%' ) {
352             if ( $output->[$i] =~ /'\^' marker/ ) { # Typo & bad arg errors
353             chomp $lastline;
354             $self->error( join "\n",
355             "Last command and router error: ",
356             ( $self->last_prompt . $string ),
357             $lastline,
358             $output->[$i],
359             );
360             splice @$output, $i - 1, 3;
361             } else { # All other errors.
362             chomp $output->[$i];
363             $self->error( join "\n",
364             "Last command and router error: ",
365             ( $self->last_prompt . $string ),
366             $output->[$i],
367             );
368             splice @$output, $i, 2;
369             }
370             $ok = 0;
371             last;
372             }
373             }
374              
375             if ($normal) {
376             @$output = _normalize (@$output);
377             }
378              
379             ## Return command output via named arg, if requested.
380             if (defined $output_ref) {
381             if (ref($output_ref) eq "SCALAR") {
382             $$output_ref = join "", @$output;
383             } elsif (ref($output_ref) eq "HASH") {
384             %$output_ref = @$output;
385             }
386             }
387              
388             wantarray ? @$output : $ok
389             }
390              
391             sub cmd_remove_mode {
392             my ($self, $arg) = @_;
393              
394             if (defined $arg) {
395             if (defined (my $r = _parse_cmd_remove_mode($self, $arg))) {
396             $self->{cmd_rm_mode} = $r
397             }
398             }
399             return $self->{cmd_rm_mode}
400             }
401              
402             sub connect { &open }
403              
404             sub disable {
405             my $self = shift;
406             $self->cmd('disable');
407             if ($self->is_enabled) {
408             return $self->error("Failed to exit enabled mode")
409             }
410             1
411             }
412              
413             sub dump_log {
414             my ($self, $name) = @_;
415              
416             my $fh = $self->{dumplog};
417              
418             if (@_ >= 2) {
419             if (!defined($name) or $name eq "") { # input arg is ""
420             ## Turn off logging.
421             $fh = "";
422             } elsif (&_is_open_fh($name)) { # input arg is an open fh
423             ## Use the open fh for logging.
424             $fh = $name;
425             select((select($fh), $|=1)[$[]); # don't buffer writes
426             } elsif (!ref $name) { # input arg is filename
427             ## Open the file for logging.
428             $fh = &_fname_to_handle($self, $name)
429             or return;
430             select((select($fh), $|=1)[$[]); # don't buffer writes
431             } else {
432             return $self->error("bad Dump_log argument ",
433             "\"$name\": not filename or open fh");
434             }
435             $self->{dumplog} = $fh;
436             }
437             $fh;
438             }
439              
440             sub enable {
441             my $self = shift;
442              
443             my ($en_username, $en_password, $en_passcode, $en_level) = ('','','','');
444             my ($error, $lastline, $orig_errmode, $reset, %seen);
445             my $chan = $self->{_SSH_CHAN_};
446             my $ors = $self->{ors};
447             my $timeout = $self->{time_out};
448              
449             if (!defined $chan) {
450             &_croak($self, "no login " .
451             "for " . ref($self) . "::enable()");
452             }
453              
454             $self->{timedout} = '';
455              
456             if (@_ == 1) { # just passwd given
457             ($en_password) = @_
458             } else {
459             my %args = @_;
460             foreach (keys %args) {
461             if (/^-?name$|^-?login$|^-?user/i) {
462             $en_username = $args{$_}
463             } elsif (/^-?passw/i) {
464             $en_password = $args{$_}
465             } elsif (/^-?passc/i) {
466             $en_passcode = $args{$_}
467             } elsif (/^-?level$/i) {
468             $en_level = $args{$_}
469             } elsif (/^-?timeout$/i) {
470             $timeout = $args{$_}
471             } else {
472             # pass through
473             #$params{$_} = $args{$_}
474             &_croak($self, "bad named parameter \"$_\" given " .
475             "to " . ref($self) . "::enable()");
476             }
477             }
478             }
479              
480             local $self->{time_out} = $timeout;
481              
482             ## Create a subroutine to generate an error for user.
483             # $error = sub {
484             # my($errmsg) = @_;
485              
486             # if ($self->timed_out) {
487             # return $self->error($errmsg);
488             # } elsif ($self->eof) {
489             # ($lastline = $self->lastline) =~ s/\n+//;
490             # return $self->error($errmsg, ": ", $lastline);
491             # } else {
492             # return $self->error($errmsg);
493             # }
494             # };
495              
496             # Store the old prompt without the //s around it.
497             my ($old_prompt) = _prep_regex($self->{cmd_prompt});
498              
499             # We need to expect either a Password prompt or a
500             # typical prompt. If the user doesn't have enough
501             # access to run the 'enable' command, the device
502             # won't even query for a password, it will just
503             # ignore the command and display another [boring] prompt.
504             $self->put("enable " . $en_level . $ors);
505              
506             select(undef,undef,undef,$self->{waitfor_pause}); # sleep
507              
508             {
509             my ($prematch, $match) = $self->waitfor(
510             -match => '/[Ll]ogin[:\s]*$/',
511             -match => '/[Uu]sername[:\s]*$/',
512             -match => '/[Pp]assw(?:or)?d[:\s]*$/',
513             -match => '/(?i:Passcode)[:\s]*$/',
514             -match => "/$old_prompt/",
515             ) or do {
516             return &$error("read eof waiting for enable login or password prompt")
517             if $self->eof;
518             return &$error("timed-out waiting for enable login or password prompt");
519             };
520              
521             if (not defined $match) {
522             return $self->error("enable failed: access denied or bad name, passwd, etc")
523             } elsif ($match =~ /sername|ogin/) {
524             if (!defined $self->put($en_username . $ors)) {
525             return $self->error("enable failed")
526             }
527             $self->{last_prompt} = $match;
528             if ($seen{login}++) {
529             return $self->error("enable failed: access denied or bad username")
530             }
531             redo
532             } elsif ($match =~ /[Pp]assw/ ) {
533             if (!defined $self->put($en_password . $ors)) {
534             return $self->error("enable failed")
535             }
536             $self->{last_prompt} = $match;
537             if ($seen{passwd}++) {
538             return $self->error("enable failed: access denied or bad password")
539             }
540             redo
541             } elsif ($match =~ /(?i:Passcode)/ ) {
542             if (!defined $self->put($en_passcode . $ors)) {
543             return $self->error("enable failed")
544             }
545             $self->{last_prompt} = $match;
546             if ($seen{passcode}++) {
547             return $self->error("enable failed: access denied or bad passcode")
548             }
549             redo
550             } elsif ($match =~ /$old_prompt/) {
551             ## Success! Exit the block.
552             $self->{last_prompt} = $match;
553             last
554             } else {
555             return $self->error("enable received unexpected prompt. Aborting.")
556             }
557             }
558              
559             if (not defined $en_level or $en_level =~ /^[1-9]/) {
560             # Prompts and levels over 1 give a #/(enable) prompt.
561             if ($self->is_enabled) {
562             return 1
563             } else {
564             return $self->error("Failed to enter enable mode")
565             }
566             } else {
567             # Assume success
568             return 1
569             }
570             }
571              
572             sub eof {
573             my ($self) = @_;
574             exists $self->{_SSH_CHAN_} ? ($self->{_SSH_CHAN_}->eof or $self->{eofile}) : $self->{eofile};
575             }
576              
577             sub errmode {
578             my ($self, $arg) = @_;
579              
580             if (defined $arg) {
581             if (defined (my $r = _parse_errmode($self, $arg))) {
582             $self->{errormode} = $r
583             }
584             }
585             return $self->{errormode}
586             }
587              
588             sub errmsg {
589             my ($self, @errmsgs) = @_;
590              
591             if (@_ >= 2) {
592             $self->{errormsg} = join "", @errmsgs;
593             }
594              
595             return $self->{errormsg}
596             }
597              
598             sub error {
599             my ($self, @errmsg) = @_;
600              
601             if ($self->ignore_warnings) {
602             my $errmsg = join '', @errmsg;
603             my $warnings_re = _prep_regex($self->warnings);
604             return if $errmsg =~ /$warnings_re/;
605             }
606              
607             my ($errmsg, $func, $mode, @args);
608             local $_;
609              
610             if (@_ >= 2) {
611             ## Put error message in the object.
612             $errmsg = join "", @errmsg;
613             $self->{errormsg} = $errmsg;
614             ## Do the error action as described by error mode.
615             $mode = $self->{errormode};
616             if (ref($mode) eq "CODE") {
617             &$mode($errmsg);
618             return;
619             } elsif (ref($mode) eq "ARRAY") {
620             ($func, @args) = @$mode;
621             &$func(@args);
622             return;
623             } elsif ($mode =~ /^return$/i) {
624             return;
625             } else { # die
626             if ($errmsg =~ /\n$/) {
627             die $errmsg;
628             } else {
629             ## Die and append caller's line number to message.
630             &_croak($self, $errmsg);
631             }
632             }
633             } else {
634             return $self->{errormsg} ne "";
635             }
636             }
637              
638             sub family {
639             my ($self, $arg) = @_;
640              
641             if (defined $arg) {
642             if (defined (my $r = _parse_family($self, $arg))) {
643             $self->{peer_family} = $r
644             }
645             }
646             return $self->{peer_family}
647             }
648              
649             sub fhopen{
650             my ($self, $arg) = @_;
651             $self->{fh_open} = $arg if defined $arg;
652             return $self->{fh_open}
653             }
654              
655             sub host {
656             my ($self, $arg) = @_;
657             $self->{host} = $arg if defined $arg;
658             return $self->{host}
659             }
660              
661             sub ignore_warnings {
662             my ($self, $arg) = @_;
663             $self->{ignore_warnings} = $arg if defined $arg;
664             return $self->{ignore_warnings}
665             }
666              
667             sub input_log {
668             my ($self, $name) = @_;
669              
670             my $fh = $self->{inputlog};
671              
672             if (@_ >= 2) {
673             if (!defined($name) or $name eq "") { # input arg is ""
674             ## Turn off logging.
675             $fh = "";
676             } elsif (&_is_open_fh($name)) { # input arg is an open fh
677             ## Use the open fh for logging.
678             $fh = $name;
679             select((select($fh), $|=1)[$[]); # don't buffer writes
680             } elsif (!ref $name) { # input arg is filename
681             ## Open the file for logging.
682             $fh = &_fname_to_handle($self, $name)
683             or return;
684             select((select($fh), $|=1)[$[]); # don't buffer writes
685             } else {
686             return $self->error("bad Input_log argument ",
687             "\"$name\": not filename or open fh");
688             }
689             $self->{inputlog} = $fh;
690             }
691             $fh;
692             }
693              
694             sub input_record_separator {
695             my ($self, $arg) = @_;
696             $self->{rs} = $arg if (defined $arg and length $arg);
697             return $self->{rs}
698             }
699              
700             sub ios_break {
701             my ($self, $arg) = @_;
702              
703             my $chan = $self->{_SSH_CHAN_};
704             my $ret;
705             if (defined $arg) {
706             $ret = $self->put("\c^$arg")
707             } else {
708             $ret = $self->put("\c^")
709             }
710              
711             return $ret;
712             }
713              
714             sub is_enabled { $_[0]->last_prompt =~ /\#|enable|config/ ? 1 : undef }
715              
716             sub last_cmd {
717             my $self = shift;
718             exists $self->{last_cmd} ? $self->{last_cmd} : undef
719             }
720              
721             sub last_prompt {
722             my $self = shift;
723             exists $self->{last_prompt} ? $self->{last_prompt} : undef
724             }
725              
726             sub login {
727             my ($self, @args) = @_;
728              
729             if (!$self->{opened}) {
730             &_croak($self, "no connect " .
731             "for " . ref($self) . "::login()");
732             }
733              
734             my $bin = $self->{bin_mode};
735             my $block = $self->{blocking};
736             my $prompt = $self->{cmd_prompt};
737             my $timeout = $self->{time_out};
738             my $ssh = $self->{_SSH_};
739             my $sent_wakeup = 0;
740             my ($user, $pass);
741              
742             $self->{timedout} = '';
743              
744             my $arg_errmode = &_extract_arg_errmode($self, \@args);
745             local $self->{errormode} = $arg_errmode if $arg_errmode;
746              
747             if (@_ == 3) { # just username and passwd given
748             ($user, $pass) = (@_[1,2])
749             } else {
750             my %args = @args;
751             for (keys(%args)) {
752             if (/^-?(?:user)?name$/i) {
753             $user = $args{$_}
754             } elsif (/^-?passw(?:ord)?$/i) {
755             $pass = $args{$_}
756             } elsif (/^-?prompt$/i) {
757             $prompt = _parse_prompt($self, $args{$_})
758             or return
759             } elsif (/^-?timeout$/i) {
760             $timeout = _parse_timeout($self, $args{$_})
761             } else {
762             # pass through
763             #$params{$_} = $args{$_}
764             &_croak($self, "bad named parameter \"$_\" given ",
765             "to " . ref($self) . "::login()");
766             }
767             }
768             }
769              
770             if (!defined $user) {
771             &_croak($self,"username argument not given to " . ref($self) . "::login()")
772             }
773             if (!defined $pass) {
774             &_croak($self,"password argument not given to " . ref($self) . "::login()")
775             }
776              
777             local $self->{time_out} = $timeout;
778              
779             # This is where we'd do 'connect' send_wakeup if Net::SSH2 supported
780             if ($self->{send_wakeup} eq 'connect') {
781             $sent_wakeup = 1;
782              
783             # my $old_sep = $self->output_record_separator;
784             # $self->output_record_separator("\n");
785             # $self->print('');
786             # $self->output_record_separator($old_sep);
787             }
788              
789             AUTH:
790             if ($ssh->auth_password($user, $pass)) {
791             my $chan = $ssh->channel();
792             $chan->blocking($block); # 0 Needed on Windows
793             $chan->shell();
794             if ($bin) {
795             CORE::binmode ($chan)
796             }
797             $self->{_SSH_CHAN_} = $chan;
798              
799             # flush buffer, read off first prompt
800             if ($sent_wakeup == 0 && $self->{send_wakeup} eq 'noflush') {
801              
802             # do nothing
803             $sent_wakeup = 1;
804             } else {
805             $sent_wakeup = 1;
806             $self->waitfor($self->{cmd_prompt});
807             }
808             } else {
809             # This is where we'd do 'timeout' send_wakeup if Net::SSH2 supported
810             if ($sent_wakeup == 0 && $self->{send_wakeup} eq 'timeout') {
811             $sent_wakeup = 1;
812              
813             # my $old_sep = $self->output_record_separator;
814             # $self->output_record_separator("\n");
815             # $self->print('');
816             # $self->output_record_separator($old_sep);
817              
818             # goto AUTH;
819             }
820             my ($errcode, $errname, $errstr) = $ssh->error;
821             return $self->error("Net::SSH2 error $errcode:$errname [$errstr]\nauthentication failed for user - `$user'")
822             }
823             1
824             }
825              
826             sub max_buffer_length {
827             my ($self, $arg) = @_;
828              
829             my $minbufsize = 512;
830              
831             if (defined $arg) {
832             if ($arg =~ /^\d+$/) {
833             $self->{maxbufsize} = $arg
834             } else {
835             &_carp($self, "ignoring bad Max_buffer_length " .
836             "argument \"$arg\": it's not a positive integer");
837             }
838             }
839              
840             ## Adjust up values that are too small.
841             if ($self->{maxbufsize} < $minbufsize) {
842             $self->{maxbufsize} = $minbufsize;
843             }
844              
845             return $self->{maxbufsize}
846             }
847              
848             sub more_prompt {
849             my ($self, $arg) = @_;
850              
851             if (defined $arg) {
852             $self->_match_check($arg);
853             $self->{more_prompt} = $arg;
854             }
855             return $self->{more_prompt};
856             }
857              
858             sub normalize_cmd {
859             my ($self, $arg) = @_;
860             $self->{normalize_cmd} = $arg if defined $arg;
861             return $self->{normalize_cmd}
862             }
863              
864             sub ofs { &output_field_separator; }
865              
866             sub open {
867             my ($self, @args) = @_;
868              
869             return 1 if $self->{opened};
870              
871             my $ssh = $self->{_SSH_};
872             my $family = $self->{peer_family};
873             my $fh = $self->{fh_open};
874             my $host = $self->{host};
875             my $port = $self->{port};
876             my $timeout = $self->{time_out};
877              
878             $self->{timedout} = '';
879              
880             my $arg_errmode = &_extract_arg_errmode($self, \@args);
881             local $self->{errormode} = $arg_errmode if $arg_errmode;
882              
883             if (@_ == 2) {
884             ($host) = $_[1]
885             } else {
886             my %args = @args;
887             for (keys(%args)) {
888             if (/^-?fhopen$/i) {
889             $fh = $args{$_}
890             } elsif (/^-?host$/i) {
891             $host = $args{$_}
892             } elsif (/^-?family$/i) {
893             $family = _parse_family($self, $args{$_})
894             } elsif (/^-?port$/i) {
895             $port = _parse_port($self, $args{$_})
896             } elsif (/^-?timeout$/i) {
897             $timeout = _parse_timeout($self, $args{$_})
898             } else {
899             # pass through
900             #$params{$_} = $args{$_}
901             &_croak($self, "bad named parameter \"$_\" given ",
902             "to " . ref($self) . "::connect()");
903             }
904             }
905             }
906              
907             local $self->{time_out} = $timeout;
908              
909             my $r;
910             # IO::Socket object provided
911             if (defined $fh) {
912             $r = $ssh->connect($fh)
913             # host provided
914             } else {
915             # resolve
916             if (defined(my $res = _resolv($self, $host, _parse_family_to_num($self, $family)))) {
917             $host = $res->{addr};
918             $port = $res->{port} || $port
919             } else {
920             return $self->error($self->errmsg)
921             }
922             # connect if IPv4
923             if ($family eq 'ipv4') {
924             $ssh->timeout($timeout*1000); # timeout is in millisecs
925             $r = $ssh->connect($host, $port)
926              
927             # if IPv6, Net::SSH2 doesn't yet support,
928             # so need to create our own IO::Socket::IP
929             } else {
930             my $socket = IO::Socket::IP->new(
931             PeerHost => $host,
932             PeerPort => $port,
933             Timeout => $timeout,
934             Family => _parse_family_to_num($self, $family)
935             );
936             if (!$socket) {
937             return $self->error("unable to connect to [$family] host - `$host:$port'")
938             }
939             $r = $ssh->connect($socket);
940             }
941             }
942             if (! $r) {
943             my ($errcode, $errname, $errstr) = $ssh->error;
944             return $self->error("Net::SSH2 error - $errcode:$errname = $errstr\nunable to connect to host - `$host:$port'")
945             }
946              
947             $self->{eofile} = '';
948             $self->{errormsg} = "";
949             $self->{opened} = 1;
950             $self->{timedout} = '';
951             1
952             }
953              
954             sub ors { &output_record_separator }
955              
956             sub output_field_separator {
957             my ($self, $arg) = @_;
958             $self->{ofs} = $arg if (defined $arg and length $arg);
959             return $self->{ofs}
960             }
961              
962             sub output_log {
963             my ($self, $name) = @_;
964              
965             my $fh = $self->{outputlog};
966              
967             if (@_ >= 2) {
968             if (!defined($name) or $name eq "") { # input arg is ""
969             ## Turn off logging.
970             $fh = "";
971             } elsif (&_is_open_fh($name)) { # input arg is an open fh
972             ## Use the open fh for logging.
973             $fh = $name;
974             select((select($fh), $|=1)[$[]); # don't buffer writes
975             } elsif (!ref $name) { # input arg is filename
976             ## Open the file for logging.
977             $fh = &_fname_to_handle($self, $name)
978             or return;
979             select((select($fh), $|=1)[$[]); # don't buffer writes
980             } else {
981             return $self->error("bad Output_log argument ",
982             "\"$name\": not filename or open fh");
983             }
984             $self->{outputlog} = $fh;
985             }
986             $fh;
987             }
988              
989             sub output_record_separator {
990             my ($self, $arg) = @_;
991             $self->{ors} = $arg if (defined $arg and length $arg);
992             return $self->{ors}
993             }
994              
995             sub port {
996             my ($self, $arg) = @_;
997              
998             if (defined $arg) {
999             if (defined (my $r = _parse_port($self, $arg))) {
1000             $self->{port} = $r
1001             }
1002             }
1003             return $self->{port}
1004             }
1005              
1006             sub print {
1007             my ($self) = shift;
1008              
1009             $self->{timedout} = '';
1010              
1011             return $self->error("write error: filehandle isn't open")
1012             unless $self->{opened};
1013              
1014             ## Add field and record separators.
1015             my $buf = join($self->{"ofs"}, @_) . $self->{"ors"};
1016              
1017             if ($self->{outputlog}) {
1018             &_log_print($self->{outputlog}, $buf);
1019             }
1020              
1021             &_put($self, \$buf, "print");
1022             }
1023              
1024             sub prompt {
1025             my ($self, $arg) = @_;
1026              
1027             if (defined $arg) {
1028             if (defined (my $r = _parse_prompt($self, $arg))) {
1029             $self->{cmd_prompt} = $r
1030             }
1031             }
1032             return $self->{cmd_prompt}
1033             }
1034              
1035             sub put {
1036             my ($self, @args) = @_;
1037              
1038             local $_;
1039              
1040             my $binmode = $self->{bin_mode};
1041             my $timeout = $self->{time_out};
1042             $self->{timedout} = '';
1043              
1044             my $arg_errmode = &_extract_arg_errmode($self, \@args);
1045             local $self->{errormode} = $arg_errmode if $arg_errmode;
1046              
1047             my $buf;
1048             if (@_ == 2) {
1049             $buf = $_[1];
1050             } elsif (@_ > 2) {
1051             my (undef, %args) = @_;
1052             foreach (keys %args) {
1053             if (/^-?binmode$/i) {
1054             $binmode = $args{$_};
1055             } elsif (/^-?string$/i) {
1056             $buf = $args{$_};
1057             } elsif (/^-?timeout$/i) {
1058             $timeout = &_parse_timeout($self, $args{$_});
1059             } else {
1060             &_croak($self, "bad named parameter \"$_\" given ",
1061             "to " . ref($self) . "::put()");
1062             }
1063             }
1064             }
1065              
1066             ## If any args given, override corresponding instance data.
1067             local $self->{bin_mode} = $binmode;
1068             local $self->{time_out} = $timeout;
1069              
1070             ## Check for errors.
1071             return $self->error("write error: filehandle isn't open")
1072             unless $self->{opened};
1073              
1074             if ($self->{outputlog}) {
1075             &_log_print($self->{outputlog}, $buf);
1076             }
1077              
1078             &_put($self, \$buf, "put");
1079             }
1080              
1081             sub rs { &input_record_separator }
1082              
1083             sub send_wakeup {
1084             my ($self, $arg) = @_;
1085             $self->{send_wakeup} = $arg if defined $arg;
1086             return $self->{send_wakeup}
1087             }
1088              
1089             sub sock {
1090             my $self = shift;
1091             exists $self->{_SSH_} ? $self->{_SSH_}->sock : undef
1092             }
1093              
1094             sub ssh2 {
1095             my $self = shift;
1096             exists $self->{_SSH_} ? $self->{_SSH_} : undef
1097             }
1098              
1099             sub ssh2_chan {
1100             my $self = shift;
1101             exists $self->{_SSH_CHAN_} ? $self->{_SSH_CHAN_} : undef
1102             }
1103              
1104             sub timed_out {
1105             my $self = shift;
1106             exists $self->{timedout} ? $self->{timedout} : undef
1107             }
1108              
1109             sub timeout {
1110             my ($self, $arg) = @_;
1111              
1112             if (defined $arg) {
1113             if (defined (my $r = _parse_timeout($self, $arg))) {
1114             $self->{time_out} = $r
1115             }
1116             }
1117             return $self->{time_out}
1118             }
1119              
1120             sub waitfor {
1121             my ($self, @args) = @_;
1122              
1123             my $ap = $self->{autopage};
1124             my $awfp = $self->{always_waitfor_prompt};
1125             my $binmode = $self->{bin_mode};
1126             my $chan = $self->{_SSH_CHAN_};
1127             my $clear = $self->{waitfor_clear};
1128             my $cmd = $self->{last_cmd};
1129             my $rm = $self->{cmd_rm_mode};
1130             my $timeout = $self->{time_out};
1131              
1132             if (!defined $chan) {
1133             &_croak($self, "no login " .
1134             "for " . ref($self) . "::waitfor()");
1135             }
1136              
1137             local $@;
1138             local $_;
1139             my $DONE = 0;
1140             my $MORE = _prep_regex($self->{more_prompt});
1141             my $PROMPT = _prep_regex($self->{cmd_prompt});
1142             my ($match, $buffer, $errmode);
1143             my @matches;
1144             if ($awfp) {
1145             push @matches, $PROMPT
1146             }
1147              
1148             $self->{timedout} = '';
1149             return if $self->{eofile};
1150             return unless @args;
1151              
1152             if (@_ == 2) {
1153             push @matches, _prep_regex($_[1])
1154             } else {
1155             my $arg;
1156             while (($_, $arg) = splice @args, 0, 2 ) {
1157             if (/^-?binmode$/i) {
1158             $binmode = $arg
1159             } elsif (/^-?errmode$/i) {
1160             $errmode = &_parse_errmode($self, $arg);
1161             } elsif (/^-?match$/i) {
1162             push @matches, _prep_regex($arg)
1163             } elsif (/^-?string$/i) {
1164             $arg =~ s/'/\\'/g; # quote ticks
1165             push @matches, $arg
1166             } elsif (/^-?timeout$/i) {
1167             $timeout = _parse_timeout($self, $arg)
1168             } elsif (/^-?waitfor_clear$/i) {
1169             $clear = $arg
1170             } else {
1171             # pass through
1172             #$params{$_} = $args{$_}
1173             &_croak($self, "bad named parameter \"$_\" given " .
1174             "to " . ref($self) . "::waitfor()");
1175             }
1176             }
1177             }
1178              
1179             local $self->{errormode} = $errmode if defined $errmode;
1180             local $self->{waitfor_clear} = $clear;
1181              
1182             eval {
1183             local $SIG{ALRM} = sub { die "timed-out\n" };
1184             alarm $timeout;
1185              
1186             if ($self->{waitfor_clear}) {
1187             $chan->flush;
1188             }
1189              
1190             # Read until $DONE
1191             while (1) {
1192             last if $DONE;
1193             last if $self->eof;
1194             my $buf;
1195             # Read chunk
1196             while (defined (my $len = $chan->read($buf,$self->{maxbufsize}))) {
1197              
1198             ## Display network traffic if requested.
1199             if ($self->{dumplog} and ($buf ne '')) {
1200             &_log_dump('<', $self->{dumplog}, \$buf, 0, $len);
1201             }
1202              
1203             # input logging
1204             if ($self->{inputlog}) {
1205             &_log_print($self->{inputlog}, $buf);
1206             }
1207              
1208             # Found match then $DONE
1209             for my $m (@matches) {
1210             if ($buf =~ /($m)/) {
1211             $match = $1;
1212             $buf =~ s/$m//;
1213             $DONE++
1214             }
1215             }
1216             # autopage
1217             if ($ap and ($buf =~ /($MORE)/)) {
1218             #$buf =~ s/$MORE//g;
1219             $self->put(" ");
1220             }
1221             $buffer .= $buf
1222             }
1223             }
1224             };
1225             alarm 0;
1226             if ($@ =~ /^timed-out$/) {
1227             $self->errmsg("timed-out during read");
1228             $self->{timedout} = 1;
1229             # If previous call to waitfor timed out, there may still be
1230             # stuff in the channel - e.g., "show run" takes time to "build
1231             # configuration" and that may time out, but the output will fill
1232             # the channel after the return and if term length is a finite
1233             # value - like the default 24 - a MORE prompt is waiting. So
1234             # we need to send a character to cancel that; otherwise, the first
1235             # character of the subsequent $chan->write() (usually from cmd())
1236             # will "disappear" from the output - satisfying the MORE prompt
1237             # and being lost forever.
1238             # The following is 'Control-Shift-Z', which breaks a MORE and
1239             # returns the prompt.
1240             #$chan->write("\cZ")
1241             if ($self->{waitfor_clear}) {
1242             $self->ios_break("Z")
1243             }
1244             }
1245              
1246             if (defined $match and ($match =~ /$PROMPT/)) {
1247             $self->{last_prompt} = $match
1248             }
1249              
1250             wantarray ? ($buffer, $match) : 1;
1251             }
1252              
1253             sub waitfor_clear {
1254             my ($self, $arg) = @_;
1255             $self->{waitfor_clear} = $arg if defined $arg;
1256             return $self->{waitfor_clear}
1257             }
1258              
1259             sub waitfor_pause {
1260             my ($self, $arg) = @_;
1261              
1262             if (defined $arg) {
1263             if (defined (my $r = _parse_waitfor_pause($self, $arg))) {
1264             $self->{waitfor_pause} = $r
1265             }
1266             }
1267             return $self->{waitfor_pause}
1268             }
1269              
1270             sub warnings {
1271             my ($self, $arg) = @_;
1272             $self->{warnings} = $arg if defined $arg;
1273             return $self->{warnings}
1274             }
1275              
1276             #### PRIVATE ####
1277              
1278             sub _append_lineno {
1279             my ($obj, @msgs) = @_;
1280              
1281             my ( $file, $line, $pkg);
1282              
1283             ## Find the caller that's not in object's class or one of its base classes.
1284             ($pkg, $file , $line) = &_user_caller($obj);
1285             join("", @msgs, " at ", $file, " line ", $line, "\n");
1286             }
1287              
1288             sub _carp {
1289             my ($self) = @_;
1290              
1291             $self->{errormsg} = &_append_lineno(@_);
1292             warn $self->{errormsg}, "\n";
1293             }
1294              
1295             sub _croak {
1296             my ($self) = @_;
1297              
1298             $self->{errormsg} = &_append_lineno(@_);
1299             die $self->{errormsg}, "\n";
1300             }
1301              
1302             sub _extract_arg_errmode {
1303             my ($self, $args) = @_;
1304             my (%args);
1305             local $_;
1306             my $errmode = '';
1307              
1308             ## Check for named parameters.
1309             return '' unless @$args >= 2;
1310              
1311             ## Rebuild args without errmode parameter.
1312             %args = @$args;
1313             @$args = ();
1314              
1315             ## Extract errmode arg.
1316             foreach (keys %args) {
1317             if (/^-?errmode$/i) {
1318             $errmode = &_parse_errmode($self, $args{$_});
1319             } else {
1320             push @$args, $_, $args{$_};
1321             }
1322             }
1323             $errmode;
1324             }
1325              
1326             sub _fname_to_handle {
1327             my ($self, $filename) = @_;
1328              
1329             no strict "refs";
1330              
1331             my $fh = &_new_handle();
1332             CORE::open $fh, ">", $filename
1333             or return $self->error("problem creating $filename: $!");
1334              
1335             $fh;
1336             }
1337              
1338             sub _is_open_fh {
1339             my ($fh) = @_;
1340             my $is_open = '';
1341             local $@;
1342              
1343             eval {
1344             local $SIG{"__DIE__"} = "DEFAULT";
1345             $is_open = defined(fileno $fh);
1346             };
1347              
1348             $is_open;
1349             }
1350              
1351             sub _log_dump {
1352             my ($direction, $fh, $data, $offset, $len) = @_;
1353              
1354             my $addr = 0;
1355             $len = length($$data) - $offset
1356             if !defined $len;
1357             return 1 if $len <= 0;
1358              
1359             my ($hexvals, $line);
1360             ## Print data in dump format.
1361             while ($len > 0) {
1362             ## Convert up to the next 16 chars to hex, padding w/ spaces.
1363             if ($len >= 16) {
1364             $line = substr $$data, $offset, 16;
1365             } else {
1366             $line = substr $$data, $offset, 16;
1367             }
1368             $hexvals = unpack("H*", $line);
1369             $hexvals .= ' ' x (32 - length $hexvals);
1370              
1371             ## Place in 16 columns, each containing two hex digits.
1372             $hexvals = sprintf("%s %s %s %s " x 4, unpack("a2" x 16, $hexvals));
1373              
1374             ## For the ASCII column, change unprintable chars to a period.
1375             $line =~ s/[\000-\037,\177-\237]/./g;
1376              
1377             ## Print the line in dump format.
1378             &_log_print($fh, sprintf("%s 0x%5.5lx: %s%s\n",
1379             $direction, $addr, $hexvals, $line));
1380              
1381             $addr += 16;
1382             $offset += 16;
1383             $len -= 16;
1384             }
1385              
1386             &_log_print($fh, "\n");
1387              
1388             1;
1389             }
1390              
1391             sub _log_print {
1392             my ($fh, $buf) = @_;
1393             local $\ = '';
1394              
1395             if (ref($fh) eq "GLOB") { # fh is GLOB ref
1396             print $fh $buf;
1397             } else { # fh isn't GLOB ref
1398             $fh->print($buf);
1399             }
1400             }
1401              
1402             sub _match_check {
1403             my ($self, $code) = @_;
1404             my $error;
1405             my @warns = ();
1406             local $@;
1407              
1408             ## Use eval to check for syntax errors or warnings.
1409             {
1410             local $SIG{"__DIE__"} = "DEFAULT";
1411             local $SIG{"__WARN__"} = sub { push @warns, @_ };
1412             local $^W = 1;
1413             local $_ = '';
1414             eval "\$_ =~ $code;";
1415             }
1416             if ($@) {
1417             ## Remove useless lines numbers from message.
1418             ($error = $@) =~ s/ at \(eval \d+\) line \d+.?//;
1419             chomp $error;
1420             return $self->error("bad match operator: $error");
1421             } elsif (@warns) {
1422             ## Remove useless lines numbers from message.
1423             ($error = shift @warns) =~ s/ at \(eval \d+\) line \d+.?//;
1424             $error =~ s/ while "strict subs" in use//;
1425             chomp $error;
1426             return $self->error("bad match operator: $error");
1427             }
1428              
1429             1;
1430             }
1431              
1432             sub _new_handle {
1433             if ($INC{"IO/Handle.pm"}) {
1434             return IO::Handle->new;
1435             } else {
1436             require FileHandle;
1437             return FileHandle->new;
1438             }
1439             }
1440              
1441             sub _normalize {
1442             $_ = join "", @_;
1443              
1444             1 while s/[^\cH\c?][\cH\c?]//mg; # ^H ^?
1445             s/^.*\cU//mg; # ^U
1446              
1447             return wantarray ? split /$/m, $_ : $_; # ORS instead?
1448             }
1449              
1450             sub _parse_cmd_remove_mode {
1451             my ($self, $arg) = @_;
1452              
1453             my $crm;
1454             if ($arg =~ /^\d+$/) {
1455             $crm = $arg
1456             } elsif ($arg =~ /^\s*auto\s*$/i) {
1457             $crm = "auto"
1458             } else {
1459             &_carp($self, "ignoring bad Cmd_remove_mode " .
1460             "argument \"$arg\": it's not \"auto\" or a " .
1461             "non-negative integer");
1462             $crm = $self->{cmd_rm_mode}
1463             }
1464             $crm
1465             }
1466              
1467             sub _parse_errmode {
1468             my ($self, $errmode) = @_;
1469              
1470             ## Set the error mode.
1471             if (!defined $errmode) {
1472             &_carp($self, "ignoring undefined Errmode argument");
1473             $errmode = $self->{errormode};
1474             } elsif ($errmode =~ /^\s*return\s*$/i) {
1475             $errmode = "return";
1476             } elsif ($errmode =~ /^\s*die\s*$/i) {
1477             $errmode = "die";
1478             } elsif (ref($errmode) eq "CODE") {
1479             } elsif (ref($errmode) eq "ARRAY") {
1480             unless (ref($errmode->[0]) eq "CODE") {
1481             &_carp($self, "ignoring bad Errmode argument: " .
1482             "first list item isn't a code ref");
1483             $errmode = $self->{errormode};
1484             }
1485             } else {
1486             &_carp($self, "ignoring bad Errmode argument \"$errmode\"");
1487             $errmode = $self->{errormode};
1488             }
1489             $errmode;
1490             }
1491              
1492             sub _parse_family {
1493             my ($self, $arg) = @_;
1494              
1495             my $family;
1496             if ($arg =~ /^(?:(?:(:?ip)?v?(?:4|6))|${\AF_INET}|$AF_INET6)$/) {
1497             if ($arg =~ /^(?:(?:(:?ip)?v?4)|${\AF_INET})$/) {
1498             $family = 'ipv4' # AF_INET
1499             } else {
1500             if (!$HAVE_IO_Socket_IP) {
1501             return $self->error("IO::Socket::IP required for IPv6")
1502             }
1503             $family = 'ipv6' # $AF_INET6
1504             }
1505             } else {
1506             return $self->error("bad Family argument \"$arg\": " .
1507             "must be \"ipv4\" or \"ipv6\"");
1508             }
1509             $family
1510             }
1511              
1512             sub _parse_family_to_num {
1513             my ($self, $arg) = @_;
1514             if ($arg eq 'ipv4') {
1515             return AF_INET
1516             } elsif ($arg eq 'ipv6') {
1517             return $AF_INET6
1518             } else {
1519             return $self->error("invalid address family - `$arg'");
1520             }
1521             }
1522              
1523             sub _parse_port {
1524             my ($self, $arg) = @_;
1525              
1526             my $port;
1527             if ($arg =~ /^\d{1,5}$/) {
1528             if (($arg > 0) and ($arg < 65536)) {
1529             $port = $arg
1530             } else {
1531             return $self->error("not a valid port - `$arg'")
1532             }
1533             } else {
1534             return $self->error("port not a valid number - `$arg'")
1535             }
1536             $port
1537             }
1538              
1539             sub _parse_prompt {
1540             my ($self, $prompt) = @_;
1541              
1542             unless (defined $prompt) {
1543             $prompt = "";
1544             }
1545              
1546             return $self->error("bad Prompt argument \"$prompt\": " .
1547             "missing opening delimiter of match operator")
1548             unless $prompt =~ m(^\s*/) or $prompt =~ m(^\s*m\s*\W);
1549              
1550             $prompt;
1551             }
1552              
1553             sub _parse_timeout {
1554             my ($self, $arg) = @_;
1555              
1556             my $timeout;
1557             if ($arg =~ /^\d+$/) {
1558             $timeout = $arg
1559             } else {
1560             return $self->error("not a valid timeout - `$arg'")
1561             }
1562             $timeout
1563             }
1564              
1565             sub _parse_waitfor_pause {
1566             my ($self, $arg) = @_;
1567              
1568             my $wfp;
1569             if ($arg =~ /^[0-9]*\.?[0-9]+$/) {
1570             $wfp = $arg
1571             } else {
1572             return $self->error("not a valid waitfor_pause - `$arg'")
1573             }
1574             $wfp
1575             }
1576              
1577             sub _put {
1578             my ($self, $buf, $subname) = @_;
1579              
1580             return $self->error("write error: filehandle isn't open")
1581             unless $self->{opened};
1582              
1583             if (exists $self->{_SSH_CHAN_}) {
1584             my $nwrote = $self->{_SSH_CHAN_}->write($$buf);
1585              
1586             ## Display network traffic if requested.
1587             if ($self->{dumplog}) {
1588             &_log_dump('>', $self->{dumplog}, $buf, 0, $nwrote);
1589             }
1590             } else {
1591             return $self->error("Net::SSH2::Channel not created")
1592             }
1593             1
1594             }
1595              
1596             sub _prep_regex {
1597             my ($regex) = @_;
1598             # strip leading / if found
1599             $regex =~ s/^\///;
1600             # strip trailing / if found
1601             $regex =~ s/\/$//;
1602              
1603             return $regex
1604             }
1605              
1606             ##################################################
1607             # DNS hostname resolution
1608             # return:
1609             # $host->{name} = host - as passed in
1610             # $host->{host} = host - as passed in without :port
1611             # $host->{port} = OPTIONAL - if :port, then value of port
1612             # $host->{addr} = resolved numeric address
1613             # $host->{family} = AF_INET/6
1614             ############################
1615             sub _resolv {
1616             my ($self, $name, $family) = @_;
1617              
1618             my %h;
1619             $h{name} = $name;
1620              
1621             # Default to IPv4 for backward compatiblity
1622             # THIS MAY CHANGE IN THE FUTURE!!!
1623             if (!defined $family) {
1624             $family = AF_INET
1625             }
1626              
1627             # START - host:port
1628             my $cnt = 0;
1629              
1630             # Count ":"
1631             $cnt++ while ($name =~ m/:/g);
1632              
1633             # 0 = hostname or IPv4 address
1634             if ($cnt == 0) {
1635             $h{host} = $name
1636             # 1 = IPv4 address with port
1637             } elsif ($cnt == 1) {
1638             ($h{host}, $h{port}) = split /:/, $name
1639             # >=2 = IPv6 address
1640             } elsif ($cnt >= 2) {
1641             #IPv6 with port - [2001::1]:port
1642             if ($name =~ /^\[.*\]:\d{1,5}$/) {
1643             ($h{host}, $h{port}) = split /:([^:]+)$/, $name # split after last :
1644             # IPv6 without port
1645             } else {
1646             $h{host} = $name
1647             }
1648             }
1649              
1650             # Clean up host
1651             $h{host} =~ s/\[//g;
1652             $h{host} =~ s/\]//g;
1653             # Clean up port
1654             if (defined $h{port} && (($h{port} !~ /^\d{1,5}$/) || ($h{port} < 1) || ($h{port} > 65535))) {
1655             $self->errmsg("Invalid port `$h{port}' in `$name'");
1656             return undef
1657             }
1658             # END - host:port
1659              
1660             # address check
1661             # new way
1662             if (version->parse($Socket::VERSION) >= version->parse(1.94)) {
1663             my %hints = (
1664             family => $AF_UNSPEC,
1665             protocol => IPPROTO_TCP,
1666             flags => $AI_NUMERICHOST
1667             );
1668              
1669             # numeric address, return
1670             my ($err, @getaddr) = Socket::getaddrinfo($h{host}, undef, \%hints);
1671             if (defined $getaddr[0]) {
1672             $h{addr} = $h{host};
1673             $h{family} = $getaddr[0]->{family};
1674             return \%h
1675             }
1676             # old way
1677             } else {
1678             # numeric address, return
1679             my $ret = gethostbyname($h{host});
1680             if (defined $ret && (inet_ntoa($ret) eq $h{host})) {
1681             $h{addr} = $h{host};
1682             $h{family} = AF_INET;
1683             return \%h
1684             }
1685             }
1686              
1687             # resolve
1688             # new way
1689             if (version->parse($Socket::VERSION) >= version->parse(1.94)) {
1690             my %hints = (
1691             family => $family,
1692             protocol => IPPROTO_TCP
1693             );
1694              
1695             my ($err, @getaddr) = Socket::getaddrinfo($h{host}, undef, \%hints);
1696             if (defined $getaddr[0]) {
1697             my ($err, $address) = Socket::getnameinfo($getaddr[0]->{addr}, $NI_NUMERICHOST);
1698             if (defined $address) {
1699             $h{addr} = $address;
1700             $h{addr} =~ s/\%(.)*$//; # remove %ifID if IPv6
1701             $h{family} = $getaddr[0]->{family};
1702             return \%h
1703             } else {
1704             $self->errmsg("getnameinfo($getaddr[0]->{addr}) failed - $err");
1705             return undef
1706             }
1707             } else {
1708             my $LASTERROR = sprintf "getaddrinfo($h{host},,%s) failed - $err", ($family == AF_INET) ? "AF_INET" : "AF_INET6";
1709             $self->errmsg($LASTERROR);
1710             return undef
1711             }
1712             # old way
1713             } else {
1714             if ($family == $AF_INET6) {
1715             $self->errmsg("Socket >= 1.94 required for IPv6 - found Socket $Socket::VERSION");
1716             return undef
1717             }
1718              
1719             my @gethost = gethostbyname($h{host});
1720             if (defined $gethost[4]) {
1721             $h{addr} = inet_ntoa($gethost[4]);
1722             $h{family} = AF_INET;
1723             return \%h
1724             } else {
1725             $self->errmsg("gethostbyname($h{host}) failed - $^E");
1726             return undef
1727             }
1728             }
1729             }
1730              
1731             sub _user_caller {
1732             my ($obj) = @_;
1733              
1734             my ($class, $curr_pkg, $file, $i, $line, $pkg, %isa, @isa);
1735             local $@;
1736             local $_;
1737              
1738             ## Create a boolean hash to test for isa. Make sure current
1739             ## package and the object's class are members.
1740             $class = ref $obj;
1741             @isa = eval "\@${class}::ISA";
1742             push @isa, $class;
1743             ($curr_pkg) = caller 1;
1744             push @isa, $curr_pkg;
1745             %isa = map { $_ => 1 } @isa;
1746              
1747             ## Search back in call frames for a package that's not in isa.
1748             $i = 1;
1749             while (($pkg, $file, $line) = caller ++$i) {
1750             next if $isa{$pkg};
1751              
1752             return ($pkg, $file, $line);
1753             }
1754              
1755             ## If not found, choose outer most call frame.
1756             ($pkg, $file, $line) = caller --$i;
1757             return ($pkg, $file, $line);
1758             }
1759              
1760             1;
1761              
1762             __END__