1# fetchconfig - Retrieving configuration for multiple devices 2# Copyright (C) 2010 Everton da Silva Marques 3# 4# fetchconfig is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 2, or (at your option) 7# any later version. 8# 9# fetchconfig is distributed in the hope that it will be useful, but 10# WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12# General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with fetchconfig; see the file COPYING. If not, write to the 16# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 17# MA 02110-1301 USA. 18# 19# $Id: MikroTik.pm,v 1.2 2010/12/02 19:50:37 evertonm Exp $ 20 21package fetchconfig::model::MikroTik; # fetchconfig/model/MikroTik.pm 22 23use strict; 24use warnings; 25use Net::Telnet; 26use fetchconfig::model::Abstract; 27 28@fetchconfig::model::MikroTik::ISA = qw(fetchconfig::model::Abstract); 29 30#################################### 31# Implement model::Abstract - Begin 32# 33 34sub label { 35 'mikrotik'; 36} 37 38# "sub new" fully inherited from fetchconfig::model::Abstract 39 40sub fetch { 41 my ($self, $file, $line_num, $line, $dev_id, $dev_host, $dev_opt_tab) = @_; 42 43 my $saved_prefix = $self->{log}->prefix; # save log prefix 44 $self->{log}->prefix("$saved_prefix: dev=$dev_id host=$dev_host"); 45 46 my @conf = $self->do_fetch($file, $line_num, $line, $dev_id, $dev_host, $dev_opt_tab); 47 48 # restore log prefix 49 $self->{log}->prefix($saved_prefix); 50 51 @conf; 52} 53 54# 55# Implement model::Abstract - End 56################################## 57 58sub chat_login { 59 my ($self, $t, $dev_id, $dev_host, $dev_opt_tab) = @_; 60 my $ok; 61 62 my $login_prompt = '/Login: $/'; 63 64 # chat_banner is used to allow temporary modification 65 # of timeout throught the 'banner_timeout' option 66 67 my ($prematch, $match) = $self->chat_banner($t, $dev_opt_tab, $login_prompt); 68 if (!defined($prematch)) { 69 $self->log_error("could not find login prompt: $login_prompt"); 70 return undef; 71 } 72 73 $self->log_debug("found login prompt: [$match]"); 74 75 my $dev_user = $self->dev_option($dev_opt_tab, "user"); 76 if (!defined($dev_user)) { 77 $self->log_error("login username needed but not provided"); 78 return undef; 79 } 80 81 # Append +ct console login options to username: 82 # c: disable console colors 83 # t: Do auto detection of terminal capabilities 84 # 85 # Source: 86 # http://wiki.mikrotik.com/wiki/Console_login_process#Console_login_options 87 # 88 my $user = "$dev_user+ct"; 89 $self->log_debug("sending user='$user'"); 90 91 $ok = $t->print($user); 92 if (!$ok) { 93 $self->log_error("could not send login username: '$user'"); 94 return undef; 95 } 96 97 ($prematch, $match) = $t->waitfor(Match => '/Password: $/'); 98 if (!defined($prematch)) { 99 $self->log_error("could not find password prompt"); 100 return undef; 101 } 102 103 $self->log_debug("found password prompt: [$match]"); 104 105 my $dev_pass = $self->dev_option($dev_opt_tab, "pass"); 106 if (!defined($dev_pass)) { 107 $self->log_error("login password needed but not provided"); 108 return undef; 109 } 110 111 #$self->log_debug("sending password: '$dev_pass'"); 112 113 $ok = $t->print($dev_pass); 114 if (!$ok) { 115 $self->log_error("could not send login password"); 116 return undef; 117 } 118 119 ($prematch, $match) = $t->waitfor(Match => '/(\S+) > $/'); 120 if (!defined($prematch)) { 121 $self->log_error("could not find command prompt"); 122 return undef; 123 } 124 125 my $prompt = $match; 126 127 $self->log_debug("logged in prompt='$prompt'"); 128 129 $self->{prompt} = $prompt; # save prompt 130 131 $prompt; 132} 133 134sub expect_enable_prompt { 135 my ($self, $t, $prompt) = @_; 136 137 if (!defined($prompt)) { 138 $self->log_error("internal failure: undefined command prompt"); 139 return undef; 140 } 141 142 my $enable_prompt_regexp = '/' . $prompt . ' > $/'; 143 144 my ($prematch, $match) = $t->waitfor(Match => $enable_prompt_regexp); 145 if (!defined($prematch)) { 146 $self->log_error("could not match enable command prompt: $enable_prompt_regexp"); 147 } 148 149 ($prematch, $match); 150} 151 152sub expect_enable_prompt_paging { 153 my ($self, $t, $prompt, $paging_prompt) = @_; 154 155 if (!defined($prompt)) { 156 $self->log_error("internal failure: undefined command prompt"); 157 return undef; 158 } 159 160 my $escaped_prompt = &escape_brackets($prompt); 161 $self->log_debug("regexp='$prompt' escaped_brackets='$escaped_prompt'"); 162 163 my $prompt_regexp = '/(' . $escaped_prompt . ')|(' . $paging_prompt . ')/'; 164 my $paging_prompt_regexp = '/' . $paging_prompt . '/'; 165 166 my ($prematch, $match, $full_prematch); 167 168 for (;;) { 169 ($prematch, $match) = $t->waitfor(Match => $prompt_regexp); 170 if (!defined($prematch)) { 171 $self->log_error("could not match enable/paging prompt: $prompt_regexp"); 172 return; # signals error with undef 173 } 174 175 #$self->log_debug("paging match: [$match]"); 176 177 $full_prematch .= $prematch; 178 179 if ($match ne $paging_prompt) { 180 #$self->log_debug("done paging match: [$match][$paging_prompt_regexp]"); 181 last; 182 } 183 184 # Do paging 185 my $ok = $t->put(' '); # SPACE 186 if (!$ok) { 187 $self->log_error("could not send paging SPACE command"); 188 return; # signals error with undef 189 } 190 } 191 192 ($full_prematch, $match); 193} 194 195sub chat_fetch { 196 my ($self, $t, $dev_id, $dev_host, $prompt, $fetch_timeout, $show_cmd, $conf_ref) = @_; 197 my ($ok, $prematch, $match); 198 199 #$t->input_log(\*STDERR); 200 201 if ($self->chat_show_conf($t, 'export', $show_cmd)) { 202 return 1; 203 } 204 205 # Prevent "show run" command from appearing in config dump 206 #$t->getline(); 207 208 my $save_timeout; 209 if (defined($fetch_timeout)) { 210 $save_timeout = $t->timeout; 211 $t->timeout($fetch_timeout); 212 } 213 214 ($prematch, $match) = $self->expect_enable_prompt_paging($t, $prompt, '--More-- '); 215 if (!defined($prematch)) { 216 $self->log_error("could not find end of configuration"); 217 return 1; 218 } 219 220 if (defined($fetch_timeout)) { 221 $t->timeout($save_timeout); 222 } 223 224 $self->log_debug("found end of configuration: '$match'"); 225 226 @$conf_ref = split /\n/, $prematch; 227 228 $self->log_debug("fetched: " . scalar @$conf_ref . " lines"); 229 230 undef; 231} 232 233sub do_fetch { 234 my ($self, $file, $line_num, $line, $dev_id, $dev_host, $dev_opt_tab) = @_; 235 236 $self->log_debug("trying"); 237 238 my $dev_repository = $self->dev_option($dev_opt_tab, "repository"); 239 if (!defined($dev_repository)) { 240 $self->log_error("undefined repository"); 241 return; 242 } 243 244 if (! -d $dev_repository) { 245 $self->log_error("not a directory repository=$dev_repository at file=$file line=$line_num: $line"); 246 return; 247 } 248 249 if (! -w $dev_repository) { 250 $self->log_error("unable to write to repository=$dev_repository at file=$file line=$line_num: $line"); 251 return; 252 } 253 254 my $dev_timeout = $self->dev_option($dev_opt_tab, "timeout"); 255 256 my $t = new Net::Telnet(Errmode => 'return', Timeout => $dev_timeout); 257 258 my $tm = $t->telnetmode(1); 259 $self->log_debug("telnet command interpretation was: " . ($tm ? "on" : "off")); 260 261 my $ok = $t->open($dev_host); 262 if (!$ok) { 263 $self->log_error("could not connect: $!"); 264 return; 265 } 266 267 $self->log_debug("connected"); 268 269 $tm = $t->telnetmode(); 270 $self->log_debug("telnet command interpretation is: " . ($tm ? "on" : "off")); 271 272 my $prompt = $self->chat_login($t, $dev_id, $dev_host, $dev_opt_tab); 273 274 return unless defined($prompt); 275 276 my @config; 277 278 my $fetch_timeout = $self->dev_option($dev_opt_tab, "fetch_timeout"); 279 280 my $show_cmd = $self->dev_option($dev_opt_tab, "show_cmd"); 281 282 return if $self->chat_fetch($t, $dev_id, $dev_host, $prompt, $fetch_timeout, $show_cmd, \@config); 283 284 $ok = $t->close; 285 if (!$ok) { 286 $self->log_error("disconnecting: $!"); 287 } 288 289 $self->log_debug("disconnected"); 290 291 $self->dump_config($dev_id, $dev_opt_tab, \@config); 292} 293 2941; 295