diff options
Diffstat (limited to 'FS')
-rw-r--r-- | FS/FS/Schema.pm | 1 | ||||
-rw-r--r-- | FS/FS/part_export/broadband_sqlradius.pm | 8 | ||||
-rw-r--r-- | FS/FS/part_export/sqlradius.pm | 11 | ||||
-rw-r--r-- | FS/FS/part_svc.pm | 11 | ||||
-rwxr-xr-x | FS/FS/router.pm | 114 | ||||
-rw-r--r-- | FS/FS/svc_Common.pm | 19 | ||||
-rw-r--r-- | FS/FS/svc_IP_Mixin.pm | 90 |
7 files changed, 229 insertions, 25 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 340b678e9..87a14aac4 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2282,6 +2282,7 @@ sub tables_hashref { 'selfservice_access', 'varchar', 'NULL', $char_d, '', '', 'classnum', 'int', 'NULL', '', '', '', 'restrict_edit_password','char', 'NULL', 1, '', '', + 'has_router', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'svcpart', 'unique' => [], diff --git a/FS/FS/part_export/broadband_sqlradius.pm b/FS/FS/part_export/broadband_sqlradius.pm index b5d1a80cb..522c6377c 100644 --- a/FS/FS/part_export/broadband_sqlradius.pm +++ b/FS/FS/part_export/broadband_sqlradius.pm @@ -6,6 +6,7 @@ use Tie::IxHash; use FS::Conf; use FS::Record qw( dbh str2time_sql ); #qsearch qsearchs ); use FS::part_export::sqlradius qw(sqlradius_connect); +use NEXT; FS::UID->install_callback(sub { $conf = new FS::Conf }); @@ -88,7 +89,9 @@ sub export_username { sub radius_reply { my($self, $svc_broadband) = (shift, shift); - my %reply; + # start with attributes the service wants + my %reply = $self->NEXT::radius_reply($svc_broadband); + # add export-specific stuff if ( length($self->option('ip_addr_as',1)) and length($svc_broadband->ip_addr) ) { $reply{$self->option('ip_addr_as')} = $svc_broadband->ip_addr; @@ -98,8 +101,9 @@ sub radius_reply { sub radius_check { my($self, $svc_broadband) = (shift, shift); + + my %check = $self->SUPER::radius_check($svc_broadband); my $password_attrib = $conf->config('radius-password') || 'Password'; - my %check; if ( $self->option('mac_as_password') ) { $check{$password_attrib} = $self->export_username($svc_broadband); } diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index 833dd9a1d..c8a963dbc 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -9,6 +9,7 @@ use FS::part_export; use FS::svc_acct; use FS::export_svc; use Carp qw( cluck ); +use NEXT; @ISA = qw(FS::part_export); @EXPORT_OK = qw( sqlradius_connect ); @@ -133,12 +134,14 @@ sub export_username { # override for other svcdb sub radius_reply { #override for other svcdb my($self, $svc_acct) = (shift, shift); - $svc_acct->radius_reply; + my %every = $svc_acct->EVERY::radius_reply; + map { @$_ } values %every; } sub radius_check { #override for other svcdb my($self, $svc_acct) = (shift, shift); - $svc_acct->radius_check; + my %every = $svc_acct->EVERY::radius_check; + map { @$_ } values %every; } sub _export_insert { @@ -194,8 +197,8 @@ sub _export_replace { foreach my $table (qw(reply check)) { my $method = "radius_$table"; - my %new = $new->$method(); - my %old = $old->$method(); + my %new = $self->$method($new); + my %old = $self->$method($old); if ( grep { !exists $old{$_} #new attributes || $new{$_} ne $old{$_} #changed } keys %new diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index da794dd4c..a1168199c 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -65,6 +65,10 @@ empty for full access, "readonly" for read-only, "hidden" to hide it entirely right to change the password field, rather than just "Edit password". Only relevant to svc_acct for now. +=item has_router - Allow the service to have an L<FS::router> connected +through it. Probably only relevant to svc_broadband, svc_acct, and svc_dsl +for now. + =back =head1 METHODS @@ -394,11 +398,12 @@ sub check { $self->ut_numbern('svcpart') || $self->ut_text('svc') || $self->ut_alpha('svcdb') - || $self->ut_enum('disabled', [ '', 'Y' ] ) - || $self->ut_enum('preserve', [ '', 'Y' ] ) + || $self->ut_flag('disabled') + || $self->ut_flag('preserve') || $self->ut_enum('selfservice_access', [ '', 'hidden', 'readonly' ] ) || $self->ut_foreign_keyn('classnum', 'part_svc_class', 'classnum' ) - || $self->ut_enum('restrict_edit_password', [ '', 'Y' ] ) + || $self->ut_flag('restrict_edit_password') + || $self->ut_flag('has_router') ; return $error if $error; diff --git a/FS/FS/router.pm b/FS/FS/router.pm index 6fa44b408..937dc1f45 100755 --- a/FS/FS/router.pm +++ b/FS/FS/router.pm @@ -63,16 +63,87 @@ sub table { 'router'; } Adds this record to the database. If there is an error, returns the error, otherwise returns false. -=item delete +If the pseudo-field 'blocknum' is set to an L<FS::addr_block> number, then +that address block will be assigned to this router. Currently only one +block can be assigned this way. + +=cut + +sub insert { + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; -Deletes this record from the database. If there is an error, returns the -error, otherwise returns false. + my $self = shift; + my $error = $self->SUPER::insert(@_); + return $error if $error; + if ( $self->blocknum ) { + my $block = FS::addr_block->by_key($self->blocknum); + if ($block) { + if ($block->routernum) { + $error = "block ".$block->cidr." is already assigned to a router"; + } else { + $block->set('routernum', $self->routernum); + $block->set('manual_flag', 'Y'); + $error = $block->replace; + } + } else { + $error = "blocknum ".$self->blocknum." not found"; + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + $dbh->commit if $oldAutoCommit; + return $error; +} =item replace OLD_RECORD Replaces OLD_RECORD with this one in the database. If there is an error, returns the error, otherwise returns false. +=cut + +sub replace { + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $self = shift; + my $old = shift || $self->replace_old; + my $error = $self->SUPER::replace($old, @_); + return $error if $error; + + if ( defined($self->blocknum) ) { + #warn "FS::router::replace: blocknum = ".$self->blocknum."\n"; + # then release any blocks we're already holding + foreach my $block ($self->addr_block) { + $block->set('routernum', 0); + $block->set('manual_flag', ''); + $error ||= $block->replace; + } + if ( !$error and $self->blocknum > 0 ) { + # and, if the new blocknum is a real blocknum, assign it + my $block = FS::addr_block->by_key($self->blocknum); + if ( $block ) { + $block->set('routernum', $self->routernum); + $block->set('manual_flag', ''); + $error ||= $block->replace; + } else { + $error = "blocknum ".$self->blocknum." not found"; + } + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + $dbh->commit if $oldAutoCommit; + return $error; +} + =item check Checks all fields to make sure this is a valid record. If there is an error, @@ -89,6 +160,7 @@ sub check { || $self->ut_text('routername') || $self->ut_enum('manual_addr', [ '', 'Y' ]) || $self->ut_agentnum_acl('agentnum', 'Broadband global configuration') + || $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum') ; return $error if $error; @@ -97,28 +169,25 @@ sub check { =item delete -Deletes this router if and only if no address blocks (see L<FS::addr_block>) -are currently allocated to it. +Deallocate all address blocks from this router and delete it. =cut sub delete { my $self = shift; - return 'Router has address blocks allocated to it' if $self->addr_block; - - local $SIG{HUP} = 'IGNORE'; - local $SIG{INT} = 'IGNORE'; - local $SIG{QUIT} = 'IGNORE'; - local $SIG{TERM} = 'IGNORE'; - local $SIG{TSTP} = 'IGNORE'; - local $SIG{PIPE} = 'IGNORE'; - my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; - - my $error = $self->SUPER::delete; + + my $error; + foreach my $block ($self->addr_block) { + $block->set('manual_flag', ''); + $block->set('routernum', 0); + $error ||= $block->replace; + } + + $error ||= $self->SUPER::delete; if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -187,6 +256,19 @@ sub agent { qsearchs('agent', { 'agentnum' => shift->agentnum }); } +=item cust_svc + +Returns the cust_svc associated with this router, if any. This should be +the service that I<provides connectivity to the router>, not any service +connected I<through> the router. + +=cut + +sub cust_svc { + my $svcnum = shift->svcnum or return undef; + FS::cust_svc->by_key($svcnum); +} + =back =head1 SEE ALSO diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 0aea4559b..3993d3d64 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -367,6 +367,7 @@ sub delete { || $self->SUPER::delete || $self->export('delete', @$export_args) || $self->return_inventory + || $self->release_router || $self->predelete_hook || $self->cust_svc->delete ; @@ -989,6 +990,24 @@ sub inventory_item { }); } +=item release_router + +Delete any routers associated with this service. This will release their +address blocks, also. + +=cut + +sub release_router { + my $self = shift; + my @routers = qsearch('router', { svcnum => $self->svcnum }); + foreach (@routers) { + my $error = $_->delete; + return "$error (removing router '".$_->routername."')" if $error; + } + ''; +} + + =item cust_svc Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc diff --git a/FS/FS/svc_IP_Mixin.pm b/FS/FS/svc_IP_Mixin.pm index 7eda7e02c..ff7c2f5d4 100644 --- a/FS/FS/svc_IP_Mixin.pm +++ b/FS/FS/svc_IP_Mixin.pm @@ -3,6 +3,7 @@ package FS::svc_IP_Mixin; use strict; use base 'FS::IP_Mixin'; use FS::Record qw(qsearchs qsearch); +use NEXT; =item addr_block @@ -120,4 +121,93 @@ sub _is_used { } } +=item attached_router + +Returns the L<FS::router> attached via this service (as opposed to the one +this service is connected through), that is, a router whose "svcnum" field +equals this service's primary key. + +If the 'router_routernum' pseudo-field is set, returns that router instead. + +=cut + +sub attached_router { + my $self = shift; + if ( length($self->get('router_routernum') )) { + return FS::router->by_key($self->router_routernum); + } else { + qsearchs('router', { 'svcnum' => $self->svcnum }); + } +} + +=item attached_block + +Returns the address block (L<FS::addr_block>) assigned to the attached_router, +if there is one. + +If the 'router_blocknum' pseudo-field is set, returns that block instead. + +=cut + +sub attached_block { + my $self = shift; + if ( length($self->get('router_blocknum')) ) { + return FS::addr_block->by_key($self->router_blocknum); + } else { + my $router = $self->attached_router or return ''; + my ($block) = $router->addr_block; + return $block || ''; + } +} + +=item radius_check + +Returns nothing. + +=cut + +sub radius_check { } + +=item radius_reply + +Returns RADIUS reply items that are relevant across all exports and +necessary for the IP address configuration of the service. Currently, that +means "Framed-Route" if there's an attached router. + +=cut + +sub radius_reply { + my $self = shift; + my %reply; + my ($block) = $self->attached_block; + if ( $block ) { + # block routed over dynamic IP: "192.168.100.0/29 0.0.0.0 1" + # or + # block routed over fixed IP: "192.168.100.0/29 192.168.100.1 1" + # (the "1" at the end is the route metric) + $reply{'Framed-Route'} = + $block->cidr . ' ' . + ($self->ip_addr || '0.0.0.0') . ' 1'; + } + %reply; +} + +sub replace_check { + my ($new, $old) = @_; + # this modifies $old, not $new, which is a slight abuse of replace_check, + # but there's no way to ensure that replace_old gets called... + # + # ensure that router_routernum and router_blocknum are set to their + # current values, so that exports remember the service's attached router + # and block even after they've been replaced + my $router = $old->attached_router; + my $block = $old->attached_block; + $old->set('router_routernum', $router ? $router->routernum : 0); + $old->set('router_blocknum', $block ? $block->blocknum : 0); + my $err_or_ref = $new->NEXT::replace_check($old) || ''; + # because NEXT::replace_check($old) ends up trying to AUTOLOAD replace_check + # which is dumb, but easily worked around + ref($err_or_ref) ? '' : $err_or_ref; +} + 1; |