1 package FS::part_export::router;
3 =head1 FS::part_export::router
5 This export connects to a router and transmits commands via telnet or SSH.
6 It requires the following custom router fields:
8 =head1 Required custom fields
12 =item admin_address - IP address (or hostname) to connect.
14 =item admin_user - Username for the router.
16 =item admin_password - Password for the router.
18 =item admin_protocol - Protocol to use for the router. 'telnet' or 'ssh'. The ssh protocol only support password-less (ie. RSA key) authentication. As such, the admin_password field isn't used if ssh is specified.
20 =item admin_timeout - Time in seconds to wait for a connection.
22 =item admin_prompt - A regular expression matching the router's prompt. See Net::Telnet for details. Only applies to the 'telnet' protocol.
24 =item admin_cmd_insert - Insert export command. See below.
26 =item admin_cmd_delete - Delete export command. See below.
28 =item admin_cmd_replace - Replace export command. See below.
30 =item admin_cmd_suspend - Suspend export command. See below.
32 =item admin_cmd_unsuspend - Unsuspend export command. See below.
34 The admin_cmd_* virtual fields, if set, will be double quoted, eval'd, and executed on the router specified.
36 If any of the required router virtual fields are not defined, then the export silently declines.
40 The export itself takes no options.
45 use vars qw(@ISA %info $me $DEBUG);
47 use String::ShellQuote;
48 use FS::Record qw(qsearchs);
51 @ISA = qw(FS::part_export);
53 tie my %options, 'Tie::IxHash',
57 options => [qw(telnet ssh)],
62 'svc' => 'svc_broadband',
63 'desc' => 'Send a command to a router.',
64 'options' => \%options,
65 'notes' => 'Installation of Net::Telnet from CPAN is required for telnet connections. This export will execute if the following virtual fields are set on the router: admin_user, admin_password, admin_address, admin_timeout, admin_prompt. Option virtual fields are: admin_cmd_insert, admin_cmd_replace, admin_cmd_delete, admin_cmd_suspend, admin_cmd_unsuspend.',
68 $me = '[' . __PACKAGE__ . ']';
72 sub rebless { shift; }
74 sub _field_prefix { 'admin'; }
76 sub _req_router_fields {
78 $_[0]->_field_prefix . '_' . $_
79 } (qw(address prompt user));
84 warn "Running insert for " . ref($self);
85 $self->_export_command('insert', @_);
90 $self->_export_command('delete', @_);
95 $self->_export_command('suspend', @_);
98 sub _export_unsuspend {
100 $self->_export_command('unsuspend', @_);
103 sub _export_replace {
105 $self->_export_command('replace', @_);
108 sub _export_command {
109 my ($self, $action, $svc_broadband) = (shift, shift, shift);
112 if ($action eq 'replace') {
116 warn "[debug]$me Processing action '$action'" if $DEBUG;
119 my $router = $self->_get_router($svc_broadband, @_);
121 return "Unable to lookup router for $action export";
124 unless ($self->_check_router_fields($router)) {
125 # Virtual fields aren't defined. Exit silently.
126 warn "[debug]$me Required router virtual fields not defined. Returning...";
131 ($error, $args) = $self->_prepare_args(
140 # Error occured while preparing args.
142 } elsif (not defined $args) {
144 warn "[debug]$me Declining '$action' export";
146 } # else ... queue the export.
148 warn "[debug]$me Queueing with args: " . join(', ', @$args) if $DEBUG;
152 $svc_broadband->svcnum,
153 $self->_get_cmd_sub($svc_broadband, $router),
162 my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift);
163 my $old = shift if ($action eq 'replace');
165 my $field_prefix = $self->_field_prefix;
166 my $command = $router->getfield("${field_prefix}_cmd_${action}");
168 warn "[debug]$me router custom field '${field_prefix}_cmd_$action' "
169 . "is not defined." if $DEBUG;
177 if ($action eq 'replace') {
178 ${"old_$_"} = $old->getfield($_) foreach $old->fields;
179 ${"new_$_"} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
180 $command = eval(qq("$command"));
182 ${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
183 $command = eval(qq("$command"));
189 'user' => $router->getfield($field_prefix . '_user'),
190 'password' => $router->getfield($field_prefix . '_password'),
191 'host' => $router->getfield($field_prefix . '_address'),
192 'Timeout' => $router->getfield($field_prefix . '_timeout'),
193 'Prompt' => $router->getfield($field_prefix . '_prompt'),
194 'command' => $command,
203 my ($self, $svc_broadband, $router) = (shift, shift, shift);
206 $router->getfield($self->_field_prefix . '_protocol') =~ /^(telnet|ssh)$/
209 return(ref($self)."::".$protocol."_cmd");
213 sub _check_router_fields {
215 my ($self, $router, $action) = (shift, shift, shift);
216 my @check_fields = $self->_req_router_fields;
218 foreach (@check_fields) {
219 if ($router->getfield($_) eq '') {
220 warn "[debug]$me Required field '$_' is unset";
231 my( $self, $svcnum, $cmd_sub ) = (shift, shift, shift);
232 my $queue = new FS::queue {
235 $queue->job($cmd_sub);
240 my ($self, $svc_broadband, %args) = (shift, shift, shift, @_);
243 if ($args{'routernum'}) {
244 $router = qsearchs('router', { routernum => $args{'routernum'}});
246 $router = $svc_broadband->addr_block->router;
257 &Net::SSH::ssh_cmd( { @_ } );
261 eval 'use Net::Telnet;';
268 my $t = new Net::Telnet (Timeout => $arg{Timeout},
269 Prompt => $arg{Prompt});
270 $t->open($arg{host});
271 $t->login($arg{user}, $arg{password});
272 my @error = $t->cmd($arg{command});
273 die @error if (grep /^ERROR/, @error);