1# fetchconfig - Retrieving configuration for multiple devices
2# Copyright (C) 2006 Doug Schaapveld
3# Copyright (C) 2006 Everton da Silva Marques
4#
5# fetchconfig is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2, or (at your option)
8# any later version.
9#
10# fetchconfig is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with fetchconfig; see the file COPYING. If not, write to the
17# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
18# MA 02110-1301 USA.
19#
20# $Id: ProCurve.pm,v 1.4 2007/01/12 20:11:08 djschaap Exp $
21
22package fetchconfig::model::ProCurve; # fetchconfig/model/ProCurve.pm
23
24use strict;
25use warnings;
26use fetchconfig::model::Abstract;
27
28@fetchconfig::model::ProCurve::ISA = qw(fetchconfig::model::Abstract);
29
30####################################
31# Implement model::Abstract - Begin
32#
33
34sub label {
35	'procurve';
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
58# There's probably a better way to do this, but this works for now!
59# http://en.wikipedia.org/wiki/ANSI_escape_code
60sub stripansi ($) {
61	my $str=shift;
62	$str=~s/\x1b\[\d*;?\d*[A-Za-z]//g;
63	$str=~s/\x1b\[\?\d+[h]//g;  # What is this code? HP ProCurves use it
64	$str=~s/\x1bE//g;  # HP ProCurves use this, too
65	$str=~s/\x0d//g;
66	$str;
67}
68
69sub chat_login_telnet {
70	my ($self, $t, $dev_id, $dev_host, $dev_opt_tab) = @_;
71	my $ok;
72
73#	my ($prematch, $match) = $t->waitfor(Match => '/(Username:|Password:) $/');
74#	my ($prematch, $match) = $t->waitfor(Match => '/Password: $/');
75	my ($prematch, $match) = $t->waitfor(Match => '/Password: |Press any key to continue/');
76	if (!defined($prematch)) {
77		$self->log_error("could not find login prompt");
78		return undef;
79	}
80
81	$self->log_debug("found login prompt: [$match]");
82
83	if ($match =~ /Press any key to continue/) {
84		$ok = $t->print("\n");
85		if (!$ok) {
86			$self->log_error("could not send any key");
87			return undef;
88		}
89
90		($prematch, $match) = $t->waitfor(Match => '/([A-Za-z0-9-]+ ?)[>#] /');
91		if (!defined($prematch)) {
92			$self->log_error("could not find command prompt (nopw)");
93			return undef;
94		}
95
96		# Note that below line may not appear properly due to the
97		# escape sequences from the switch
98		$self->log_debug("found command prompt: [$match]");
99	}
100
101	if ($match =~ /login: $/) {
102		my $dev_user = $self->dev_option($dev_opt_tab, "user");
103		if (!defined($dev_user)) {
104			$self->log_error("login username needed but not provided");
105			return undef;
106		}
107
108		$ok = $t->print($dev_user);
109		if (!$ok) {
110			$self->log_error("could not send login username");
111			return undef;
112		}
113
114		($prematch, $match) = $t->waitfor(Match => '/Password: $/');
115		if (!defined($prematch)) {
116			$self->log_error("could not find password prompt");
117			return undef;
118		}
119
120		$self->log_debug("found password prompt: [$match]");
121	}
122
123	if ($match =~ /^Password: /) {
124		my $dev_pass = $self->dev_option($dev_opt_tab, "pass");
125		if (!defined($dev_pass)) {
126			$self->log_error("login password needed but not provided");
127			return undef;
128		}
129
130		$ok = $t->print($dev_pass);
131		if (!$ok) {
132			$self->log_error("could not send login password");
133			return undef;
134		}
135
136		($prematch, $match) = $t->waitfor(Match => '/([A-Za-z0-9-]+ ?)[>#] /');
137		if (!defined($prematch)) {
138			$self->log_error("could not find command prompt (pw)");
139			return undef;
140		}
141
142		# Note that below line may not appear properly due to the
143		# escape sequences from the switch
144		$self->log_debug("found command prompt: [$match]");
145	}
146
147	if ($match !~ /^(\S+ ?)[>#] $/) {
148		$self->log_error("could not match command prompt in [$match]");
149		return undef;
150	}
151
152	if ($match =~ /^(\S+ ?)> $/) {
153		$ok = $t->print('enable');
154		if (!$ok) {
155			$self->log_error("could not send enable command");
156			return undef;
157		}
158
159		($prematch, $match) = $t->waitfor(Match => '/Password: /');
160		if (!defined($prematch)) {
161			$self->log_error("could not find enable password prompt");
162			return undef;
163		}
164
165		if ($match =~ /^Password/) {
166			my $dev_enable = $self->dev_option($dev_opt_tab, "enable");
167			if (!defined($dev_enable)) {
168				$self->log_error("enable password needed but not provided");
169				return undef;
170			}
171
172			$ok = $t->print($dev_enable);
173			if (!$ok) {
174				$self->log_error("could not send enable password");
175				return undef;
176			}
177
178			($prematch, $match) = $t->waitfor(Match => '/([A-Za-z0-9-]+ ?)# /');
179			if (!defined($prematch)) {
180				$self->log_error("could not find enable command prompt");
181				return undef;
182			}
183		}
184
185		$self->log_debug("found enable prompt: [$match]");
186	}
187
188	if ($match !~ /([A-Za-z0-9-]+ ?)#/) {
189		$self->log_error("could not match enable command prompt");
190		return undef;
191	}
192
193	my $prompt = $1;
194
195	$self->{prompt} = $prompt; # save prompt
196
197	$self->log_debug("logged in prompt: [$prompt]");
198
199	$prompt;
200}
201
202sub expect_enable_prompt {
203	my ($self, $t, $prompt) = @_;
204
205	if (!defined($prompt)) {
206		$self->log_error("internal failure: undefined command prompt");
207		return undef;
208	}
209
210	my $enable_prompt_regexp = '/' . $prompt . '# /';
211
212    my ($prematch, $match) = $t->waitfor(Match => $enable_prompt_regexp);
213	if (!defined($prematch)) {
214		$self->log_error("could not match enable command prompt: $enable_prompt_regexp");
215	}
216
217	($prematch, $match);
218}
219
220sub chat_fetch {
221	my ($self, $t, $dev_id, $dev_host, $prompt, $fetch_timeout, $conf_ref) = @_;
222	my $ok;
223
224	$ok = $t->print('no page');
225	if (!$ok) {
226		$self->log_error("could not send pager disabling command");
227		return 1;
228	}
229
230	my ($prematch, $match) = $self->expect_enable_prompt($t, $prompt);
231	return unless defined($prematch);
232
233	my $show_cmd="show run";
234
235	$ok = $t->print($show_cmd);
236	if (!$ok) {
237		$self->log_error("could not send show run command: $show_cmd");
238		return 1;
239	}
240
241	# Prevent "show run" command and "Running configuration"
242	# from appearing in config dump
243	$t->getline();
244	$t->getline();
245	$t->getline();
246
247	my $save_timeout;
248	if (defined($fetch_timeout)) {
249		$save_timeout = $t->timeout;
250		$t->timeout($fetch_timeout);
251	}
252
253	($prematch, $match) = $self->expect_enable_prompt($t, $prompt);
254	if (!defined($prematch)) {
255		$self->log_error("could not find end of configuration");
256		return 1;
257	}
258
259	if (defined($fetch_timeout)) {
260		$t->timeout($save_timeout);
261	}
262
263	$self->log_debug("found end of configuration: [$match]");
264
265	foreach my $line (split /\n/, $prematch) {
266		my $ascii_line=stripansi($line);
267		chomp $ascii_line;
268		push(@$conf_ref,$ascii_line ? $ascii_line : "");
269	}
270
271	# Remove ANSI fragment from final line (if present)
272	$conf_ref->[$#$conf_ref]=~s/\x1b\[24\;//;
273
274	# Debugging code for line-by-line analysis
275#	for(my $i=0;$i<(scalar @$conf_ref);$i++) {
276#		if((my $line_len=length $conf_ref->[$i]) >1) {
277#			$self->log_debug("[L " . $i . "-" . (length $conf_ref->[$i]) . "] " . $conf_ref->[$i]);
278#		}
279#	}
280
281	$self->log_debug("fetched: " . scalar @$conf_ref . " lines");
282
283	return undef;
284}
285
286sub do_fetch_telnet {
287	my ($self, $file, $line_num, $line, $dev_id, $dev_host, $dev_opt_tab) = @_;
288
289	my $dev_timeout = $self->dev_option($dev_opt_tab, "timeout");
290
291	my $t = new Net::Telnet(Errmode => 'return', Timeout => $dev_timeout);
292
293	my $ok = $t->open($dev_host);
294	if (!$ok) {
295		$self->log_error("could not connect: $!");
296		return;
297	}
298
299	$self->log_debug("connected");
300
301	my $prompt = $self->chat_login_telnet($t, $dev_id, $dev_host, $dev_opt_tab);
302
303	return unless defined($prompt);
304
305	my $conf_ref=[];
306
307	my $fetch_timeout = $self->dev_option($dev_opt_tab, "fetch_timeout");
308
309	return if $self->chat_fetch($t, $dev_id, $dev_host, $prompt, $fetch_timeout, $conf_ref);
310
311	$ok = $t->close;
312	if (!$ok) {
313		$self->log_error("disconnecting: $!");
314	}
315
316	return $conf_ref;
317}
318
319sub do_fetch {
320	my ($self, $file, $line_num, $line, $dev_id, $dev_host, $dev_opt_tab) = @_;
321
322	$self->log_debug("trying");
323
324	my $dev_repository = $self->dev_option($dev_opt_tab, "repository");
325	if (!defined($dev_repository)) {
326		$self->log_error("undefined repository");
327		return;
328	}
329
330	if (! -d $dev_repository) {
331		$self->log_error("not a directory repository=$dev_repository at file=$file line=$line_num: $line");
332		return;
333	}
334
335	if (! -w $dev_repository) {
336		$self->log_error("unable to write to repository=$dev_repository at file=$file line=$line_num: $line");
337		return;
338	}
339
340	my $conf_ref;
341	$conf_ref=$self->do_fetch_telnet($file, $line_num, $line, $dev_id, $dev_host, $dev_opt_tab);
342
343	$self->log_debug("disconnected");
344
345	$self->dump_config($dev_id, $dev_opt_tab, $conf_ref);
346}
347
3481;
349