1package Net::Telnet::Netscreen;
2
3#-----------------------------------------------------------------
4#
5# Net::Telnet::Netscreen - Control Netscreen firewalls
6#
7# by Marcus Ramberg <m@songsolutions.no>
8# Lots of code ripped from Net::Telnet::Cisco;
9#
10#-----------------------------------------------------------------
11
12use strict;
13use Net::Telnet 3.02;
14use Carp;
15
16use vars qw($AUTOLOAD @ISA $VERSION);
17
18@ISA      = qw (Net::Telnet);
19$VERSION  = '1.2';
20
21
22#------------------------------
23# New Methods
24#------------------------------
25
26
27# ping a host, return true if can be reached
28sub ping {
29    my ($self,$host)=@_;
30    if ($self->cmd('ping '.$host)) {
31      my $l=$self->lastline;
32      if ($l =~ /Success Rate is (\d+) percent/) {
33        return $1;
34      } else { print "FOO", $l };
35    }
36    return 0;
37}
38
39# Displays the last prompt.
40sub last_prompt {
41    my $self = shift;
42    my $stream = $ {*$self}{net_telnet_Netscreen};
43    exists $stream->{last_prompt} ? $stream->{last_prompt} : undef;
44}
45
46
47# Displays the last command.
48sub last_cmd {
49    my $self = shift;
50    my $stream = $ {*$self}{net_telnet_Netscreen};
51    exists $stream->{last_cmd} ? $stream->{last_cmd} : undef;
52}
53
54# Displays the current vsys
55sub current_vsys {
56  my ($self) =@_;
57  if ($self->ha_mode ne '') {
58    $self->last_prompt =~ /\(([\w.-]+)\)\(\w+\)/ ? $1 : '';
59  } else {
60    $self->last_prompt =~ /\(([\w.-]+)\)/ ? $1 : '';
61  }
62}
63
64# enter a vsys
65sub enter_vsys {
66  my ($self, $vsys) = @_;
67  if ($self->current_vsys) {
68     return $self->error('Already in a vsys');
69  }
70  my %vsys = $self->get_vsys();
71  if (exists $vsys{$vsys}) {
72    if ($self->cmd('enter vsys '.$vsys)) {
73      return 1;
74    } else {
75      return $self->error('Error entering vsys');
76    }
77  } else { return $self->error("Vsys not found");}
78}
79
80sub ha_mode {
81  my ($self) = @_;
82  my $stream = $ {*$self}{net_telnet_Netscreen};
83  return $stream->{ha_mode};
84}
85
86# exit a vsys or main {
87sub exit {
88  my ($self,$save) =@_;
89  my $stream = $ {*$self}{net_telnet_Netscreen};
90  if ($self->current_vsys) {
91    $self->cmd('save') if $stream->{changed};
92    $self->cmd('exit');
93    $stream->{changed}=0;
94  } else {
95    $self->cmd('save') if $stream->{changed};
96    $self->close;
97  }
98}
99
100#get a hash of the vsys in existence.
101sub get_vsys {
102  my $self = shift;
103  my (%vsys,$result,$backupsys);
104  if ($self->current_vsys) {
105    $backupsys=$self->current_vsys;
106    $self->cmd('exit');
107  }
108  my @results = $self->getValue("vsys");
109  if ($backupsys) {$self->enter_vsys($backupsys);}
110  foreach $result (@results) {
111    if ($result=~/([\w.-]+)\s+(\d+)\s+/) {
112      $vsys{$1}=$2;
113    }
114  }
115  return %vsys;
116}
117
118#get a value from the ns box
119sub getValue {
120  my ($self, $setting) = @_;
121  return $self->error("No setting specified") unless $setting;
122  my @result= $self->cmd("get ".$setting);
123  if ($self->lastline =~ /\$\$Ambigious command!!/) {
124    return $self->error("Ambigious command");
125  }
126  return @result;
127}
128
129#set a value in ns box
130sub setValue {
131  my ($self,$setting, $value) = @_;
132  return $self->error("No setting specified") unless $setting;
133  return $self->error("No value specified") unless $value;
134
135  my @results=$self->cmd("set ".$setting." ".$value);
136  foreach my $result (@results) {
137    if ($result =~ /\w+/) { return $self->error($result); }
138  }
139  return 1;
140}
141
142#------------------------------------------
143# Overridden Methods
144#------------------------------------------
145
146#destructor!
147
148sub DESTROY {
149  my $self=shift;
150  if ($self->current_vsys()) {
151    $self->exit;
152  }
153  $self->exit;
154}
155
156#set the prompt to that of a Netscreen box..
157sub new {
158    my $class = shift;
159
160    # There's a new cmd_prompt in town.
161    my $self = $class->SUPER::new(
162       	prompt => '/[\w().-]*\(?([\w.-])?\)?\s*->\s*$/',
163	@_,			# user's additional arguments
164    ) or return;
165
166    *$self->{net_telnet_Netscreen} = {
167	last_prompt  => '',
168        last_cmd     => '',
169	ha_mode	     => '',
170	changed      => 0,
171    };
172
173    $self
174} # end sub new
175
176# The new prompt() stores the last matched prompt for later
177# fun 'n amusement. You can access this string via $self->last_prompt.
178#
179# It also parses out any router errors and stores them in the
180# correct place, where they can be acccessed/handled by the
181# Net::Telnet error methods.
182#
183# No POD docs for prompt(); these changes should be transparent to
184# the end-user.
185sub prompt {
186    my( $self, $prompt ) = @_;
187    my( $prev, $stream );
188
189    $stream  = ${*$self}{net_telnet_Netscreen};
190    $prev    = $self->SUPER::prompt;
191
192    ## Parse args.
193    if ( @_ == 2 ) {
194        defined $prompt or $prompt = '';
195
196        return $self->error('bad match operator: ',
197                            "opening delimiter missing: $prompt")
198            unless $prompt =~ m|^\s*/|;
199
200	$self->SUPER::prompt($prompt);
201
202    } elsif (@_ > 2) {
203        return $self->error('usage: $obj->prompt($match_op)');
204    }
205
206    return $prev;
207} # end sub prompt
208
209
210
211sub scrolling_cmd {
212    my ($self, @args) = @_;
213    my (
214	$arg,
215	$buf,
216	$cmd_remove_mode,
217	$firstpos,
218	$lastpos,
219	$lines,
220	$orig_errmode,
221	$orig_prompt,
222	$orig_timeout,
223	$output,
224	$output_ref,
225	$prompt,
226	$remove_echo,
227	$rs,
228	$rs_len,
229	$telopt_echo,
230	$timeout,
231	@cmd,
232	);
233    local $_;
234
235    ## Init vars.
236    $output = [];
237    $cmd_remove_mode = $self->SUPER::cmd_remove_mode;
238    $timeout = $self->SUPER::timeout;
239    $self->SUPER::timed_out('');
240    return if $self->SUPER::eof;
241
242    ## Parse args.
243    if (@_ == 2) {  # one positional arg given
244	push @cmd, $_[1];
245    }
246    elsif (@_ > 2) {  # named args given
247	## Parse the named args.
248	while (($_, $arg) = splice @args, 0, 2) {
249	    if (/^-?cmd_remove/i) {
250		$cmd_remove_mode = $arg;
251		$cmd_remove_mode = "auto"
252		    if $cmd_remove_mode =~ /^auto/i;
253	    }
254	    elsif (/^-?output$/i) {
255		$output_ref = $arg;
256		if (defined($output_ref) and ref($output_ref) eq "ARRAY") {
257		    $output = $output_ref;
258		}
259	    }
260	    elsif (/^-?prompt$/i) {
261		$prompt = $arg;
262	    }
263	    elsif (/^-?string$/i) {
264		push @cmd, $arg;
265	    }
266	    elsif (/^-?timeout$/i) {
267		$timeout = &_parse_timeout($arg);
268	    }
269	    else {
270		return $self->SUPER::error('usage: $obj->cmd(',
271				    '[Cmd_remove => $boolean,] ',
272				    '[Output => $ref,] ',
273				    '[Prompt => $match,] ',
274				    '[String => $string,] ',
275				    '[Timeout => $secs,])');
276	    }
277	}
278    }
279
280    ## Override some user settings.
281    $orig_errmode = $self->SUPER::errmode('return');
282    $orig_timeout = $self->SUPER::timeout(&_endtime($timeout));
283    $orig_prompt  = $self->SUPER::prompt($prompt) if defined $prompt;
284    $self->SUPER::errmsg('');
285
286    ## Send command and wait for the prompt.
287    $self->print(@cmd);
288     my ($input,$match) = $self->SUPER::waitfor(Match=>$self->prompt,
289           			         String=>"\n--- more ---");
290     while ($match) {
291      $lines=$lines.$input;
292      last if (eval "\$match =~ ".$self->prompt);
293      $self->SUPER::print("");
294     ($input,$match) = $self->SUPER::waitfor(Match=>$self->prompt,
295           		              String=>"\n--- more ---");
296    }
297    chomp($lines); # Cleanup output
298    ## Restore user settings.
299    $self->SUPER::errmode($orig_errmode);
300    $self->SUPER::timeout($orig_timeout);
301    $self->SUPER::prompt($orig_prompt) if defined $orig_prompt;
302
303    ## Check for failure.
304    return $self->SUPER::error("command timed-out") if $self->SUPER::timed_out;
305    return $self->error($self->SUPER::errmsg) if $self->SUPER::errmsg ne '';
306    return if $self->SUPER::eof;
307
308    ## Split lines into an array, keeping record separator at end of line.
309    $firstpos = 0;
310    $rs = $self->SUPER::input_record_separator;
311    $rs_len = length $rs;
312    while (($lastpos = index($lines, $rs, $firstpos)) > -1) {
313	push(@$output,
314	     substr($lines, $firstpos, $lastpos - $firstpos + $rs_len));
315	$firstpos = $lastpos + $rs_len;
316    }
317
318    if ($firstpos < length $lines) {
319	push @$output, substr($lines, $firstpos);
320    }
321
322    ## Determine if we should remove the first line of output based
323    ## on the assumption that it's an echoed back command.
324    ## FIXME: I had to uncomment this for now. Hope it doesn't
325    ## break stuff too badly for anyone ;>
326#    if ($cmd_remove_mode eq "auto") {
327#	## See if remote side told us they'd echo.
328#	$telopt_echo = $self->SUPER::option_state(&TELOPT_ECHO);
329#	$remove_echo = $telopt_echo->{remote_enabled};
330#    }
331#    else {  # user explicitly told us how many lines to remove.
332#	$remove_echo = $cmd_remove_mode;
333#    }
334
335    ## Get rid of possible echo back command.
336    while ($remove_echo--) {
337	shift @$output;
338    }
339
340    ## Ensure at least a null string when there's no command output - so
341    ## "true" is returned in a list context.
342    unless (@$output) {
343	@$output = ('');
344    }
345
346    ## Return command output via named arg, if requested.
347    if (defined $output_ref) {
348	if (ref($output_ref) eq "SCALAR") {
349	    $$output_ref = join '', @$output;
350	}
351	elsif (ref($output_ref) eq "HASH") {
352	    %$output_ref = @$output;
353	}
354    }
355
356    wantarray ? @$output : 1;
357} # end sub scrolling_cmd
358
359
360
361sub cmd {
362    my $self             = shift;
363    my $ok               = 1;
364    my $cmd;
365
366    # Extract the command from arguments
367    if ( @_ == 1 ) {
368	$cmd = $_[0];
369    } elsif ( @_ >= 2 ) {
370	my @args = @_;
371	while ( my ( $k, $v ) = splice @args, 0, 2 ) {
372	    $cmd = $v if $k =~ /^-?[Ss]tring$/;
373	}
374    }
375
376    $ {*$self}{net_telnet_Netscreen}{last_cmd} = $cmd;
377    $ {*$self}{net_telnet_Netscreen}{changed} = 1
378      if $cmd =~ m/^\s*(set|unset)/;
379
380    my @output = $self->scrolling_cmd(@_);
381
382    for ( my ($i, $lastline) = (0, '');
383	  $i <= $#output;
384	  $lastline = $output[$i++] ) {
385
386	# This may have to be a pattern match instead.
387	if ( $output[$i] =~ /^\s*\^-+/ ) {
388
389	    if ( $output[$i] =~ /unknown keyword (\w+)$/ ) { # Typo & bad arg errors
390		chomp $lastline;
391		$self->error( join "\n",
392			             "Last command and firewall error: ",
393			             ( $self->last_prompt . $cmd ),
394			             $lastline,
395			             "Unknown Keyword:" . $1,
396			    );
397		splice @output, $i - 1, 3;
398
399	    } else { # All other errors.
400		chomp $output[$i];
401		$self->error( join "\n",
402			      "Last command and firewall error: ",
403			      ( $self->last_prompt . $cmd ),
404			      $output[$i],
405			    );
406		splice @output, $i, 2;
407	    }
408
409	    $ok = undef;
410	    last;
411	}
412    }
413    return wantarray ? @output : $ok;
414}
415
416sub waitfor {
417    my $self = shift;
418    return unless @_;
419
420    # $isa_prompt will be built into a regex that matches all currently
421    # valid prompts.
422    #
423    # -Match args will be added to this regex. The current prompt will
424    # be appended when all -Matches have been exhausted.
425    my $isa_prompt;
426
427    # Things that /may/ be prompt regexps.
428    my $promptish = '^\s*(?:/|m\s*\W).*';
429
430
431    # Parse the -Match => '/prompt \$' type options
432    # waitfor can accept more than one -Match argument, so we can't just
433    # hashify the args.
434    if ( @_ >= 2 ) {
435	my @args = @_;
436	while ( my ( $k, $v ) = splice @args, 0, 2 ) {
437	    if ( $k =~ /^-?[Mm]atch$/ && $v =~ /($promptish)/ ) {
438		if ( my $addme = re_sans_delims($1) ) {
439		    $isa_prompt .= $isa_prompt ? "|$addme" : $addme;
440		} else {
441		    return $self->error("Bad regexp '$1' passed to waitfor().");
442		}
443	    }
444	}
445    } elsif ( @_ == 1 ) {
446	# A single argument is always a match.
447	if ( $_[0] =~ /($promptish)/ and my $addme = re_sans_delims($1) ) {
448	    $isa_prompt .= $isa_prompt ? "|$addme" : $addme;
449	} else {
450	    return $self->error("Bad regexp '$_[0]' passed to waitfor().");
451	}
452    }
453
454
455    # Add the current prompt if it's not already there.
456    if ( index($isa_prompt, $self->prompt) != -1
457	 and my $addme = re_sans_delims($self->prompt) ) {
458	$isa_prompt .= "|$addme";
459    }
460
461    # Call the real waitfor.
462    my ( $prematch, $match ) = $self->SUPER::waitfor(@_);
463
464    # If waitfor was, in fact, passed a prompt then find and store it.
465    if ( $isa_prompt && defined $match ) {
466	(${*$self}{net_telnet_Netscreen}{last_prompt})
467	    = $match =~ /($isa_prompt)/;
468    }
469    return wantarray ? ( $prematch, $match ) : 1;
470}
471
472
473sub login {
474    my($self) = @_;
475    my(
476       $cmd_prompt,
477       $endtime,
478       $error,
479       $lastline,
480       $match,
481       $orig_errmode,
482       $orig_timeout,
483       $passwd,
484       $prematch,
485       $reset,
486       $timeout,
487       $usage,
488       $username,
489       %args,
490       );
491    local $_;
492
493    ## Init vars.
494    $timeout = $self->timeout;
495    $self->timed_out('');
496    return if $self->eof;
497    $cmd_prompt = $self->prompt;
498    $usage = 'usage: $obj->login(Name => $name, Password => $password, '
499	   . '[Prompt => $match,] [Timeout => $secs,])';
500
501    if (@_ == 3) {  # just username and passwd given
502	($username, $passwd) = (@_[1,2]);
503    }
504    else {  # named args given
505	# Get the named args.
506	(undef, %args) = @_;
507
508	# Parse the named args.
509	foreach (keys %args) {
510	    if (/^-?name$/i) {
511		$username = $args{$_};
512		defined($username)
513		    or $username = "";
514	    }
515	    elsif (/^-?pass/i) {
516		$passwd = $args{$_};
517		defined($passwd)
518		    or $passwd = "";
519	    }
520	    elsif (/^-?prompt$/i) {
521		$cmd_prompt = $args{$_};
522		defined $cmd_prompt
523		    or $cmd_prompt = '';
524		return $self->error("bad match operator: ",
525				    "opening delimiter missing: $cmd_prompt")
526		    unless ($cmd_prompt =~ m(^\s*/)
527			    or $cmd_prompt =~ m(^\s*m\s*\W)
528			   );
529	    }
530	    elsif (/^-?timeout$/i) {
531		$timeout = _parse_timeout($args{$_});
532	    }
533	    else {
534		return $self->error($usage);
535	    }
536	}
537    }
538
539    return $self->error($usage)
540	unless defined($username) and defined($passwd);
541
542    ## Override these user set-able values.
543    $endtime = _endtime($timeout);
544    $orig_timeout = $self->timeout($endtime);
545    $orig_errmode = $self->errmode('return');
546
547    ## Create a subroutine to reset to original values.
548    $reset
549	= sub {
550	    $self->errmode($orig_errmode);
551	    $self->timeout($orig_timeout);
552	    1;
553	};
554
555    ## Create a subroutine to generate an error for user.
556    $error
557	= sub {
558	    my($errmsg) = @_;
559
560	    &$reset;
561	    if ($self->timed_out) {
562		return $self->error($errmsg);
563	    }
564	    elsif ($self->eof) {
565		($lastline = $self->lastline) =~ s/\n+//;
566		return $self->error($errmsg, ": ", $lastline);
567	    }
568	    else {
569		return $self->error($self->errmsg);
570	    }
571	};
572
573    ## Wait for login prompt.
574    ($prematch, $match) = $self->waitfor(-match => '/[Ll]ogin[:\s]*$/',
575					 -match => '/[Uu]sername[:\s]*$/',
576					 -match => '/[Pp]assword[:\s]*$/')
577	or do {
578	    return &$error("read eof waiting for login or password prompt")
579		if $self->eof;
580	    return &$error("timed-out waiting for login or password prompt");
581	};
582
583    unless ( $match =~ /[Pp]ass/ ) {
584	## Send login name.
585	$self->print($username)
586  	    or return &$error("login disconnected");
587
588	## Wait for password prompt.
589	$self->waitfor(-match => '/[Pp]assword[: ]*$/')
590	    or do {
591		return &$error("read eof waiting for password prompt")
592		    if $self->eof;
593		return &$error("timed-out waiting for password prompt");
594	    };
595    }
596
597    # Send password.
598    $self->print($passwd)
599        or return &$error("login disconnected");
600
601    # Wait for command prompt or another login prompt.
602    ($prematch, $match) = $self->waitfor(-match => '/[Ll]ogin[:\s]*$/',
603					 -match => '/[Uu]sername[:\s]*$/',
604					 -match => '/[Pp]assword[:\s]*$/',
605					 -match => $cmd_prompt)
606	or do {
607	    return &$error("read eof waiting for command prompt")
608		if $self->eof;
609	    return &$error("timed-out waiting for command prompt");
610	};
611
612    # Reset object to orig values.
613    &$reset;
614
615    # It's a bad login if we got another login prompt.
616    return $self->error("login failed: access denied or bad name or password")
617	if $match =~ /(?:[Ll]ogin|[Uu]sername|[Pp]assword)[: ]*$/;
618
619   #if we have paranthesis at this point, box is in ha mode
620    $ {*$self}{net_telnet_Netscreen}{ha_mode} = $1
621        if $match =~/\((\w+)\)/;
622
623    1;
624} # end sub login
625
626#------------------------------
627# Class methods
628#------------------------------
629
630# Return a Net::Telnet regular expression without the delimiters.
631sub re_sans_delims { ( $_[0] =~ m(^\s*m?\s*(\W)(.*)\1\s*$) )[1] }
632
633# Look for subroutines in Net::Telnet if we can't find them here.
634sub AUTOLOAD {
635    my ($self) = @_;
636    croak "$self is an [unexpected] object, aborting" if ref $self;
637    $AUTOLOAD =~ s/.*::/Net::Telnet::/;
638    goto &$AUTOLOAD;
639}
640
641=pod
642
643=head1 NAME
644
645Net::Telnet::Netscreen - interact with a Netscreen firewall
646
647=head1 SYNOPSIS
648
649use Net::Telnet::Netscreen;
650
651my $fw = new Net::Telnet::Netscreen(host=>'192.168.1.1');
652$fw->login('admin','password') or die $fw->error;
653$fw->enter_vsys('wineasy.no');
654print "We are now in: ".$fw->current_vsys."\n";
655my %vsys=$fw->get_vsys;
656   foreach $key (sort (keys %vsys)) {
657     print $key,'=', $vsys{$key},"\n";
658   }
659print @results;
660
661=head1 DESCRIPTION
662
663Net::Telnet::Netscreen is mostly a pure rippoff of Net::Telnet::Cisco, with
664adaptations to make it work on the Netscreen firewalls.
665It also has some additional commands, but for basic functionality,
666see Net::Telnet and Net::Telnet::Cisco documentation.
667
668=head1 FIRST
669
670Before you use Net::Telnet::Netscreen, you should probably have a good
671understanding of Net::Telnet, so perldoc Net::Telnet first, and then
672come back to Net::Telnet::Netscreen to see where the improvements are.
673
674Some things are easier to accomplish with Net::SNMP. SNMP has three
675advantages: it's faster, handles errors better, and doesn't use any
676vtys on the router. SNMP does have some limitations, so for anything
677you can't accomplish with SNMP, there's Net::Telnet::Netscreen.
678
679=head1 METHODS
680
681New methods not found in Net::Telnet follow:
682
683
684
685
686
687=head2 enter_vsys - enter a virtual system
688
689Enter a virtual system in the firewall.
690parameter is system you want to enter .
691You may enter another vsys even if you are
692in a vsys. Note that we will save your changes
693for you if you do.
694(only works for ns-500+)
695
696
697=head2 exit_vsys - exit from the level you are on
698
699exit from the vsys you are in, or from the system
700if you are on the top. takes one parameter.
701if you should save any changes or not.
702(only works for ns-500+)
703
704
705=head2 current_vsys - show current vsys
706
707return the vsys you currently are in.
708returns blank if you're not in a vsys.
709(only works for ns-500+)
710
711
712=head2 get_vsys - return vsys.
713
714returns a hash of all the virtual systems
715on your system, with system id's for values
716(only works for ns-500+)
717
718=head2 ha_mode - return high availability mode.
719
720return the HA mode, if your system is in a HA cluster,
721or false if it isn't.
722
723=head2 ping - ping a system.
724
725Returns percentage of success (0-100).
726
727  $sucess=$fw->ping('192.168.1.1');
728
729=head2 exit - Exit system
730
731use this command to exit system, or exit current
732vsys
733
734=head2 getValue - Set a value from the box.
735
736Will return a value from the firewall, or from
737the vsys you are in, if you aren't in root.
738
739=head2 setValue - Set a Value in the box.
740
741Set a value in the box, returns true if set successfully.
742(guess what it returns if you fuck up? ;)
743
744=head2 lastPrompt - Show the last prompt returned.
745
746Shows the last prompt returned by your netscreen
747device.
748
749=head2 lastCmd - Show the last command executed.
750
751Shows the last command executed on your netscreen
752device.
753
754=head1 OVERRIDEN METHODS
755
756=head2 last_cmd
757
758=head2 cmd
759
760=head2 last_prompt
761
762=head2 login
763
764=head2 new
765
766=head2 re_sans_delims
767
768=head2 scrolling_cmd
769
770=head2 waitfor
771
772See L<Net::Telnet> for documentation on these methods.
773
774=head1 AUTHOR
775
776The basic functionality was ripped from
777Joshua_Keroes@eli.net $Date: 2002/07/18 10:45:12 $
778Modifications and additions to suit Netscreen was
779done by
780m.ramberg@wineasy.no $Date: 2002/07/18 10:45:12 $
781
782=head1 SEE ALSO
783
784Net::Telnet, Net::SNMP
785
786=head1 COPYRIGHT
787
788Copyright (c) 2001 Marcus Ramberg, Song Networks Norway.
789All rights reserved. This program is free software; you
790can redistribute it and/or modify it under the same terms
791as Perl itself.
792
793=head1 LICENSE
794
795This library is free software, you can redistribute it and/or modify
796it under the same terms as Perl itself.
797
798
799=cut
800
8011;
802
803__END__
804