1 package FS::part_export::dma_radiusmanager;
4 use vars qw($DEBUG %info %options);
5 use base 'FS::part_export';
10 use Digest::MD5 'md5_hex';
12 use Locale::Country qw(code2country);
13 use Locale::SubCountry;
14 use Date::Format 'time2str';
16 tie %options, 'Tie::IxHash',
17 'dbname' => { label=>'Database name', default=>'radius' },
18 'username' => { label=>'Database username' },
19 'password' => { label=>'Database password' },
20 'manager' => { label=>'Manager name' },
21 'groupid' => { label=>'Group ID', default=>'1' },
22 'service_prefix' => { label=>'Service name prefix' },
23 'nasnames' => { label=>'NAS IDs/addresses' },
24 'debug' => { label=>'Enable debugging', type=>'checkbox' },
29 'desc' => 'Export to DMA Radius Manager',
30 'options' => \%options,
39 my $datasrc = 'dbi:mysql:host='.$self->machine.
40 ':database='.$self->option('dbname');
43 $self->option('username'),
44 $self->option('password'),
46 ) or die $DBI::errstr;
49 sub export_insert { my $self = shift; $self->dma_rm_queue('insert', @_) }
50 sub export_delete { my $self = shift; $self->dma_rm_queue('delete', @_) }
51 sub export_replace { my $self = shift; $self->dma_rm_queue('replace', @_) }
52 sub export_suspend { my $self = shift; $self->dma_rm_queue('suspend', @_) }
53 sub export_unsuspend { my $self = shift; $self->dma_rm_queue('unsuspend', @_) }
56 my ($self, $action, $svc_acct, $old) = @_;
58 my $svcnum = $svc_acct->svcnum;
60 my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
61 my $cust_main = $cust_pkg->cust_main;
62 my $location = $cust_pkg->cust_location;
64 my $address = $location->address1;
65 $address .= ' '.$location->address2 if $location->address2;
66 my $country = code2country($location->country);
67 my $lsc = Locale::SubCountry->new($location->country);
68 my $state = $lsc->full_name($location->state) if defined($lsc);
72 username => $svc_acct->username,
73 password => md5_hex($svc_acct->_password),
74 groupid => $self->option('groupid'),
76 firstname => $cust_main->first,
77 lastname => $cust_main->last,
78 company => $cust_main->company,
79 phone => ($cust_main->daytime || $cust_main->night),
80 mobile => $cust_main->mobile,
81 address => $location->address1, # address2?
82 city => $location->city,
83 state => $state, #full name
84 zip => $location->zip,
85 country => $country, #full name
86 gpslat => $location->latitude,
87 gpslong => $location->longitude,
88 comment => 'svcnum'.$svcnum,
89 createdby => $self->option('manager'),
90 owner => $self->option('manager'),
91 email => $cust_main->invoicing_list_emailonly_scalar,
93 # used internally by the export
94 exportnum => $self->exportnum,
97 svcpart => $svc_acct->cust_svc->svcpart,
98 _password => $svc_acct->_password,
100 if ( $action eq 'replace' ) {
101 $params{'old_username'} = $old->username;
102 $params{'old_password'} = $old->_password;
104 my $queue = FS::queue->new({
106 'job' => "FS::part_export::dma_radiusmanager::dma_rm_action",
108 $queue->insert(%params);
113 my $svcnum = delete $params{svcnum};
114 my $action = delete $params{action};
115 my $svcpart = delete $params{svcpart};
116 my $exportnum = delete $params{exportnum};
118 my $username = $params{username};
119 my $password = delete $params{_password};
121 my $self = FS::part_export->by_key($exportnum);
122 my $dbh = $self->connect;
123 local $DEBUG = 1 if $self->option('debug');
125 # export the part_svc if needed, and get its srvid
126 my $part_svc = FS::part_svc->by_key($svcpart);
127 my $srvid = $self->export_part_svc($part_svc, $dbh); # dies on error
128 $params{srvid} = $srvid;
130 if ( $action eq 'insert' ) {
131 $params{'createdon'} = time2str('%Y-%m-%d', time);
132 $params{'expiration'} = time2str('%Y-%m-%d', time);
133 warn "rm_users: inserting svcnum$svcnum\n" if $DEBUG;
134 my $sth = $dbh->prepare( 'INSERT INTO rm_users ( '.
135 join(', ', keys(%params)).
137 join(', ', ('?') x keys(%params)).
140 $sth->execute(values(%params)) or die $dbh->errstr;
142 # minor false laziness w/ sqlradius_insert
143 warn "radcheck: inserting $username\n" if $DEBUG;
144 $sth = $dbh->prepare( 'INSERT INTO radcheck (
145 username, attribute, op, value
146 ) VALUES (?, ?, ?, ?)' );
149 'Cleartext-Password',
152 ) or die $dbh->errstr;
158 1, # should this be an option?
159 ) or die $dbh->errstr;
160 # also, we don't support exporting any other radius attrs...
161 # those should go in 'custattr' if we need them
162 } elsif ( $action eq 'replace' ) {
164 my $old_username = delete $params{old_username};
165 my $old_password = delete $params{old_password};
166 # svcnum is invariant and on the remote side, so we don't need any
167 # of the old fields to do this
168 warn "rm_users: updating svcnum$svcnum\n" if $DEBUG;
169 my $sth = $dbh->prepare( 'UPDATE rm_users SET '.
170 join(', ', map { "$_ = ?" } keys(%params)).
173 $sth->execute(values(%params), $params{comment}) or die $dbh->errstr;
174 # except for username/password changes
175 if ( $old_password ne $password ) {
176 warn "radcheck: changing password for $old_username\n" if $DEBUG;
177 $sth = $dbh->prepare( 'UPDATE radcheck SET value = ? '.
178 'WHERE username = ? and attribute = \'Cleartext-Password\''
180 $sth->execute($password, $old_username) or die $dbh->errstr;
182 if ( $old_username ne $username ) {
183 warn "radcheck: changing username $old_username to $username\n"
185 $sth = $dbh->prepare( 'UPDATE radcheck SET username = ? '.
188 $sth->execute($username, $old_username) or die $dbh->errstr;
191 } elsif ( $action eq 'suspend' ) {
194 warn "rm_users: disabling svcnum#$svcnum\n" if $DEBUG;
195 my $sth = $dbh->prepare( 'UPDATE rm_users SET enableuser = 0 '.
198 $sth->execute($params{comment}) or die $dbh->errstr;
200 } elsif ( $action eq 'unsuspend' ) {
202 warn "rm_users: enabling svcnum#$svcnum\n" if $DEBUG;
203 my $sth = $dbh->prepare( 'UPDATE rm_users SET enableuser = 1 '.
206 $sth->execute($params{comment}) or die $dbh->errstr;
208 } elsif ( $action eq 'delete' ) {
210 warn "rm_users: deleting svcnum#$svcnum\n" if $DEBUG;
211 my $sth = $dbh->prepare( 'DELETE FROM rm_users WHERE comment = ?' );
212 $sth->execute($params{comment}) or die $dbh->errstr;
214 warn "radcheck: deleting $username\n" if $DEBUG;
215 $sth = $dbh->prepare( 'DELETE FROM radcheck WHERE username = ?' );
216 $sth->execute($username) or die $dbh->errstr;
218 # if this were smarter it would also delete the rm_services record
219 # if it was no longer in use, but that's not really necessary
226 =item export_part_svc PART_SVC DBH
228 Query Radius Manager for a service definition matching the name of
229 PART_SVC (optionally with a prefix defined in the export options).
230 If there is one, update it to match the attributes of PART_SVC; if
231 not, create one. Then return its srvid.
235 sub export_part_svc {
236 my ($self, $part_svc, $dbh) = @_;
238 my $name = $self->option('service_prefix').$part_svc->svc;
242 'enableservice' => 1,
244 'dailynextsrvid' => -1,
247 # use speed settings from fixed usergroups configured on this part_svc
248 if ( my $psc = $part_svc->part_svc_column('usergroup') ) {
249 if ( $psc->columnflag eq 'F' ) {
250 # each part_svc really should only have one fixed group with non-null
251 # speed settings, but go by priority order for consistency
253 sort { $a->priority <=> $b->priority }
255 map { FS::radius_group->by_key($_) }
256 split(/\s*,\s*/, $psc->columnvalue);
258 } # otherwise there are no fixed groups, so leave speed empty
260 foreach (qw(down up)) {
261 my $speed = "speed_$_";
262 foreach my $group (@fixed_groups) {
263 if ( ($group->$speed || 0) > 0 ) {
264 $params{$_.'rate'} = $group->$speed;
269 # anything else we need here? poolname, maybe?
271 warn "rm_services: looking for '$name'\n" if $DEBUG;
272 my $sth = $dbh->prepare(
273 'SELECT srvid FROM rm_services WHERE srvname = ? AND enableservice = 1'
275 $sth->execute($name) or die $dbh->errstr;
276 if ( $sth->rows > 1 ) {
277 die "Multiple services with name '$name' found in Radius Manager.\n";
278 } elsif ( $sth->rows == 1 ) {
279 my $row = $sth->fetchrow_arrayref;
280 my $srvid = $row->[0];
281 warn "rm_services: updating srvid#$srvid\n" if $DEBUG;
282 $sth = $dbh->prepare(
283 'UPDATE rm_services SET '.join(', ', map {"$_ = ?"} keys %params) .
286 $sth->execute(values(%params), $srvid) or die $dbh->errstr;
288 } else { # $sth->rows == 0
290 # but first... get the next available srvid
291 $sth = $dbh->prepare('SELECT MAX(srvid) FROM rm_services');
292 $sth->execute or die $dbh->errstr;
293 my $srvid = 1; # just in case you somehow have nothing in your database
295 $srvid = $sth->fetchrow_arrayref->[0] + 1;
297 $params{'srvid'} = $srvid;
298 # NOW create a new one
299 warn "rm_services: inserting '$name' as srvid#$srvid\n" if $DEBUG;
300 $sth = $dbh->prepare(
301 'INSERT INTO rm_services ('.join(', ', keys %params).
302 ') VALUES ('.join(', ', map {'?'} keys %params).')'
304 $sth->execute(values(%params)) or die $dbh->errstr;
305 # also link it to our manager name
306 warn "rm_services: linking to manager\n" if $DEBUG;
307 $sth = $dbh->prepare(
308 'INSERT INTO rm_allowedmanagers (srvid, managername) VALUES (?, ?)'
310 $sth->execute($srvid, $self->option('manager')) or die $dbh->errstr;
311 # and allow it on our NAS
312 $sth = $dbh->prepare(
313 'INSERT INTO rm_allowednases (srvid, nasid) VALUES (?, ?)'
315 foreach my $nasid ($self->nas_ids($dbh)) {
316 warn "rm_services: linking to nasid#$nasid\n" if $DEBUG;
317 $sth->execute($srvid, $nasid) or die $dbh->errstr;
325 Convert the 'nasnames option into a list of real NAS ids.
333 my @nasnames = split(/\s*,\s*/, $self->option('nasnames'));
334 return unless @nasnames;
335 # pass these through unchanged
336 my @ids = grep { /^\d+$/ } @nasnames;
337 @nasnames = grep { not /^\d+$/ } @nasnames;
339 my $in_nasnames = join(',', map {$dbh->quote($_)} @nasnames);
341 my $sth = $dbh->prepare("SELECT id FROM nas WHERE nasname IN ($in_nasnames)");
342 $sth->execute or die $dbh->errstr;
343 my $rows = $sth->fetchall_arrayref;
344 push @ids, $_->[0] foreach @$rows;