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 tie %options, 'Tie::IxHash',
13 'dbname' => { label=>'Database name', default=>'radius' },
14 'username' => { label=>'Database username' },
15 'password' => { label=>'Database password' },
16 'manager' => { label=>'Manager name' },
17 'groupid' => { label=>'Group ID', default=>'1' },
18 'service_prefix' => { label=>'Service name prefix' },
19 'nasnames' => { label=>'NAS IDs/addresses' },
20 'debug' => { label=>'Enable debugging', type=>'checkbox' },
25 'desc' => 'Export to DMA Radius Manager',
26 'options' => \%options,
35 my $datasrc = 'dbi:mysql:host='.$self->machine.
36 ':database='.$self->option('dbname');
39 $self->option('username'),
40 $self->option('password'),
42 ) or die $DBI::errstr;
45 sub export_insert { my $self = shift; $self->dma_rm_queue('insert', @_) }
46 sub export_delete { my $self = shift; $self->dma_rm_queue('delete', @_) }
47 sub export_replace { my $self = shift; $self->dma_rm_queue('replace', @_) }
48 sub export_suspend { my $self = shift; $self->dma_rm_queue('suspend', @_) }
49 sub export_unsuspend { my $self = shift; $self->dma_rm_queue('unsuspend', @_) }
52 my ($self, $action, $svc_acct, $old) = @_;
54 my $svcnum = $svc_acct->svcnum;
56 my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
57 my $cust_main = $cust_pkg->cust_main;
58 my $location = $cust_pkg->cust_location;
62 username => $svc_acct->username,
63 password => md5_hex($svc_acct->_password),
64 groupid => $self->option('groupid'),
66 firstname => $cust_main->first,
67 lastname => $cust_main->last,
68 company => $cust_main->company,
69 phone => ($cust_main->daytime || $cust_main->night),
70 mobile => $cust_main->mobile,
71 address => $location->address1, # address2?
72 city => $location->city,
73 state => $location->state,
74 zip => $location->zip,
75 country => $location->country,
76 gpslat => $location->latitude,
77 gpslong => $location->longitude,
78 comment => 'svcnum'.$svcnum,
79 createdby => $self->option('manager'),
80 owner => $self->option('manager'),
81 email => $cust_main->invoicing_list_emailonly_scalar,
83 # used internally by the export
84 exportnum => $self->exportnum,
87 svcpart => $svc_acct->cust_svc->svcpart,
88 _password => $svc_acct->_password,
90 if ( $action eq 'replace' ) {
91 $params{'old_username'} = $old->username;
92 $params{'old_password'} = $old->_password;
94 my $queue = FS::queue->new({
96 'job' => "FS::part_export::dma_radiusmanager::dma_rm_action",
98 $queue->insert(%params);
103 my $svcnum = delete $params{svcnum};
104 my $action = delete $params{action};
105 my $svcpart = delete $params{svcpart};
106 my $exportnum = delete $params{exportnum};
108 my $username = $params{username};
109 my $password = delete $params{_password};
111 my $self = FS::part_export->by_key($exportnum);
112 my $dbh = $self->connect;
113 local $DEBUG = 1 if $self->option('debug');
115 # export the part_svc if needed, and get its srvid
116 my $part_svc = FS::part_svc->by_key($svcpart);
117 my $srvid = $self->export_part_svc($part_svc, $dbh); # dies on error
118 $params{srvid} = $srvid;
120 if ( $action eq 'insert' ) {
121 warn "rm_users: inserting svcnum$svcnum\n" if $DEBUG;
122 my $sth = $dbh->prepare( 'INSERT INTO rm_users ( '.
123 join(', ', keys(%params)).
125 join(', ', ('?') x keys(%params)).
128 $sth->execute(values(%params)) or die $dbh->errstr;
130 # minor false laziness w/ sqlradius_insert
131 warn "radcheck: inserting $username\n" if $DEBUG;
132 $sth = $dbh->prepare( 'INSERT INTO radcheck (
133 username, attribute, op, value
134 ) VALUES (?, ?, ?, ?)' );
137 'Cleartext-Password',
140 ) or die $dbh->errstr;
146 1, # should this be an option?
147 ) or die $dbh->errstr;
148 # also, we don't support exporting any other radius attrs...
149 # those should go in 'custattr' if we need them
150 } elsif ( $action eq 'replace' ) {
152 my $old_username = delete $params{old_username};
153 my $old_password = delete $params{old_password};
154 # svcnum is invariant and on the remote side, so we don't need any
155 # of the old fields to do this
156 warn "rm_users: updating svcnum$svcnum\n" if $DEBUG;
157 my $sth = $dbh->prepare( 'UPDATE rm_users SET '.
158 join(', ', map { "$_ = ?" } keys(%params)).
161 $sth->execute(values(%params), $params{comment}) or die $dbh->errstr;
162 # except for username/password changes
163 if ( $old_password ne $password ) {
164 warn "radcheck: changing password for $old_username\n" if $DEBUG;
165 $sth = $dbh->prepare( 'UPDATE radcheck SET value = ? '.
166 'WHERE username = ? and attribute = \'Cleartext-Password\''
168 $sth->execute($password, $old_username) or die $dbh->errstr;
170 if ( $old_username ne $username ) {
171 warn "radcheck: changing username $old_username to $username\n"
173 $sth = $dbh->prepare( 'UPDATE radcheck SET username = ? '.
176 $sth->execute($username, $old_username) or die $dbh->errstr;
179 } elsif ( $action eq 'suspend' ) {
182 warn "rm_users: disabling svcnum#$svcnum\n" if $DEBUG;
183 my $sth = $dbh->prepare( 'UPDATE rm_users SET enableuser = 0 '.
186 $sth->execute($params{comment}) or die $dbh->errstr;
188 } elsif ( $action eq 'unsuspend' ) {
190 warn "rm_users: enabling svcnum#$svcnum\n" if $DEBUG;
191 my $sth = $dbh->prepare( 'UPDATE rm_users SET enableuser = 1 '.
194 $sth->execute($params{comment}) or die $dbh->errstr;
196 } elsif ( $action eq 'delete' ) {
198 warn "rm_users: deleting svcnum#$svcnum\n" if $DEBUG;
199 my $sth = $dbh->prepare( 'DELETE FROM rm_users WHERE comment = ?' );
200 $sth->execute($params{comment}) or die $dbh->errstr;
202 warn "radcheck: deleting $username\n" if $DEBUG;
203 $sth = $dbh->prepare( 'DELETE FROM radcheck WHERE username = ?' );
204 $sth->execute($username) or die $dbh->errstr;
206 # if this were smarter it would also delete the rm_services record
207 # if it was no longer in use, but that's not really necessary
214 =item export_part_svc PART_SVC DBH
216 Query Radius Manager for a service definition matching the name of
217 PART_SVC (optionally with a prefix defined in the export options).
218 If there is one, update it to match the attributes of PART_SVC; if
219 not, create one. Then return its srvid.
223 sub export_part_svc {
224 my ($self, $part_svc, $dbh) = @_;
226 my $name = $self->option('service_prefix').$part_svc->svc;
230 'enableservice' => 1,
232 'dailynextsrvid' => -1,
235 # use speed settings from fixed usergroups configured on this part_svc
236 if ( my $psc = $part_svc->part_svc_column('usergroup') ) {
237 if ( $psc->columnflag eq 'F' ) {
238 # each part_svc really should only have one fixed group with non-null
239 # speed settings, but go by priority order for consistency
241 sort { $a->priority <=> $b->priority }
243 map { FS::radius_group->by_key($_) }
244 split(/\s*,\s*/, $psc->columnvalue);
246 } # otherwise there are no fixed groups, so leave speed empty
248 foreach (qw(down up)) {
249 my $speed = "speed_$_";
250 foreach my $group (@fixed_groups) {
251 if ( ($group->$speed || 0) > 0 ) {
252 $params{$_.'rate'} = $group->$speed;
257 # anything else we need here? poolname, maybe?
259 warn "rm_services: looking for '$name'\n" if $DEBUG;
260 my $sth = $dbh->prepare(
261 'SELECT srvid FROM rm_services WHERE srvname = ? AND enableservice = 1'
263 $sth->execute($name) or die $dbh->errstr;
264 if ( $sth->rows > 1 ) {
265 die "Multiple services with name '$name' found in Radius Manager.\n";
266 } elsif ( $sth->rows == 1 ) {
267 my $row = $sth->fetchrow_arrayref;
268 my $srvid = $row->[0];
269 warn "rm_services: updating srvid#$srvid\n" if $DEBUG;
270 $sth = $dbh->prepare(
271 'UPDATE rm_services SET '.join(', ', map {"$_ = ?"} keys %params) .
274 $sth->execute(values(%params), $srvid) or die $dbh->errstr;
276 } else { # $sth->rows == 0
278 # but first... get the next available srvid
279 $sth = $dbh->prepare('SELECT MAX(srvid) FROM rm_services');
280 $sth->execute or die $dbh->errstr;
281 my $srvid = 1; # just in case you somehow have nothing in your database
283 $srvid = $sth->fetchrow_arrayref->[0] + 1;
285 $params{'srvid'} = $srvid;
286 # NOW create a new one
287 warn "rm_services: inserting '$name' as srvid#$srvid\n" if $DEBUG;
288 $sth = $dbh->prepare(
289 'INSERT INTO rm_services ('.join(', ', keys %params).
290 ') VALUES ('.join(', ', map {'?'} keys %params).')'
292 $sth->execute(values(%params)) or die $dbh->errstr;
293 # also link it to our manager name
294 warn "rm_services: linking to manager\n" if $DEBUG;
295 $sth = $dbh->prepare(
296 'INSERT INTO rm_allowedmanagers (srvid, managername) VALUES (?, ?)'
298 $sth->execute($srvid, $self->option('manager')) or die $dbh->errstr;
299 # and allow it on our NAS
300 $sth = $dbh->prepare(
301 'INSERT INTO rm_allowednases (srvid, nasid) VALUES (?, ?)'
303 foreach my $nasid ($self->nas_ids($dbh)) {
304 warn "rm_services: linking to nasid#$nasid\n" if $DEBUG;
305 $sth->execute($srvid, $nasid) or die $dbh->errstr;
313 Convert the 'nasnames option into a list of real NAS ids.
321 my @nasnames = split(/\s*,\s*/, $self->option('nasnames'));
322 return unless @nasnames;
323 # pass these through unchanged
324 my @ids = grep { /^\d+$/ } @nasnames;
325 @nasnames = grep { not /^\d+$/ } @nasnames;
326 my $in_nasnames = join(',', map {$dbh->quote($_)} @nasnames);
328 my $sth = $dbh->prepare("SELECT id FROM nas WHERE nasname IN ($in_nasnames)");
329 $sth->execute or die $dbh->errstr;
330 my $rows = $sth->fetchall_arrayref;
331 push @ids, $_->[0] foreach @$rows;