File Coverage

lib/App/Tel.pm
Criterion Covered Total %
statement 233 343 67.9
branch 70 132 53.0
condition 28 72 38.8
subroutine 37 53 69.8
pod 20 20 100.0
total 388 620 62.5


line stmt bran cond sub pod time code
1             package App::Tel;
2 7     7   324204 use strict;
  7         10  
  7         169  
3 7     7   26 use warnings;
  7         9  
  7         165  
4 7     7   4791 use Expect qw( exp_continue );
  7         206758  
  7         430  
5 7     7   53 use POSIX qw(:sys_wait_h :unistd_h); # For WNOHANG
  7         7  
  7         40  
6 7     7   5648 use Module::Load;
  7         5248  
  7         38  
7 7     7   2634 use App::Tel::HostRange qw (check_hostrange);
  7         10  
  7         319  
8 7     7   2142 use App::Tel::Passwd;
  7         13  
  7         270  
9 7     7   2212 use App::Tel::Color;
  7         12  
  7         239  
10 7     7   2169 use App::Tel::Macro;
  7         12  
  7         331  
11 7     7   2205 use App::Tel::Merge qw ( merge );
  7         10  
  7         329  
12 7     7   2343 use App::Tel::Expect;
  7         9  
  7         218  
13 7     7   2779 use Time::HiRes qw ( sleep );
  7         6117  
  7         28  
14 7     7   858 use v5.10;
  7         14  
