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