This commit was generated by cvs2svn to compensate for changes in r9232,
[freeside.git] / FS / FS / part_export / textradius.pm
1 package FS::part_export::textradius;
2
3 use vars qw(@ISA %info $prefix);
4 use Fcntl qw(:flock);
5 use Tie::IxHash;
6 use FS::UID qw(datasrc);
7 use FS::part_export;
8
9 @ISA = qw(FS::part_export);
10
11 tie my %options, 'Tie::IxHash',
12   'user' => { label=>'Remote username', default=>'root' },
13   'users' => { label=>'users file location', default=>'/etc/raddb/users' },
14 ;
15
16 %info = (
17   'svc'     => 'svc_acct',
18   'desc'    =>
19     'Real-time export to a text /etc/raddb/users file (Livingston, Cistron)',
20   'options' => \%options,
21   'notes'   => <<'END'
22 This will edit a text RADIUS users file in place on a remote server.
23 Requires installation of
24 <a href="http://search.cpan.org/dist/RADIUS-UserFile">RADIUS::UserFile</a>
25 from CPAN.  If using RADIUS::UserFile 1.01, make sure to apply
26 <a href="http://rt.cpan.org/NoAuth/Bug.html?id=1210">this patch</a>.  Also
27 make sure <a href="http://rsync.samba.org/">rsync</a> is installed on the
28 remote machine, and <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">SSH is setup for unattended
29 operation</a>.
30 END
31 );
32
33 $prefix = "%%%FREESIDE_CONF%%%/export.";
34
35 sub rebless { shift; }
36
37 sub _export_insert {
38   my($self, $svc_acct) = (shift, shift);
39   $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'insert',
40     $svc_acct->username, $svc_acct->radius_check, '-', $svc_acct->radius_reply);
41   ref($err_or_queue) ? '' : $err_or_queue;
42 }
43
44 sub _export_replace {
45   my( $self, $new, $old ) = (shift, shift, shift);
46   return "can't (yet?) change username with textradius"
47     if $old->username ne $new->username;
48   #return '' unless $old->_password ne $new->_password;
49   $err_or_queue = $self->textradius_queue( $new->svcnum, 'insert',
50     $new->username, $new->radius_check, '-', $new->radius_reply);
51   ref($err_or_queue) ? '' : $err_or_queue;
52 }
53
54 sub _export_delete {
55   my( $self, $svc_acct ) = (shift, shift);
56   $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'delete',
57     $svc_acct->username );
58   ref($err_or_queue) ? '' : $err_or_queue;
59 }
60
61 #a good idea to queue anything that could fail or take any time
62 sub textradius_queue {
63   my( $self, $svcnum, $method ) = (shift, shift, shift);
64   my $queue = new FS::queue {
65     'svcnum' => $svcnum,
66     'job'    => "FS::part_export::textradius::textradius_$method",
67   };
68   $queue->insert(
69     $self->option('user')||'root',
70     $self->machine,
71     $self->option('users'),
72     @_,
73   ) or $queue;
74 }
75
76 sub textradius_insert { #subroutine, not method
77   my( $user, $host, $users, $username, @attributes ) = @_;
78
79   #silly arg processing
80   my($att, @check);
81   push @check, $att while @attributes && ($att=shift @attributes) ne '-';
82   my %check = @check;
83   my %reply = @attributes;
84
85   my $file = textradius_download($user, $host, $users);
86
87   eval "use RADIUS::UserFile;";
88   die $@ if $@;
89
90   my $userfile = new RADIUS::UserFile(
91     File        => $file,
92     Who         => [ $username ],
93     Check_Items => [ keys %check ],
94   ) or die "error parsing $file";
95
96   $userfile->remove($username);
97   $userfile->add(
98     Who        => $username,
99     Attributes => { %check, %reply },
100     Comment    => 'user added by Freeside',
101   ) or die "error adding to $file";
102
103   $userfile->update( Who => [ $username ] )
104     or die "error updating $file";
105
106   textradius_upload($user, $host, $users);
107
108 }
109
110 sub textradius_delete { #subroutine, not method
111   my( $user, $host, $users, $username ) = @_;
112
113   my $file = textradius_download($user, $host, $users);
114
115   eval "use RADIUS::UserFile;";
116   die $@ if $@;
117
118   my $userfile = new RADIUS::UserFile(
119     File        => $file,
120     Who         => [ $username ],
121   ) or die "error parsing $file";
122
123   $userfile->remove($username);
124
125   $userfile->update( Who => [ $username ] )
126     or die "error updating $file";
127
128   textradius_upload($user, $host, $users);
129 }
130
131 sub textradius_download {
132   my( $user, $host, $users ) = @_;
133
134   my $dir = $prefix. datasrc;
135   mkdir $dir, 0700 or die $! unless -d $dir;
136   $dir .= "/$host";
137   mkdir $dir, 0700 or die $! unless -d $dir;
138
139   my $dest = "$dir/users";
140
141   eval "use File::Rsync;";
142   die $@ if $@;
143   my $rsync = File::Rsync->new({ rsh => 'ssh' });
144
145   open(LOCK, "+>>$dest.lock")
146     and flock(LOCK,LOCK_EX)
147       or die "can't open $dest.lock: $!";
148
149   $rsync->exec( {
150     src  => "$user\@$host:$users",
151     dest => $dest,
152   } ); # true/false return value from exec is not working, alas
153   if ( $rsync->err ) {
154     die "error downloading $user\@$host:$users : ".
155         'exit status: '. $rsync->status. ', '.
156         'STDERR: '. join(" / ", $rsync->err). ', '.
157         'STDOUT: '. join(" / ", $rsync->out);
158   }
159
160   $dest;
161 }
162
163 sub textradius_upload {
164   my( $user, $host, $users ) = @_;
165
166   my $dir = $prefix. datasrc. "/$host";
167
168   eval "use File::Rsync;";
169   die $@ if $@;
170   my $rsync = File::Rsync->new({
171     rsh => 'ssh',
172     #dry_run => 1,
173   });
174   $rsync->exec( {
175     src  => "$dir/users",
176     dest => "$user\@$host:$users",
177   } ); # true/false return value from exec is not working, alas
178   if ( $rsync->err ) {
179     die "error uploading to $user\@$host:$users : ".
180         'exit status: '. $rsync->status. ', '.
181         'STDERR: '. join(" / ", $rsync->err). ', '.
182         'STDOUT: '. join(" / ", $rsync->out);
183   }
184
185   flock(LOCK,LOCK_UN);
186   close LOCK;
187
188 }
189
190 1;
191