RT# 83450 - fixed rateplan export
[freeside.git] / FS / FS / part_export / vpopmail.pm
1 package FS::part_export::vpopmail;
2
3 use vars qw(@ISA %info @saltset $exportdir);
4 use Fcntl qw(:flock);
5 use Tie::IxHash;
6 use File::Path;
7 use FS::UID qw( datasrc );
8 use FS::part_export;
9
10 @ISA = qw(FS::part_export);
11
12 tie my %options, 'Tie::IxHash',
13   #'machine' => { label=>'vpopmail machine', },
14   'dir'     => { label=>'directory', }, # ?more info? default?
15   'uid'     => { label=>'vpopmail uid' },
16   'gid'     => { label=>'vpopmail gid' },
17   'restart' => { label=> 'vpopmail restart command',
18                  default=> 'cd /home/vpopmail/domains; for domain in *; do /home/vpopmail/bin/vmkpasswd $domain; done; /var/qmail/bin/qmail-newu; killall -HUP qmail-send',
19                },
20 ;
21
22 %info = (
23   'svc'     => 'svc_acct',
24   'desc'    => 'Real-time export to vpopmail text files',
25   'options' => \%options,
26   'default_svc_class' => 'Email',
27   'notes'   => <<'END'
28 This export is currently unmaintained.  See shellcommands_withdomain for an
29 export that uses vpopmail CLI commands instead.<BR>
30 <BR>
31 Real time export to <a href="http://inter7.com/vpopmail/">vpopmail</a> text
32 files.  <a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
33 must be installed, and you will need to
34 <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">setup SSH for unattended operation</a>
35 to <b>vpopmail</b>@<i>export.host</i>. 
36 END
37 );
38
39 @saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
40
41 sub rebless { shift; }
42
43 sub _export_insert {
44   my($self, $svc_acct) = (shift, shift);
45   $self->vpopmail_queue( $svc_acct->svcnum, 'insert',
46     $svc_acct->username,
47     crypt($svc_acct->_password,$saltset[int(rand(64))].$saltset[int(rand(64))]),
48     $svc_acct->domain,
49     $svc_acct->quota,
50     $svc_acct->finger,
51   );
52 }
53
54 sub _export_replace {
55   my( $self, $new, $old ) = (shift, shift, shift);
56
57   my $cpassword = crypt(
58     $new->_password, $saltset[int(rand(64))].$saltset[int(rand(64))]
59   );
60
61   return "can't change username with vpopmail"
62     if $old->username ne $new->username;
63
64   #no.... if mail can't be preserved, better to disallow username changes
65   #if ($old->username ne $new->username || $old->domain ne $new->domain ) {
66   #  vpopmail_queue( $svc_acct->svcnum, 'delete', 
67   #    $old->username, $old->domain
68   #  );
69   #  vpopmail_queue( $svc_acct->svcnum, 'insert', 
70   #    $new->username,
71   #    $cpassword,
72   #    $new->domain,
73   #  );
74
75   return '' unless $old->_password ne $new->_password;
76
77   $self->vpopmail_queue( $new->svcnum, 'replace',
78     $new->username, $cpassword, $new->domain, $new->quota, $new->finger );
79 }
80
81 sub _export_delete {
82   my( $self, $svc_acct ) = (shift, shift);
83   $self->vpopmail_queue( $svc_acct->svcnum, 'delete',
84     $svc_acct->username, $svc_acct->domain );
85 }
86
87 #a good idea to queue anything that could fail or take any time
88 sub vpopmail_queue {
89   my( $self, $svcnum, $method ) = (shift, shift, shift);
90
91   my $exportdir = "%%%FREESIDE_EXPORT%%%/export." . datasrc;
92   mkdir $exportdir, 0700 or die $! unless -d $exportdir;
93   $exportdir .= "/vpopmail";
94   mkdir $exportdir, 0700 or die $! unless -d $exportdir;
95   $exportdir .= '/'. $self->machine;
96   mkdir $exportdir, 0700 or die $! unless -d $exportdir;
97   mkdir "$exportdir/domains", 0700 or die $! unless -d "$exportdir/domains";
98
99   my $queue = new FS::queue {
100     'svcnum' => $svcnum,
101     'job'    => "FS::part_export::vpopmail::vpopmail_$method",
102   };
103   $queue->insert(
104     $exportdir,
105     $self->machine,
106     $self->option('dir'),
107     $self->option('uid'),
108     $self->option('gid'),
109     $self->option('restart'),
110     @_
111   );
112 }
113
114 sub vpopmail_insert { #subroutine, not method
115   my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
116   my( $username, $password, $domain, $quota, $finger ) = @_;
117
118   mkdir "$exportdir/domains/$domain", 0700 or die $!
119     unless -d "$exportdir/domains/$domain";
120
121   (open(VPASSWD, ">>$exportdir/domains/$domain/vpasswd")
122     and flock(VPASSWD,LOCK_EX)
123   ) or die "can't open vpasswd file for $username\@$domain: ".
124            "$exportdir/domains/$domain/vpasswd: $!";
125   print VPASSWD join(":",
126     $username,
127     $password,
128     '1',
129     '0',
130     $finger,
131     "$dir/domains/$domain/$username",
132     $quota ? $quota.'S' : 'NOQUOTA',
133   ), "\n";
134
135   flock(VPASSWD,LOCK_UN);
136   close(VPASSWD);
137
138   for my $mkdir (
139     grep { ! -d $_ } map { "$exportdir/domains/$domain/$username$_" }
140         ( '', qw( /Maildir /Maildir/cur /Maildir/new /Maildir/tmp ) )
141   ) {
142     mkdir $mkdir, 0700 or die "can't mkdir $mkdir: $!";
143   }
144
145   vpopmail_sync( $exportdir, $machine, $dir, $uid, $gid, $restart );
146
147 }
148
149 sub vpopmail_replace { #subroutine, not method
150   my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
151   my( $username, $password, $domain, $quota, $finger ) = @_;
152   
153   (open(VPASSWD, "$exportdir/domains/$domain/vpasswd")
154     and flock(VPASSWD,LOCK_EX)
155   ) or die "can't open $exportdir/domains/$domain/vpasswd: $!";
156
157   open(VPASSWDTMP, ">$exportdir/domains/$domain/vpasswd.tmp")
158     or die "Can't open $exportdir/domains/$domain/vpasswd.tmp: $!";
159
160   while (<VPASSWD>) {
161     my ($mailbox, $pw, $vuid, $vgid, $vfinger, $vdir, $vquota, @rest) =
162       split(':', $_);
163     if ( $username ne $mailbox ) {
164       print VPASSWDTMP $_;
165       next
166     }
167     print VPASSWDTMP join (':',
168       $mailbox,
169       $password,
170       '1',
171       '0',
172       $finger,
173       "$dir/domains/$domain/$username", #$vdir
174       $quota ? $quota.'S' : 'NOQUOTA',
175     ), "\n";
176   }
177
178   close(VPASSWDTMP);
179
180   rename "$exportdir/domains/$domain/vpasswd.tmp", "$exportdir/domains/$domain/vpasswd"
181     or die "Can't rename $exportdir/domains/$domain/vpasswd.tmp: $!";
182
183   flock(VPASSWD,LOCK_UN);
184   close(VPASSWD);
185
186   vpopmail_sync( $exportdir, $machine, $dir, $uid, $gid, $restart );
187
188 }
189
190 sub vpopmail_delete { #subroutine, not method
191   my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
192   my( $username, $domain ) = @_;
193   
194   (open(VPASSWD, "$exportdir/domains/$domain/vpasswd")
195     and flock(VPASSWD,LOCK_EX)
196   ) or die "can't open $exportdir/domains/$domain/vpasswd: $!";
197
198   open(VPASSWDTMP, ">$exportdir/domains/$domain/vpasswd.tmp")
199     or die "Can't open $exportdir/domains/$domain/vpasswd.tmp: $!";
200
201   while (<VPASSWD>) {
202     my ($mailbox, $rest) = split(':', $_);
203     print VPASSWDTMP $_ unless $username eq $mailbox;
204   }
205
206   close(VPASSWDTMP);
207
208   rename "$exportdir/domains/$domain/vpasswd.tmp",
209          "$exportdir/domains/$domain/vpasswd"
210     or die "Can't rename $exportdir/domains/$domain/vpasswd.tmp: $!";
211
212   flock(VPASSWD,LOCK_UN);
213   close(VPASSWD);
214
215   rmtree "$exportdir/domains/$domain/$username"
216     or die "can't rmtree $exportdir/domains/$domain/$username: $!";
217
218   vpopmail_sync( $exportdir, $machine, $dir, $uid, $gid, $restart );
219 }
220
221 sub vpopmail_sync {
222   my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
223   
224   chdir $exportdir;
225 #  my @args = ( $rsync, "-rlpt", "-e", $ssh, "domains/",
226 #               "vpopmail\@$machine:$dir/domains/"  );
227 #  system {$args[0]} @args;
228
229   eval "use File::Rsync;";
230   die $@ if $@;
231
232   my $rsync = File::Rsync->new({ rsh => 'ssh' });
233
234   $rsync->exec( {
235     recursive => 1,
236     perms     => 1,
237     times     => 1,
238     src       => "$exportdir/domains/",
239     dest      => "vpopmail\@$machine:$dir/domains/",
240   } ); # true/false return value from exec is not working, alas
241   if ( $rsync->err ) {
242     die "error uploading to vpopmail\@$machine:$dir/domains/ : ".
243         'exit status: '. $rsync->status. ', '.
244         'STDERR: '. join(" / ", $rsync->err). ', '.
245         'STDOUT: '. join(" / ", $rsync->out);
246   }
247
248   eval "use Net::SSH qw(ssh);";
249   die $@ if $@;
250
251   ssh("vpopmail\@$machine", $restart) if $restart;
252 }
253
254 1;
255