Here is a simple perl module I wrote recently, using ssh and Expect, to deal with a similar problem. You can easily swap out ‘ssh’ with ‘telnet’ if you want. I wrote this originally to make it easier for me to administer multiple RouterOS boxes (say, have a script that logs in to every box and update the admin password). It is very primitive and has not been thoroughly tested yet, so far I know it works on recent Linux/BSD platforms with bash 2.0 and perl 5.
I also have them in .rpm and .deb packages, if anyone is interested, I can post them here.
You can just copy-n-paste the following code and save as RouterOS.pm, and call it in your perl script.
Sorry about the messy-looking comments, they are robodoc-style comments required for my work (it actually turns into pretty good looking HTML files by robodoc).
I welcome any feedback, I know this module is far from being perfect (especially in the regular expression department)…
#****c* Mikrotik/RouterOS
# NAME
# RouterOS
#
# DESCRIPTOIN
# This is an object oriented perl module that provides us easier
# access to the greenbox's RouterOS interface via SSH/Expect.
#
# NOTES
# To avoid paging issues, all print commands should have the option
# "without-paging" enabled (if available) so all the information is
# printed to screen in one shot, requiring no interaction from the
# user to press the space bar to see the next page.
#
# EXAMPLE
# Below is an example on how to use this module to establish an SSH
# connection to a RouterOS box, run a command on it, and get the
# results back as a long string:
#
# <code>
# use RouterOS;
# my $connection = RouterOS->new(
# HOST=>'192.168.0.1',
# USER=>'admin',
# PASSWD=>'secretpasswd'
# );
# my $command = '/ip hostspot active print without-paging'
# my $result = $connection->exec($command);
# </code>
#
# TODO
# - Syslog
#
# AUTHOR
# Josh Kuo
#
#***
package RouterOS;
use Expect;
$VERSION = '2.8.28'; # version of RouterOS supported
$TIMEOUT = '5'; # SSH timeout
# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
# Constructor
# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#****m* RouterOS/new
# NAME
# new
#
# DESCRIPTOIN
# This is the constructor for the RouterOS perl class. It reads in
# the arguments given to the constructor, and initializes the
# attributes of this object, and then attempts to connect to the
# remote RouterOS host via SSH. If the connection failed, the
# object creation is aborted.
#
# AUTHOR
# Josh Kuo
#
# SOURCE
sub new {
my $class = shift;
my $self = {
HOST =>undef,
USER =>undef,
PASSWD=>undef,
SSH =>undef
};
bless $self, $class;
$self->initialize(@_);
return $self if ($self->{SSH});
# if the SSH connection wasn't established, exit with error
die "Cannot connect to " . $self->{HOST} . "\n";
}
#***
# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
# Destructor
# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#****im* RouterOS/DESTROY
# NAME
# DESTROY
#
# DESCRIPTION
# This method is called whenever perl cleans up the un-used object
# automatically. It should NEVER be called manually.
#
# INPUTS
# None.
#
# RETURN VALUE
# None.
#
# OUTPUT
# None.
#
# SIDE EFFECT
# If there is an active SSH session with the RouterOS host, as part
# of the object destruction, this method will attempt to close that
# SSH connection by logging out of the RouterOS host.
#
# AUTHOR
# Josh Kuo
#
# SOURCE
sub DESTROY {
my $self = shift; my $ssh = $self->{SSH};
if ($ssh) {
print $ssh "/quit\r";
}
}
#***
# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
# Methods
# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#****m* RouterOS/exec
# NAME
# exec
#
# DESCRIPTION
# Sends a command to be executed on the remote RouterOS host.
#
# NOTES
# After we submit a command, we get back an array that looks like
# this in Expect:
# [0] = 1
# [1] = undef
# [2] = $regex
# [3] = $result
# Where $regex is the regular expression we have specified that we are
# loooking for in the result, and $result is the actual string
# returned by running the command on the remote host. So what we do
# here is return [3], the raw string returned by running the command.
#
# Since Expect by default has 'echo' turned on, the command we
# submitted will be echoed back to us, and we will also get the shell
# prompt text as our last line. Below is an example of running the
# command '/ip firewall rule input print without-paging' on a RouterOS
# machine:
#
# <RESULT>
# /ip firewall rule input print without-paging
# Flags: X - disabled, I - invalid, D - dynamic
# 0 ;;; account traffic from hotspot clients to hotspot servlet
# in-interface=Priority_Point dst-address=:80 protocol=tcp action=jump
# jump-target=hotspot
#
# 1 ;;; accept external requests for ssh
# in-interface=External dst-address=:22 protocol=tcp action=accept
#
# 2 ;;; accept request for hotspot ssl servlet
# dst-address=:443 protocol=tcp action=accept
#
# 3 ;;; accept request for hotspot ssl servlet
# dst-address=:80 protocol=tcp action=accept
#
# 4 ;;; accept requests for local DHCP server
# dst-address=:67 protocol=udp action=accept
#
# 5 ;;; limit access for unauthorized hotspot clients
# in-interface=Priority_Point action=jump jump-target=hotspot-temp
#
# [admin@PNETWORKS_000]
# </RESULT>
#
# INPUTS
# (REQ) The command string to be executed on the remote host.
#
# RETURN VALUE
# The output from running the command (if any) as an array of strings.
#
# OUTPUT
# None.
#
# AUTHOR
# Josh Kuo
#
# SOURCE
sub exec {
my $self = shift;
my $cmd = shift;
my $ssh = $self->{SSH};
$ssh->send("$cmd\r");
my @results = $ssh->expect($TIMEOUT, '-re', "> ");
unless (@results) {
die "Error submitting command.\n";
}
return @results[3];
}
#***
#****m* RouterOS/host
# NAME
# host
#
# DESCRIPTOIN
# This is the accessor method for the attribute HOST. It acts as both
# the SET and GET methods.
#
# EXAMPLE
# Using it as a GET method:
# $host = $obj->host();
#
# Using it as a SET method:
# $obj->host('128.64.162.35');
#
# INPUTS
# (OPT) RouterOS host to connect to
#
# RETURN VALUE
# The value of the HOST attribute.
#
# OUTPUT
# None.
#
# AUTHOR
# Josh Kuo
#
# SOURCE
sub host {
my $self = shift;
if (@_) {
$self->{HOST} = shift;
}
return $self->{HOST};
}
#***
#****im* RouterOS/initialize
# NAME
# initialize
#
# DESCRIPTION
# This method takes the arguments passed to the constructor and sets
# the object's attributes accordingly. It does this by chopping the
# arguments array into pairs, and stuff them into a hash table; then
# it checks each key to see if it has a value, if it does, then it
# assigns that value to the object itself.
#
# INPUTS
# (REQ) Array passed to the constructor
#
# RETURN VALUE
# None.
#
# OUTPUT
# None.
#
# SIDE EFFECT
# Sets the object's attributes 'HOST', 'USER', and 'PASSWD' base on
# the values given.
#
# AUTHOR
# Josh Kuo
#
# SOURCE
sub initialize {
my $self = shift;
# loop through the rest of the arguments and chop them into pairs,
# and stuff them into the hash table for easy lookup later
my $size = @_; my %hash;
for ($counter=0;$counter<$size;$counter+=2) {
%hash->{@_[$counter]} = @_[$counter+1];
}
my $host = %hash->{HOST};
if ($host) { $self->{HOST} = $host }
my $user = %hash->{USER};
if ($user) { $self->{USER} = $user }
my $passwd = %hash->{PASSWD};
if ($passwd) { $self->{PASSWD} = $passwd }
# Create a new Expect object to connect to this host
my $ssh = new Expect();
# This tells Expect NOT to send the results to STDOUT
$ssh->log_stdout(0);
# This will log the STDOUT output to a file instead
#$ssh->log_file("/tmp/expect.tmp", "w");
# We need to fool RouterOS into thinking that our terminal type
# is xterm before making the connection
$ssh->spawn("export TERM=xterm && ssh -l $user $host");
unless ($ssh->expect($TIMEOUT, '-re', "password:")) {
die "Did not receive password prompt.";
}
print $ssh "$passwd\r";
unless ($ssh->expect($TIMEOUT, '-re', "> ")) {
die "Password submission failed, did not receive login prompt.";
}
if ( $ssh ) { $self->{SSH} = $ssh }
else { die "Could not establish SSH connection to $host." };
}
#***
#****m* RouterOS/user
# NAME
# user
#
# DESCRIPTOIN
# This is the accessor method for the attribute USER. It acts as both
# the SET and GET methods.
#
# EXAMPLE
# Using it as a GET method:
# $user = $obj->user();
#
# Using it as a SET method:
# $obj->user('admin');
#
# INPUTS
# (OPT) RouterOS user name
#
# RETURN VALUE
# The value of the USER attribute.
#
# OUTPUT
# None.
#
# AUTHOR
# Josh Kuo
#
# SOURCE
sub user {
my $self = shift;
if (@_) {
$self->{USER} = shift;
}
return $self->{USER};
}
#***
#****m* RouterOS/passwd
# NAME
# passwd
#
# DESCRIPTION
# This is the accessor method for the attribute PASSWD. It acts as
# both the SET and GET methods.
#
# EXAMPLE
# Using it as a GET method:
# $passwd = $obj->passwd();
#
# Using it as a SET method:
# $obj->passwd('secret passwd');
#
# INPUTS
# (OPT) RouterOS password
#
# RETURN VALUE
# The value of the PASSWD attribute.
#
# OUTPUT
# None.
#
# TODO
# We may want to restrict the usage of this method in the future to a
# SET only method, so no one can gain access to the password this way.
#
# AUTHOR
# Josh Kuo
#
# SOURCE
sub passwd {
my $self = shift;
if (@_) {
$self->{PASSWD} = shift;
}
return $self->{PASSWD};
}
#***
1;