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