summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/Schema.pm1
-rw-r--r--FS/FS/part_export/broadband_sqlradius.pm8
-rw-r--r--FS/FS/part_export/sqlradius.pm11
-rw-r--r--FS/FS/part_svc.pm11
-rwxr-xr-xFS/FS/router.pm114
-rw-r--r--FS/FS/svc_Common.pm19
-rw-r--r--FS/FS/svc_IP_Mixin.pm90
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;