15              
16             =head1 NAME
17              
18             App::Tel - A script for logging into devices
19              
20             =head1 VERSION
21              
22             0.201601
23              
24             =cut
25              
26             our $VERSION = '0.201601';
27              
28              
29             =head1 SYNOPSIS
30              
31             tel gw1-my-dev
32              
33             See the README and COMMANDS files for examples of usage and ways to extend the
34             application.
35              
36             =head1 AUTHOR
37              
38             Robert Drake, C<< >>
39              
40             =head1 COPYRIGHT & LICENSE
41              
42             Copyright 2006 Robert Drake, all rights reserved.
43              
44             This program is free software; you can redistribute it and/or modify it
45             under the same terms as Perl itself.
46              
47             =cut
48              
49             #### GLOBALS
50             # For reasons related to state I needed to make $_winch_it global
51             # because it needs to be written inside signals.
52             my $_winch_it=0;
53              
54             sub _winch_handler {
55 0     0   0 $_winch_it=1;
56             }
57              
58             sub _winch {
59 1     1   3 my $session = shift->session;
60             # these need to be wrapped in eval or you get Given filehandle is not a
61             # tty in clone_winsize_from if you call winch() under a scripted
62             # environment like rancid (or just under par, or anywhere there is no pty)
63 1         2 eval {
64 1         11 $session->slave->clone_winsize_from(\*STDIN);
65 0 0       0 kill WINCH => $session->pid if $session->pid;
66             };
67 1         851 $_winch_it=0;
68 1         9 $SIG{WINCH} = \&_winch_handler;
69             }
70              
71             =head1 METHODS
72              
73             =head2 new
74              
75             my $tel = App::Tel->new();
76              
77             Creates a new App::Tel object.
78              
79             =cut
80              
81             sub new {
82 6     6 1 2160 my ($class, %args) = @_;
83              
84             my $self = {
85             'stdin' => Expect->exp_init(\*STDIN),
86             'connected' => 0,
87             'enabled' => 0,
88             'title_stack' => 0,
89             'log_stdout' => 1,
90             'profile' => {},
91             'perl' => $args{perl} || '',
92             'opts' => $args{opts},
93 6   100     57 'colors' => App::Tel::Color->new($args{opts}->{d}),
94             };
95              
96 6         24 return bless($self, $class);
97             }
98              
99              
100             =head2 go
101              
102             $self->go('hostname');
103              
104             Handles all the routines of connecting, enable, looping and disconnection.
105             This is the method called by bin/tel for each host.
106              
107             =cut
108              
109             sub go {
110 1     1 1 2 my ($self, $host) = @_;
111              
112             # default profile always loads before anything else. replace == 1
113 1         4 $self->profile('default', 1);
114 1 50       3 $self->profile($self->{opts}->{P}) if ($self->{opts}->{P});
115 1         2 $self->hostname($host);
116 1         2 $self->login($self->hostname);
117 1 50       4 $self->profile($self->{opts}->{A}) if ($self->{opts}->{A});
118 1 50       4 if ($self->connected) {
119 1         3 $self->enable->logging->control_loop->disconnect;
120             }
121              
122 0         0 return $self;
123             }
124              
125             =head2 disconnect
126              
127             $self->disconnect($hard_close);
128              
129             Tears down the session gracefully and resets internal variables to their
130             default values. Useful if you want to connect to another host after the first
131             one.
132              
133             If you supply a true value, it will hard_close the session.
134              
135             =cut
136              
137             sub disconnect {
138 0     0 1 0 my ($self, $hard) = @_;
139 0         0 $self->{profile} = {};
140 0 0       0 $self->{timeout} = $self->{opts}->{t} ? $self->{opts}->{t} : 90;
141 0         0 $self->{banners} = undef;
142 0         0 $self->{methods} = ();
143 0         0 $self->connected(0);
144 0         0 $self->{colors}=App::Tel::Color->new($self->{opts}->{d});
145 0         0 $self->{enabled}=0;
146              
147 0 0       0 if ($self->{title_stack} > 0) {
148 0         0 $self->{title_stack}--;
149 0         0 print "\e[23t";
150             }
151              
152 0 0       0 if ($hard) {
153 0         0 $self->session->hard_close();
154             } else {
155 0         0 $self->session->soft_close();
156             }
157 0         0 return $self;
158             }
159              
160             =head2 send
161              
162             $self->send("text\r");
163              
164             Wrapper for Expect's send() method.
165              
166             =cut
167              
168             sub send {
169 4     4 1 9 return shift->session->send(@_);
170             }
171              
172             =head2 expect
173              
174             $self->expect("text");
175              
176             Wrapper for Expect's expect() method.
177              
178             =cut
179              
180             sub expect {
181 2     2 1 7 return shift->session->expect(@_);
182             }
183              
184             =head2 load_config
185              
186             Loads the config from /etc/telrc, /usr/local/etc/telrc, $ENV{HOME}/.telrc2, or
187             it can be appended to by using the environment variable TELRC, or overridden
188             by calling load_config with an argument:
189              
190             $self->load_config('/home/user/.my_custom_override');
191              
192             =cut
193              
194             sub load_config {
195 4     4 1 355 my $self = shift;
196 4   33     27 my $xdg = $ENV{XDG_CONFIG_HOME} || "$ENV{HOME}/.config/";
197 4         9 my @configs = @_;
198 4 50       17 @configs = ( "/etc/telrc", "/usr/local/etc/telrc", "$ENV{HOME}/.telrc2", "$xdg/telrc") if (!@configs);
199 4 50       12 push(@configs, $ENV{TELRC}) if (defined($ENV{TELRC}));
200 4         4 our $telrc;
201 4         5 my $config;
202              
203 4         10 foreach my $conf (@configs) {
204 4 100       248 if (-r $conf) {
205 3         1593 require $conf;
206 3         35 push(@{$config->{'telrc_file'}}, $conf);
  3         12  
207 3         13 $config = merge($config, $telrc);
208             }
209             }
210              
211 4 100       12 if (!defined($config->{'telrc_file'})) {
212 1         18 warn "No configuration files loaded. You may need to run mktelrc.";
213             }
214              
215             # load global syntax highlighting things if found
216 4         81 $self->{colors}->load_syntax($config->{syntax});
217 4         6 $self->{config} = $config;
218 4         13 return $self;
219             }
220              
221             =head2 hostname
222              
223             $hostname = $self->hostname("hostname");
224              
225             Called to parse the hostname provided by the user when first making a
226             connection. If the hostname has special attributes like a port designation,
227             it's parsed here.
228              
229             This also looks up the profile for the hostname to see if it needs to be
230             translated because of an alias. The final hostname is stored and returned.
231              
232              
233             =cut
234              
235             sub hostname {
236 14     14 1 6478 my ($self, $hostname) = @_;
237              
238 14 100       26 if (!defined($hostname)) {
239 2         7 return $self->{hostname};
240             }
241              
242 12         20 $hostname =~ s#/tftpboot/##;
243 12         13 $hostname =~ s/-confg//;
244              
245 12 100 100     134 if ($hostname =~ qr@(ssh|telnet)://\[([a-zA-Z0-9\-\.\:]+)\](?::(\d+))?@) {
    100          
    100          
    100          
246 2 100       7 $self->{port} = $3 if ($3);
247 2         5 $self->methods($1);
248 2         3 $hostname = $2;
249             } elsif ($hostname =~ qr@(ssh|telnet)://([a-zA-Z0-9\-\.]+)(?::(\d+))?@) {
250 2 100       9 $self->{port} = $3 if ($3);
251 2         4 $self->methods($1);
252 2         3 $hostname = $2;
253             } elsif ($hostname =~ /\[(\S+)\](?::(\d+))?/) {
254 2 100       8 $self->{port} = $2 if ($2);
255 2 100       4 $self->methods('telnet') if ($2);
256 2         3 $hostname = $1;
257             } elsif (($hostname =~ tr/://) < 2 && $hostname =~ /(\S+):(\d+)$/) {
258 1         6 $self->{port} = $2;
259 1         4 $self->methods('telnet');
260 1         2 $hostname = $1;
261             }
262              
263             # load profile based on the hostname
264 12         101 $self->rtr_find($hostname);
265              
266             # if rtr->hostname is defined as code then call it to determine the real hostname
267             # if it's not then treat it as an alias.
268 12 100       16 if (defined($self->profile->{hostname})) {
269 2 100       4 if (ref($self->profile->{hostname}) eq 'CODE') {
270 1         2 $hostname = &{$self->profile->{hostname}}($hostname);
  1         2  
271             } else {
272 1         3 $hostname = $self->profile->{hostname};
273             }
274             }
275 12         20 $self->{hostname} = $hostname;
276 12         27 return $self->{hostname};
277             }
278              
279             =head2 methods
280              
281             $self->methods();
282              
283             This is used to determine the method used to connect to the remote device.
284             Generally, the CLI argument -m has the highest priority. The uri should be
285             second, profiles third, and the defaults would be last. If called without
286             arguments it will return whatever the currently decided method array is. If
287             called with an argument that will be set as the new method array.
288              
289             If you call it multiple times it won't change right now. I may need to
290             rethink this later but right now it works with the way the program flows.
291             $self->disconnect removes all methods so connecting to another router will run
292             this again.
293              
294             $self->methods('ssh', 'telnet');
295              
296             =cut
297              
298             sub methods {
299 11     11 1 16 my $self = shift;
300              
301 11 100       74 if (@_) {
    100          
    100          
    100          
302 7         7 @{$self->{methods}} = @_;
  7         17  
303             } elsif (defined($self->{methods})) {
304 1         4 return $self->{methods};
305             } elsif ($self->{opts}->{m}) {
306 1         3 $self->{methods} = [ $self->{opts}->{m} ];
307             } elsif (defined($self->{'profile'}->{method})) {
308 1         5 @{$self->{methods}} = split(/,/, $self->{'profile'}->{method});
  1         4  
309             } else {
310 1         4 $self->{methods} = [ 'ssh', 'telnet' ];
311             }
312              
313 10         21 return $self->{methods};
314             }
315              
316             # this is overridable in the individual test file to connect a special
317             # way if the method is listed as "test"
318 0     0   0 sub _test_connect { undef }
319              
320             sub _banners {
321 1     1   2 my $self = shift;
322 1 50       5 return $self->{banners} if ($self->{banners});
323 1         2 my $config = $self->{'config'};
324              
325             # if there are no banners then we want to return an empty list
326 1         3 $self->{banners} = [];
327 1         3 while (my ($regex, $profile) = each %{$config->{banners}}) {
  1         6  
328 0     0   0 push @{$self->{banners}}, [ $regex, sub { $self->profile($profile); exp_continue; } ];
  0         0  
  0         0  
  0         0  
329             }
330 1         35 return $self->{banners};
331             }
332              
333             =head2 rtr_find
334              
335             my $profile = $self->rtr_find($regex);
336              
337             Find the router by hostname/regex and load the config associated with it.
338             Load a profile for it if there is one.
339              
340             If none of the router lines match then this does not merge another profile,
341             but will return the existing profile. That may not be what you want in all
342             cases. The workaround for it would be to clear profiles before calling this
343             with $self->profile('default',1);
344              
345             I'm trying to decide if there should be a better way to do this.
346              
347             =cut
348              
349             sub rtr_find {
350 12     12 1 12 my ($self, $host) = @_;
351 12         13 my $profile = $self->{'profile'};
352 12         9 my $config = $self->{'config'};
353              
354 12         10 foreach my $h (@{$config->{rtr}}) {
  12         20  
355 4         8 my $h2 = $h->{regex};
356 4 100 66     60 if ($host =~ /$h2/i || check_hostrange($h2, $host)) {
357 3         8 $profile=merge($profile, $h);
358 3         5 last;
359             }
360             }
361 12         14 $self->{'profile'}=$profile;
362              
363             # if the host specified a profile to load then load it here
364 12 50       22 if (defined($profile->{profile})) {
365 0         0 $self->profile($profile->{profile});
366             }
367              
368 12         12 return $self;
369             }
370              
371             =head2 profile
372              
373             $profile = $self->profile;
374             $profile = $self->profile('a_example_profile', $replace);
375              
376             Called without arguments this will return the current profile. If called with
377             a profile name it will load that new profile on top of whatever profile
378             currently exists. You can set the second argument to true if you want to
379             replace the current profile with the new one.
380              
381             =cut
382              
383             sub profile {
384 22     22 1 25 my ($self, $profile_arg, $replace) = @_;
385 22         27 my $stdin = $self->{'stdin'};
386 22         50 my $profile = $self->{'profile'};
387              
388 22 100       64 return $profile if (!defined($profile_arg));
389              
390 2 50       8 if ($replace) {
391             # wipe out the old profile if we're replacing it.
392 2         4 $profile = {};
393             }
394              
395 2         8 foreach(split(/\+/, $profile_arg)) {
396 2         12 $profile = merge($profile, $self->{'config'}->{profile}{$_});
397              
398             # load handlers for profile
399 2 50       6 if ($profile->{handlers}) {
400 0         0 foreach my $v (keys %{$profile->{handlers}}) {
  0         0  
401 0         0 $stdin->set_seq($v, $profile->{handlers}{$v}, [ \$self ]);
402             }
403             }
404             # load syntax highlight
405 2         10 $self->{colors}->load_syntax($profile->{syntax});
406 2         5 $profile->{profile_name}=$_;
407             }
408              
409             # add some sane defaults if the profile doesn't have them
410 2   33     22 $profile->{'user'} ||= $ENV{'USER'} || $ENV{'LOGNAME'};
      66        
411             # need to warn if user is still not defined?
412 2         4 $self->{'profile'}=$profile;
413 2         3 return $profile;
414             }
415              
416             sub _stty_rows {
417 0     0   0 my $new_rows = shift;
418 0         0 eval {
419 0         0 Module::Load::load Term::ReadKey;
420 0         0 my ($columns, undef, $xpix, $ypix) = GetTerminalSize(\*STDOUT);
421 0         0 SetTerminalSize($columns, $new_rows, $xpix, $ypix, \*STDOUT);
422             };
423              
424 0 0       0 warn $@ if ($@);
425             }
426              
427             =head2 password
428              
429             my $password = $self->password;
430              
431             This pulls the password from the config. If the password is blank it checks
432             to see if you have a password manager, then tries to load the password from
433             there. It then tries to use an OS keyring.
434              
435             By default it will pull the regular password. To pull the enable password
436             you can call it with $self->password('enable');
437              
438             =cut
439              
440             sub password {
441 2     2 1 4 my ($self, $type) = @_;
442 2         10 my $profile = $self->profile;
443 2         3 my $router = $self->{hostname};
444              
445 2   100     13 $type ||= 'password';
446              
447 2 50 66     9 warn "Unknown password type $type" if ($type ne 'password' && $type ne 'enable');
448              
449 2 50 33     13 if (defined($profile->{$type}) && $profile->{$type} ne '') {
450 2         11 return $profile->{$type};
451             }
452              
453             # if enable is blank but password has something then use the same password
454             # for enable.
455 0 0 0     0 if ($type eq 'enable' and $profile->{$type} eq '' && $profile->{'password'} ne '') {
      0        
456 0         0 return $profile->{'password'};
457             }
458              
459             # if we get here, the password is blank so try other means
460 0         0 my $pass = App::Tel::Passwd::load_from_profile($profile);
461 0 0       0 if ($pass ne '') {
462 0         0 return $pass;
463             }
464              
465             # I was wondering how to decide what to prompt for, but I think it should
466             # be whichever profile loaded. So maybe we check for hostname and check
467             # for profile name and then save as profile name. If they want to be
468             # explicit they should specify the password format as KEYRING or
469             # something.. I dunno.
470 0         0 App::Tel::Passwd::keyring($profile->{user}, $profile->{profile_name}, $profile->{profile_name});
471              
472             # if they make it here and still don't have a password then none was
473             # defined anywhere and we probably should prompt for one. Consider
474             # turning off echo then normal read.
475 0         0 return App::Tel::Passwd::input_password($router);
476             }
477              
478             =head2 session
479              
480             my $session = $self->session;
481              
482             Expect won't let you reuse a connection that has spawned a process, so you
483             can call this with an argument to reset the session. If called without an
484             argument it will return the current session (If it exists) or create a new
485             session.
486              
487             =cut
488              
489             sub session {
490 9 100 66 9 1 72 return $_[0]->{'session'} if (!$_[1] && defined($_[0]->{'session'}));
491              
492 1         1 my $self = shift;
493 1         2 my $session = $self->{'session'};
494              
495 1 50 33     3 $session->soft_close() if ($session && $session->pid());
496 1         6 $session = Expect->new;
497              
498             # install sig handler for window size change
499 1         918 $SIG{WINCH} = \&_winch_handler;
500              
501              
502 1         14 $session->log_stdout(1);
503 1         32 $self->{'session'} = $session;
504 1         2 return $session;
505             }
506              
507             =head2 connect
508              
509             my $session = $self->connect('routername');
510              
511             This sets up the session. If there already is a session open it closes and opens a new one.
512              
513             =cut
514              
515             sub connect {
516 1     1 1 9 my ($self, @arguments) = @_;
517              
518 1         4 $self->connected(0);
519 1         4 my $session = $self->session(1);
520 1         5 $session->spawn(@arguments);
521 1         2787 return $session;
522             }
523              
524             =head2 connected
525              
526             if ($self->connected);
527             or
528             $self->connected(1);
529              
530             Returns connection status, or sets the status to whatever value is supplied to
531             the method.
532              
533             Note: This isn't the session state, but an indicator that our session has
534             gotten through the login stage and is now waiting for input. i.e., the router
535             is at a prompt of some kind.
536              
537             =cut
538              
539             sub connected {
540 4     4 1 6 my ($self, $status) = @_;
541 4 100       8 if (defined($status)) {
542 2         6 $self->{connected}=$status;
543             }
544              
545 4         7 return $self->{connected};
546             }
547              
548             =head2 enable
549              
550             $self->enable;
551              
552             if enablecmd is set then this method attempts to enable the user. Returns
553             $self.
554              
555             =cut
556              
557             sub enable {
558 1     1 1 2 my $self = shift;
559 1         4 my $profile = $self->profile;
560              
561 1 50       5 if ($profile->{enablecmd}) {
562 1         4 $self->send($profile->{enablecmd} . "\r");
563              
564 1   33     87 $profile->{ena_username_prompt} ||= qr/[Uu]ser[Nn]ame:|Login:/;
565 1   33     7 $profile->{ena_password_prompt} ||= qr/[Pp]ass[Ww]ord/;
566              
567             # we need to be able to handle routers that prompt for username and password
568             # need to check the results to see if enable succeeded
569             $self->expect($self->{timeout},
570 1     1   367 [ $profile->{ena_username_prompt} => sub { $self->send("$profile->{user}\r"); exp_continue; } ],
  1         45  
571 1     1   16 [ $profile->{ena_password_prompt} => sub { $self->send($self->password('enable') . "\r"); } ]
  1         452  
572             );
573             }
574              
575 1         68 $self->{enabled}=1;
576 1         5 return $self;
577             }
578              
579             =head2 enabled
580              
581             my $enabled = $self->enabled;
582              
583             Check if enabled. This returns true if the $self->enable() method succeeds.
584              
585             =cut
586              
587 1     1 1 656 sub enabled { $_[0]->{enabled} };
588              
589             =head2 login
590              
591             $self->login("hostname");
592              
593             Cycles through the connection methods trying each in the order specified by
594             the profile until we successfully connect to the host. Returns $self.
595              
596             =cut
597              
598             sub login {
599 1     1 1 2 my ($self, $hostname) = @_;
600              
601             # dumb stuff to alias $rtr to the contents of $self->{'profile'}
602             # needed because we can reload the profile inside the expect loop
603             # and can't update the alias.
604 1         1 our $rtr;
605 1         3 *rtr = \$self->{'profile'};
606              
607 1         1 my $ssho = '-o StrictHostKeyChecking=no';
608 1 50 33     6 if (defined($rtr->{sshoptions}) && scalar $rtr->{sshoptions} > 0) {
609 1         2 $ssho = '-o '. join(' -o ', @{$rtr->{sshoptions}});
  1         4  
610             }
611 1 50       3 $ssho .= $rtr->{ciphertype} ? " -c $rtr->{ciphertype}" : '';
612              
613             # because we use last METHOD; in anonymous subs this suppresses the
614             # warning of "exiting subroutine via last;"
615 7     7   34 no warnings 'exiting';
  7         7  
  7         7820  
616             # handle MOTD profile loading, and other things parsed from the config
617 1         1 my @dynamic;
618 1 50       3 if (defined($rtr->{prompt})) {
619 0     0   0 push @dynamic, [ qr/$rtr->{prompt}/, sub { $self->connected(1); last METHOD; } ];
  0         0  
  0         0  
620             }
621              
622             # handle prompts in foreign languages or other things we didn't think of
623 1   33     6 $rtr->{username_prompt} ||= qr/[Uu]ser[Nn]ame:|[Ll]ogin:/;
624 1   33     6 $rtr->{password_prompt} ||= qr/[Pp]ass[Ww]ord/;
625              
626 1   33     7 $self->{port} ||= $self->{opts}->{p} || $rtr->{port}; # get port from CLI or the profile
      33        
627             # if it's not set in the profile or CLI above, it gets set in the
628             # method below, but needs to be reset on each loop to change from
629             # telnet to ssh defaults
630              
631 1         1 my $family = '';
632 1 50       3 $family = '-4' if ($self->{opts}->{4});
633 1 50       8 $family = '-6' if ($self->{opts}->{6});
634              
635 1         1 METHOD: for (@{$self->methods}) {
  1         6  
636 1         2 my $p = $self->{port};
637 1 50 0     5 if ($_ eq 'ssh') { $p ||= 22; $self->connect("ssh $family -p $p -l $rtr->{user} $ssho $hostname"); }
  0 50       0  
  0 50       0  
638 0   0     0 elsif ($_ eq 'telnet') { $p ||= ''; $self->connect("telnet $family $hostname $p"); }
  0         0  
639 1         3 elsif ($_ eq 'test') { $self->_test_connect($hostname); }
640 0         0 else { die "No program defined for method $_\n"; }
641              
642             # suppress stdout if needed
643 1         5 $self->session->log_stdout($self->{log_stdout});
644              
645             # need to make this optional
646             # also need to make it display whatever the user cares about.
647 1         51 print "\e[22t\033]0;$_ $hostname\007";
648 1         2 $self->{title_stack}++;
649 1     0   17 $SIG{INT} = sub { for (1..$self->{title_stack}) { print "\e[23t"; } $self->{title_stack}=0; };
  0         0  
  0         0  
  0         0  
650              
651             $self->expect($self->{timeout},
652 1         6 @{$self->_banners},
653             @dynamic,
654             [ $rtr->{username_prompt} => sub {
655 0     0   0 $self->send("$rtr->{user}\r");
656 0         0 exp_continue;
657             } ],
658             [ $rtr->{password_prompt} => sub {
659 1     1   19795 $self->send($self->password() ."\r");
660 1         78 $self->connected(1);
661 1         11 last METHOD;
662             } ],
663             [ qr/Name or service not known|hostname nor servname provided, or not known|could not resolve / => sub
664             {
665             # if host lookup fails then check to see if there is an alternate method defined
666 0 0 0 0   0 if ($rtr->{hostsearch} && !$rtr->{hostsearched}) {
667 0         0 $hostname = &{$rtr->{hostsearch}}($hostname);
  0         0  
668 0         0 $rtr->{hostsearched}=1;
669 0         0 redo METHOD;
670             } else {
671 0         0 warn "unknown host: $hostname\n";
672             # skip to next host if this one doesn't exist
673 0         0 last METHOD;
674             }
675             }
676             ],
677             # almost never needed anymore. Some people might not want a
678             # fallback to des. If anyone does we need to make it optional
679             #[ qr/cipher type \S+ not supported/ => sub { $rtr->{ciphertype}="des"; redo METHOD; } ],
680              
681             # removing these 4, they should be handled by eof anyway
682             #[ qr/ssh_exchange_identification/ => sub { next METHOD; } ],
683             #[ qr/[Cc]onnection (refused|closed)/ => sub { next METHOD; } ],
684             #[ qr/key_verify failed/ => sub { next METHOD; } ],
685             #[ qr/Corrupted/ => sub { next METHOD; } ],
686 0     0   0 [ 'eof' => sub { next METHOD; } ],
687 1     0   5 [ 'timeout' => sub { next METHOD; } ],
  0         0  
688             );
689             }
690              
691 1   50     28 $rtr->{logoutcmd} ||= "logout";
692 1   50     7 $rtr->{prompt} ||= '#';
693              
694 1 50       2 warn "Connection to $hostname failed.\n" if !$self->connected;
695 1         4 return $self;
696             }
697              
698             =head2 logging
699              
700             $self->logging('filename');
701              
702             Turns on logging for this session. If you specify a filename it will log to
703             /tmp/.log, otherwise it will use /tmp/.log.
704              
705             =cut
706              
707             sub logging {
708 1     1 1 2 my $self = shift;
709 1 50       11 return $self if (!$self->{opts}->{l});
710              
711 0         0 my $file = shift;
712 0   0     0 $file ||= $self->{hostname};
713 0 0       0 unlink ("/tmp/$file.log") if (-f "/tmp/$file.log");
714 0         0 $self->session->log_file("/tmp/$file.log");
715 0         0 return $self;
716             }
717              
718             =head2 run_commands
719              
720             $self->run_commands(@commands);
721              
722             TODO: Document this
723              
724              
725             =cut
726              
727             sub run_commands {
728 0     0 1 0 my $self = shift;
729 0         0 my $opts = $self->{opts};
730              
731 0         0 foreach my $arg (@_) {
732 0         0 $arg =~ s/\\r/\r/g; # fix for reload\ry. I believe 'perldoc quotemeta' explains why this happens
733 0         0 chomp($arg);
734 0         0 $self->send("$arg\r");
735             $self->expect($self->{timeout},
736       0     [ $self->profile->{prompt} => sub { } ],
737 0     0   0 [ 'eof' => sub { die "EOF From host.\n"; } ],
  0         0  
738             );
739 0 0       0 sleep($opts->{s}) if ($opts->{s});
740             }
741              
742 0         0 return $self;
743             }
744              
745             =head2 control_loop
746              
747             $self->control_loop();
748              
749             This is where control should be passed once the session is logged in. This
750             handles CLI commands passed via the -c option, or scripts executed with the -x
751             option. It also handles auto commands passed via either option -a on the
752             command line, or via autocmds in the profile.
753              
754             Calling this without any commands will just run interact()
755              
756             =cut
757              
758             sub control_loop {
759 1     1 1 1 my $self = shift;
760 1         3 my $profile = $self->profile;
761 1         2 my $opts = $self->{opts};
762 1         2 my $prompt = $profile->{prompt};
763 1         3 my $pagercmd = $profile->{pagercmd};
764 1         2 my $autocmds;
765             my @args;
766              
767 1 50       8 if ($opts->{a}) {
768 1         5 $autocmds = [ split(/;/, $opts->{a}) ];
769             } else {
770 0         0 $autocmds = $profile->{autocmds};
771             }
772              
773 1         4 $self->_winch();
774              
775             # should -c override -x or be additive? or error if both are specified?
776              
777 1 50       4 @args = split(/;/, $opts->{c}) if ($opts->{c});
778              
779 1 50       4 if ($opts->{x}) {
780 0         0 for (@{$opts->{x}}) {
  0         0  
781 0 0       0 open(my $X, '<', $_) || die "Can't open file $_\n";
782 0         0 push(@args,<$X>);
783 0         0 close $X;
784             }
785             }
786              
787 1 50       4 if (@args) {
788 0         0 $self->expect($self->{timeout},'-re',$prompt);
789 0 0       0 if (ref($pagercmd) eq 'CODE') {
    0          
790 0         0 $pagercmd->();
791             } elsif ($pagercmd) {
792 0         0 $self->run_commands("$pagercmd");
793             }
794 0         0 $self->run_commands(@args);
795 0         0 $self->send($profile->{logoutcmd} ."\r");
796             } else {
797 1 50       23 die 'STDIN Not a tty' if (!POSIX::isatty($self->{stdin}));
798 0 0         if ($autocmds) {
799 0           $self->expect($self->{timeout},'-re',$prompt);
800 0           eval { $self->run_commands(@$autocmds); };
  0            
801 0 0         return $self if ($@);
802             }
803              
804             my $color_cb = sub {
805 0     0     my ($session) = @_;
806 0 0         $self->_winch() if $_winch_it;
807 0           ${*$session}{exp_Pty_Buffer} = $self->{colors}->colorize(${*$session}{exp_Pty_Buffer});
  0            
  0            
808 0           return 1;
809 0           };
810              
811             my $sleep_cb = sub {
812 0     0     sleep($self->{opts}->{S});
813 0           return 1;
814 0           };
815 0           $self->session->set_cb($self->session,$color_cb, [ \${$self->session} ]);
  0            
816 0 0         $self->{stdin}->set_seq("\r",$sleep_cb) if ($self->{opts}->{S});
817 0           $self->session->interact($self->{stdin}, '\cD');
818             # q\b is to end anything that's at a More prompt or other dialog and
819             # get you back to the command prompt
820             # would be nice to detect if the session is closing down and not send
821             # this. I've tried checking for session and session->pid but they
822             # both still exist at this point so unless we wait for soft_close
823             # we're kinda stuck doing this.
824 0           $self->send("q\b" . $profile->{logoutcmd}. "\r");
825             }
826 0           return $self;
827             }
828              
829             1;