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 'template_name' => { label=>'Template service name' },
22 'service_prefix' => { label=>'Service name prefix' },
23 'debug' => { label=>'Enable debugging', type=>'checkbox' },
28 'desc' => 'Export to DMA Radius Manager',
29 'options' => \%options,
38 my $datasrc = 'dbi:mysql:host='.$self->machine.
39 ':database='.$self->option('dbname');
42 $self->option('username'),
43 $self->option('password'),
45 ) or die $DBI::errstr;
48 sub export_insert { my $self = shift; $self->dma_rm_queue('insert', @_) }
49 sub export_delete { my $self = shift; $self->dma_rm_queue('delete', @_) }
50 sub export_replace { my $self = shift; $self->dma_rm_queue('replace', @_) }
51 sub export_suspend { my $self = shift; $self->dma_rm_queue('suspend', @_) }
52 sub export_unsuspend { my $self = shift; $self->dma_rm_queue('unsuspend', @_) }
55 my ($self, $action, $svc_acct, $old) = @_;
57 my $svcnum = $svc_acct->svcnum;
59 my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
60 my $cust_main = $cust_pkg->cust_main;
61 my $location = $cust_pkg->cust_location;
63 my $address = $location->address1;
64 $address .= ' '.$location->address2 if $location->address2;
65 my $country = code2country($location->country);
66 my $lsc = Locale::SubCountry->new($location->country);
67 my $state = $lsc->full_name($location->state) if defined($lsc);
71 username => $svc_acct->username,
72 password => md5_hex($svc_acct->_password),
73 groupid => $self->option('groupid'),
75 firstname => $cust_main->first,
76 lastname => $cust_main->last,
77 company => $cust_main->company,
78 phone => ($cust_main->daytime || $cust_main->night),
79 mobile => $cust_main->mobile,
80 address => $location->address1, # address2?
81 city => $location->city,
82 state => $state, #full name
83 zip => $location->zip,
84 country => $country, #full name
85 gpslat => $location->latitude,
86 gpslong => $location->longitude,
87 comment => 'svcnum'.$svcnum,
88 createdby => $self->option('manager'),
89 owner => $self->option('manager'),
90 email => $cust_main->invoicing_list_emailonly_scalar,
92 # used internally by the export
93 exportnum => $self->exportnum,
96 svcpart => $svc_acct->cust_svc->svcpart,
97 _password => $svc_acct->_password,
99 if ( $action eq 'replace' ) {
100 $params{'old_username'} = $old->username;
101 $params{'old_password'} = $old->_password;
103 my $queue = FS::queue->new({
105 'job' => "FS::part_export::dma_radiusmanager::dma_rm_action",
107 $queue->insert(%params);
112 my $svcnum = delete $params{svcnum};
113 my $action = delete $params{action};
114 my $svcpart = delete $params{svcpart};
115 my $exportnum = delete $params{exportnum};
117 my $username = $params{username};
118 my $password = delete $params{_password};
120 my $self = FS::part_export->by_key($exportnum);
121 my $dbh = $self->connect;
122 local $DEBUG = 1 if $self->option('debug');
124 # export the part_svc if needed, and get its srvid
125 my $part_svc = FS::part_svc->by_key($svcpart);
126 my $srvid = $self->export_part_svc($part_svc, $dbh); # dies on error
127 $params{srvid} = $srvid;
129 if ( $action eq 'insert' ) {
130 $params{'createdon'} = time2str('%Y-%m-%d', time);
131 $params{'expiration'} = time2str('%Y-%m-%d', time);
132 warn "rm_users: inserting svcnum$svcnum\n" if $DEBUG;
133 my $sth = $dbh->prepare( 'INSERT INTO rm_users ( '.
134 join(', ', keys(%params)).
136 join(', ', ('?') x keys(%params)).
139 $sth->execute(values(%params)) or die $dbh->errstr;
141 # minor false laziness w/ sqlradius_insert
142 warn "radcheck: inserting $username\n" if $DEBUG;
143 $sth = $dbh->prepare( 'INSERT INTO radcheck (
144 username, attribute, op, value
145 ) VALUES (?, ?, ?, ?)' );
148 'Cleartext-Password',
151 ) or die $dbh->errstr;
157 1, # should this be an option?
158 ) or die $dbh->errstr;
159 # also, we don't support exporting any other radius attrs...
160 # those should go in 'custattr' if we need them
161 } elsif ( $action eq 'replace' ) {
163 my $old_username = delete $params{old_username};
164 my $old_password = delete $params{old_password};
165 # svcnum is invariant and on the remote side, so we don't need any
166 # of the old fields to do this
167 warn "rm_users: updating svcnum$svcnum\n" if $DEBUG;
168 my $sth = $dbh->prepare( 'UPDATE rm_users SET '.
169 join(', ', map { "$_ = ?" } keys(%params)).
172 $sth->execute(values(%params), $params{comment}) or die $dbh->errstr;
173 # except for username/password changes
174 if ( $old_password ne $password ) {
175 warn "radcheck: changing password for $old_username\n" if $DEBUG;
176 $sth = $dbh->prepare( 'UPDATE radcheck SET value = ? '.
177 'WHERE username = ? and attribute = \'Cleartext-Password\''
179 $sth->execute($password, $old_username) or die $dbh->errstr;
181 if ( $old_username ne $username ) {
182 warn "radcheck: changing username $old_username to $username\n"
184 $sth = $dbh->prepare( 'UPDATE radcheck SET username = ? '.
187 $sth->execute($username, $old_username) or die $dbh->errstr;
190 } elsif ( $action eq 'suspend' ) {
193 warn "rm_users: disabling svcnum#$svcnum\n" if $DEBUG;
194 my $sth = $dbh->prepare( 'UPDATE rm_users SET enableuser = 0 '.
197 $sth->execute($params{comment}) or die $dbh->errstr;
199 } elsif ( $action eq 'unsuspend' ) {
201 warn "rm_users: enabling svcnum#$svcnum\n" if $DEBUG;
202 my $sth = $dbh->prepare( 'UPDATE rm_users SET enableuser = 1 '.
205 $sth->execute($params{comment}) or die $dbh->errstr;
207 } elsif ( $action eq 'delete' ) {
209 warn "rm_users: deleting svcnum#$svcnum\n" if $DEBUG;
210 my $sth = $dbh->prepare( 'DELETE FROM rm_users WHERE comment = ?' );
211 $sth->execute($params{comment}) or die $dbh->errstr;
213 warn "radcheck: deleting $username\n" if $DEBUG;
214 $sth = $dbh->prepare( 'DELETE FROM radcheck WHERE username = ?' );
215 $sth->execute($username) or die $dbh->errstr;
217 # if this were smarter it would also delete the rm_services record
218 # if it was no longer in use, but that's not really necessary
225 =item export_part_svc PART_SVC DBH
227 Query Radius Manager for a service definition matching the name of
228 PART_SVC (optionally with a prefix defined in the export options).
229 If there is one, update it to match the attributes of PART_SVC; if
230 not, create one. Then return its srvid.
234 sub export_part_svc {
235 my ($self, $part_svc, $dbh) = @_;
237 # if $dbh exists, use the existing transaction
238 # otherwise create our own and commit when finished
241 $dbh = $self->connect;
245 my $name = $self->option('service_prefix').$part_svc->svc;
249 'enableservice' => 1,
251 'dailynextsrvid' => -1,
252 # force price-related fields to zero
256 'unitpriceaddtax' => 0,
259 # use speed settings from fixed usergroups configured on this part_svc
260 if ( my $psc = $part_svc->part_svc_column('usergroup') ) {
261 # each part_svc really should only have one fixed group with non-null
262 # speed settings, but go by priority order for consistency
264 sort { $a->priority <=> $b->priority }
266 map { FS::radius_group->by_key($_) }
267 split(/\s*,\s*/, $psc->columnvalue);
268 } # otherwise there are no fixed groups, so leave speed empty
270 foreach (qw(down up)) {
271 my $speed = "speed_$_";
272 foreach my $group (@fixed_groups) {
273 if ( ($group->$speed || 0) > 0 ) {
274 $params{$_.'rate'} = $group->$speed;
279 # anything else we need here? poolname, maybe?
281 warn "rm_services: looking for '$name'\n" if $DEBUG;
282 my $sth = $dbh->prepare(
283 'SELECT srvid FROM rm_services WHERE srvname = ? AND enableservice = 1'
285 $sth->execute($name) or die $dbh->errstr;
286 if ( $sth->rows > 1 ) {
287 die "Multiple services with name '$name' found in Radius Manager.\n";
289 } elsif ( $sth->rows == 0 ) {
290 # leave this blank to disable creating new service defs
291 my $template_name = $self->option('template_name');
293 die "Can't create a new service profile--no template service specified.\n"
294 unless $template_name;
296 warn "rm_services: fetching template '$template_name'\n" if $DEBUG;
297 $sth = $dbh->prepare('SELECT * FROM rm_services WHERE srvname = ? LIMIT 1');
298 $sth->execute($template_name);
299 die "Can't create a new service profile--template service ".
300 "'$template_name' not found.\n" unless $sth->rows == 1;
301 my $template = $sth->fetchrow_hashref;
302 %params = (%$template, %params);
304 # get the next available srvid
305 $sth = $dbh->prepare('SELECT MAX(srvid) FROM rm_services');
306 $sth->execute or die $dbh->errstr;
309 $srvid = $sth->fetchrow_arrayref->[0] + 1;
311 $params{'srvid'} = $srvid;
313 # create a new one based on the template
314 warn "rm_services: inserting '$name' as srvid#$srvid\n" if $DEBUG;
315 $sth = $dbh->prepare(
316 'INSERT INTO rm_services ('.join(', ', keys %params).
317 ') VALUES ('.join(', ', map {'?'} keys %params).')'
319 $sth->execute(values(%params)) or die $dbh->errstr;
320 # also link it to all the managers allowed on the template service
321 warn "rm_services: linking to manager\n" if $DEBUG;
322 $sth = $dbh->prepare(
323 'INSERT INTO rm_allowedmanagers (srvid, managername) '.
324 'SELECT ?, managername FROM rm_allowedmanagers WHERE srvid = ?'
326 $sth->execute($srvid, $template->{srvid}) or die $dbh->errstr;
327 # and the same for NASes
328 warn "rm_services: linking to nas\n" if $DEBUG;
329 $sth = $dbh->prepare(
330 'INSERT INTO rm_allowednases (srvid, nasid) '.
331 'SELECT ?, nasid FROM rm_allowednases WHERE srvid = ?'
333 $sth->execute($srvid, $template->{srvid}) or die $dbh->errstr;
335 $dbh->commit if $commit;
338 } else { # $sth->rows == 1, it already exists
340 my $row = $sth->fetchrow_arrayref;
341 my $srvid = $row->[0];
342 warn "rm_services: updating srvid#$srvid\n" if $DEBUG;
343 $sth = $dbh->prepare(
344 'UPDATE rm_services SET '.join(', ', map {"$_ = ?"} keys %params) .
347 $sth->execute(values(%params), $srvid) or die $dbh->errstr;
349 $dbh->commit if $commit;