merging vpopmail support branch
authorjeff <jeff>
Sun, 12 Aug 2001 19:41:26 +0000 (19:41 +0000)
committerjeff <jeff>
Sun, 12 Aug 2001 19:41:26 +0000 (19:41 +0000)
FS/FS/svc_acct.pm
FS/FS/svc_domain.pm
FS/FS/svc_forward.pm [new file with mode: 0644]
bin/fs-migrate-svc_acct_sm [new file with mode: 0755]
bin/fs-setup
bin/svc_acct.export
bin/svc_acct_sm.export [deleted file]
htdocs/docs/config.html
htdocs/docs/upgrade8.html [new file with mode: 0644]
httemplate/docs/config.html
httemplate/docs/upgrade8.html

index 4b7ec98..83263b0 100644 (file)
@@ -82,6 +82,10 @@ FS::svc_acct - Object methods for svc_acct records
 
   %hash = $record->radius;
 
+  %hash = $record->radius_reply;
+
+  %hash = $record->radius_check;
+
 =head1 DESCRIPTION
 
 An FS::svc_acct object represents an account.  FS::svc_acct inherits from
@@ -113,6 +117,8 @@ FS::svc_Common.  The following fields are currently supported:
 
 =item radius_I<Radius_Attribute> - I<Radius-Attribute>
 
+=item domsvc - service number of svc_domain with which to associate
+
 =back
 
 =head1 METHODS
@@ -169,7 +175,9 @@ sub insert {
   return $error if $error;
 
   return "Username ". $self->username. " in use"
-    if qsearchs( 'svc_acct', { 'username' => $self->username } );
+    if qsearchs( 'svc_acct', { 'username' => $self->username,
+                               'domsvc'   => $self->domsvc,
+                             } );
 
   my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
   return "Unknown svcpart" unless $part_svc;
@@ -544,7 +552,7 @@ sub radius_check {
 
 =head1 VERSION
 
-$Id: svc_acct.pm,v 1.19 2001-07-30 07:34:41 ivan Exp $
+$Id: svc_acct.pm,v 1.20 2001-08-12 19:41:24 jeff Exp $
 
 =head1 BUGS
 
index 01aa21a..fcfcfa9 100644 (file)
@@ -336,6 +336,9 @@ sub check {
   $recref->{action} =~ /^(M|N)$/ or return "Illegal action";
   $recref->{action} = $1;
 
+  my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $recref->{catchall} } );
+  return "Unknown catchall" unless $svc_acct || ! $recref->{catchall};
+
   $self->ut_textn('purpose');
 
 }
@@ -478,7 +481,7 @@ sub submit_internic {
 
 =head1 VERSION
 
-$Id: svc_domain.pm,v 1.12 2001-06-03 17:22:52 ivan Exp $
+$Id: svc_domain.pm,v 1.13 2001-08-12 19:41:24 jeff Exp $
 
 =head1 BUGS
 
diff --git a/FS/FS/svc_forward.pm b/FS/FS/svc_forward.pm
new file mode 100644 (file)
index 0000000..5264a60
--- /dev/null
@@ -0,0 +1,255 @@
+package FS::svc_forward;
+
+use strict;
+use vars qw( @ISA $nossh_hack $conf $shellmachine @qmailmachines @vpopmailmachines);
+use FS::Record qw( fields qsearch qsearchs );
+use FS::svc_Common;
+use FS::cust_svc;
+use Net::SSH qw(ssh);
+use FS::Conf;
+use FS::svc_acct;
+use FS::svc_domain;
+
+@ISA = qw( FS::svc_Common );
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::svc_forward'} = sub { 
+  $conf = new FS::Conf;
+  $shellmachine = $conf->exists('qmailmachines')
+                  ? $conf->config('shellmachine')
+                  : '';
+  if ( $conf->exists('vpopmailmachines') ) {
+    @vpopmailmachines = $conf->config('vpopmailmachines');
+  }
+};
+
+=head1 NAME
+
+FS::svc_forward - Object methods for svc_forward records
+
+=head1 SYNOPSIS
+
+  use FS::svc_forward;
+
+  $record = new FS::svc_forward \%hash;
+  $record = new FS::svc_forward { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+  $error = $record->suspend;
+
+  $error = $record->unsuspend;
+
+  $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::svc_forward object represents a mail forwarding alias.  FS::svc_forward
+inherits from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item svcnum - primary key (assigned automatcially for new accounts)
+
+=item srcsvc - svcnum of the source of the forward (see L<FS::svc_acct>)
+
+=item dstsvc - svcnum of the destination of the forward (see L<FS::svc_acct>)
+
+=item dst - foreign destination (email address) - forward not local to freeside
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new mail forwarding alias.  To add the mail forwarding alias to the
+database, see L<"insert">.
+
+=cut
+
+sub table { 'svc_forward'; }
+
+=item insert
+
+Adds this mail forwarding alias to the database.  If there is an error, returns
+the error, otherwise returns false.
+
+The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be 
+defined.  An FS::cust_svc record will be created and inserted.
+
+If the configuration values (see L<FS::Conf>) vpopmailmachines exist, then
+the command:
+
+  [ -d /home/vpopmail/$vdomain/$source ] || {
+    echo "$destination" >> /home/vpopmail/$vdomain/$source/.$qmail
+    chown $vpopuid:$vpopgid /home/vpopmail/$vdomain/$source/.$qmail
+  }
+
+is executed on each vpopmailmachine via ssh (see L<dot-qmail/"EXTENSION ADDRESSES">).
+This behaviour can be surpressed by setting $FS::svc_forward::nossh_hack true.
+
+=cut
+
+sub insert {
+  my $self = shift;
+  my $error;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  $error=$self->check;
+  return $error if $error;
+
+  $error = $self->SUPER::insert;
+  return $error if $error;
+
+  my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $self->srcsvc } );
+  my $svc_domain = qsearchs( 'svc_domain', { 'svcnum' => $svc_acct->domsvc } );
+  my $source = $svc_acct->username . $svc_domain->domain;
+  my $destination;
+  if ($self->dstdvc) {
+    my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $self->dstsvc } );
+    my $svc_domain = qsearchs( 'svc_domain', { 'svcnum' => $svc_acct->domsvc } );
+    $destination = $svc_acct->username . $svc_domain->domain;
+  } else {
+    $destination = $self->dst;
+  }
+    
+  my $vdomain = $svc_acct->domain;
+
+  foreach my $vpopmailmachine ( @vpopmailmachines ) {
+    my ($machine, $vpopdir, $vpopuid, $vpopgid) = split (/\s+/, $vpopmailmachine);
+
+    ssh("root\@$machine","[ -d $vpopdir/$vdomain/$source ] || { echo $destination >> $vpopdir/$vdomain/$source/.qmail; chown $vpopuid:$vpopgid $vpopdir/$vdomain/$source/.qmail; }")  
+      if ( ! $nossh_hack && $machine);
+  }
+
+  ''; #no error
+
+}
+
+=item delete
+
+Deletes this mail forwarding alias from the database.  If there is an error,
+returns the error, otherwise returns false.
+
+The corresponding FS::cust_svc record will be deleted as well.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+  my ( $new, $old ) = ( shift, shift );
+  my $error;
+
+ $new->SUPER::replace($old);
+
+}
+
+=item suspend
+
+Just returns false (no error) for now.
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Just returns false (no error) for now.
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Just returns false (no error) for now.
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid mail forwarding alias.  If there
+is an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+Sets any fixed values; see L<FS::part_svc>.
+
+=cut
+
+sub check {
+  my $self = shift;
+  my $error;
+
+  my $x = $self->setfixed;
+  return $x unless ref($x);
+  my $part_svc = $x;
+
+  my($recref) = $self->hashref;
+
+  $recref->{srcsvc} =~ /^(\d+)$/ or return "Illegal srcsvc";
+  $recref->{srcsvc} = $1;
+  my($svc_acct);
+  return "Unknown srcsvc" unless
+    $svc_acct=qsearchs('svc_acct',{'svcnum'=> $recref->{srcsvc} } );
+
+  return "Illegal use of dstsvc and dst" if
+    ($recref->{dstsvc} && $recref->{dst});
+
+  return "Illegal use of dstsvc and dst" if
+    (! $recref->{dstsvc} && ! $recref->{dst});
+
+  $recref->{dstsvc} =~ /^(\d+)$/ or return "Illegal dstsvc";
+  $recref->{dstsvc} = $1;
+
+  if ($recref->{dstsvc}) {
+    my($svc_acct);
+    return "Unknown dstsvc" unless
+      my $svc_domain=qsearchs('svc_acct',{'svcnum'=> $recref->{dstsvc} } );
+  }
+
+  if ($recref->{dst}) {
+    $recref->{dst} =~ /^(\w\.\-]+)\@(([\w\.\-]+\.)+\w+)$/
+       or return "Illegal dst";
+    $recref->{dst} = $1;
+  }
+
+  ''; #no error
+}
+
+=back
+
+=head1 VERSION
+
+$Id: svc_forward.pm,v 1.2 2001-08-12 19:41:24 jeff Exp $
+
+=head1 BUGS
+
+The remote commands should be configurable.
+
+The $recref stuff in sub check should be cleaned up.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::Conf>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>,
+L<FS::svc_acct>, L<FS::svc_domain>, L<Net::SSH>, L<ssh>, L<dot-qmail>,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/bin/fs-migrate-svc_acct_sm b/bin/fs-migrate-svc_acct_sm
new file mode 100755 (executable)
index 0000000..d0d4a94
--- /dev/null
@@ -0,0 +1,242 @@
+#!/usr/bin/perl -Tw
+#
+# $Id: fs-migrate-svc_acct_sm,v 1.2 2001-08-12 19:41:25 jeff Exp $
+#
+# jeff@cmh.net 01-Jul-20
+#
+# $Log: fs-migrate-svc_acct_sm,v $
+# Revision 1.2  2001-08-12 19:41:25  jeff
+# merging vpopmail support branch
+#
+# Revision 1.1.2.1  2001/08/08 17:45:35  jeff
+# initial vpopmail support
+#
+#
+#
+#   Initial vpopmail changes
+#
+
+#to delay loading dbdef until we're ready
+#BEGIN { $FS::Record::setup_hack = 1; }
+
+use strict;
+use Term::Query qw(query);
+#use DBI;
+#use DBIx::DBSchema;
+#use DBIx::DBSchema::Table;
+#use DBIx::DBSchema::Column;
+#use DBIx::DBSchema::ColGroup::Unique;
+#use DBIx::DBSchema::ColGroup::Index;
+use FS::Conf;
+use FS::UID qw(adminsuidsetup datasrc checkeuid getsecrets);
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_domain;
+use FS::svc_forward;
+use vars qw( $conf $old_default_domain %part_domain_svc %part_acct_svc %part_forward_svc $svc_acct $svc_acct_sm $error);
+
+die "Not running uid freeside!" unless checkeuid();
+
+my $user = shift or die &usage;
+getsecrets($user);
+
+$conf = new FS::Conf;
+$old_default_domain = $conf->config('domain');
+
+#needs to match FS::Record
+#my($dbdef_file) = "/usr/local/etc/freeside/dbdef.". datasrc;
+
+###
+# This section would be the appropriate place to manipulate
+# the schema & tables.
+###
+
+##  we need to add the domsvc to svc_acct
+##  we must add a svc_forward record....
+##  I am thinking that the fields  svcnum (int), destsvc (int), and
+##  dest (varchar (80))  are appropriate, with destsvc/dest an either/or
+##  much in the spirit of cust_main_invoice
+
+###
+# massage the data
+###
+
+my($dbh)=adminsuidsetup $user;
+
+$|=1;
+
+$FS::svc_acct::nossh_hack = 1;
+$FS::svc_forward::nossh_hack = 1;
+$FS::svc_domain::whois_hack = 1;
+
+%part_domain_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_domain'});
+%part_acct_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_acct'});
+%part_forward_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_forward'});
+
+die "No services with svcdb svc_domain!\n" unless %part_domain_svc;
+die "No services with svcdb svc_acct!\n" unless %part_acct_svc;
+die "No services with svcdb svc_forward!\n" unless %part_forward_svc;
+
+my($svc_domain) = qsearchs('svc_domain', { 'domain' => $old_default_domain });
+if (! $svc_domain || $svc_domain->domain != $old_default_domain) {
+   print <<EOF;
+
+Your database currently does not contain a svc_domain record for the
+domain $old_default_domain.  Would you like me to add one for you?
+EOF
+
+   my($response)=scalar(<STDIN>);
+   chop $response;
+   if ($response =~ /^[yY]/) {
+      print "\n\n", &menu_domain_svc, "\n", <<END;
+I need to create new domain accounts.  Which service shall I use for that?
+END
+      my($domain_svcpart)=&getdomainpart;
+
+      $svc_domain = new FS::svc_domain {
+        'domain' => $old_default_domain,
+        'svcpart' => $domain_svcpart,
+        'action' => 'M',
+       };
+#      $error=$svc_domain->insert && die "Error adding domain $old_default_domain: $error";
+      $error=$svc_domain->insert;
+      die "Error adding domain $old_default_domain: $error" if $error;
+   }else{
+      print <<EOF;
+
+  This program cannot function properly until a svc_domain record matching
+your conf_dir/domain file exists.
+EOF
+
+      exit 1;
+   }
+}
+
+print "\n\n", &menu_acct_svc, "\n", <<END;
+I may need to create some new pop accounts and set up forwarding to them
+for some users.  Which service shall I use for that?
+END
+my($pop_svcpart)=&getacctpart;
+
+print "\n\n", &menu_forward_svc, "\n", <<END;
+I may need to create some new forwarding for some users.  Which service
+shall I use for that?
+END
+my($forward_svcpart)=&getforwardpart;
+
+sub menu_domain_svc {
+  ( join "\n", map "$_: ".$part_domain_svc{$_}->svc, sort keys %part_domain_svc ). "\n";
+}
+sub menu_acct_svc {
+  ( join "\n", map "$_: ".$part_acct_svc{$_}->svc, sort keys %part_acct_svc ). "\n";
+}
+sub menu_forward_svc {
+  ( join "\n", map "$_: ".$part_forward_svc{$_}->svc, sort keys %part_forward_svc ). "\n";
+}
+sub getdomainpart {
+  $^W=0; # Term::Query isn't -w-safe
+  my $return = query "Enter part number:", 'irk', [ keys %part_domain_svc ];
+  $^W=1;
+  $return;
+}
+sub getacctpart {
+  $^W=0; # Term::Query isn't -w-safe
+  my $return = query "Enter part number:", 'irk', [ keys %part_acct_svc ];
+  $^W=1;
+  $return;
+}
+sub getforwardpart {
+  $^W=0; # Term::Query isn't -w-safe
+  my $return = query "Enter part number:", 'irk', [ keys %part_forward_svc ];
+  $^W=1;
+  $return;
+}
+
+
+#migrate data
+
+my(@svc_accts) = qsearch('svc_acct', {});
+foreach $svc_acct (@svc_accts) {
+  my(@svc_acct_sms) = qsearch('svc_acct_sm', {
+      domuid => $svc_acct->getfield('uid'),
+      }
+    );
+
+  #  Ok.. we've got the svc_acct record, and an array of svc_acct_sm's
+  #  What do we do from here?
+
+  #  The intuitive:
+  #    plop the svc_acct into the 'default domain'
+  #    and then represent the svc_acct_sm's with svc_forwards
+  #    they can be gussied up manually, later
+  #
+  #  Perhaps better:
+  #    when no svc_acct_sm exists, place svc_acct in 'default domain'
+  #    when one svc_acct_sm exists, place svc_acct in corresponding
+  #      domain & possibly create a svc_forward in 'default domain'
+  #    when multiple svc_acct_sm's exists (in different domains) we'd
+  #    better use the 'intuitive' approach.
+  #
+  #  Specific way:
+  #    as 'perhaps better,' but we may be able to guess which domain
+  #    is correct by comparing the svcnum of domains to the username
+  #    of the svc_acct
+  #
+
+  # The intuitive way:
+
+  my $def_acct = new FS::svc_acct ( { $svc_acct->hash } );
+  $def_acct->setfield('domsvc' => $svc_domain->getfield('svcnum'));
+  $error = $def_acct->replace($svc_acct);
+  die "Error replacing svc_acct for " . $def_acct->username . " : $error" if $error;
+
+  foreach $svc_acct_sm (@svc_acct_sms) {
+
+    my($domrec)=qsearchs('svc_domain', {
+      svcnum => $svc_acct_sm->getfield('domsvc'),
+    }) || die  "svc_acct_sm references invalid domsvc $svc_acct_sm->getfield('domsvc')\n";
+
+    if ($svc_acct_sm->getfield('domuser') =~ /^\*$/) {
+      
+      my($newdom) = new FS::svc_domain ( { $domrec->hash } );
+      $newdom->setfield('catchall', $svc_acct->svcnum);
+      $newdom->setfield('action', "M");
+      $error = $newdom->replace($domrec);
+      die "Error replacing svc_domain for (anything)@" . $domrec->domain . " : $error" if $error;
+
+    } else {
+
+      my($newacct) = new FS::svc_acct {
+        'svcpart'  => $pop_svcpart,
+        'username' => $svc_acct_sm->getfield('domuser'),
+        'domsvc'   => $svc_acct_sm->getfield('domsvc'),
+        'dir'      => '/dev/null',
+      };
+      $error = $newacct->insert;
+      die "Error adding svc_acct for " . $newacct->username . " : $error" if $error;
+     
+      my($newforward) = new FS::svc_forward {
+        'svcpart'  => $forward_svcpart, 
+        'srcsvc'   => $newacct->getfield('svcnum'),
+        'dstsvc'   => $def_acct->getfield('svcnum'),
+      };
+      $error = $newforward->insert;
+      die "Error adding svc_forward for " . $newacct->username ." : $error" if $error;
+    }
+     
+    $error = $svc_acct_sm->delete;
+    die "Error deleting svc_acct_sm for " . $svc_acct_sm->domuser ." : $error" if $error;
+
+  };
+
+};
+
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+print "svc_acct_sm records sucessfully migrated\n";
+
+sub usage {
+  die "Usage:\n  fs-migrate-svc_acct_sm user\n"; 
+}
+
index 314a7c2..a3e067b 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -Tw
 #
-# $Id: fs-setup,v 1.40 2001-08-11 05:53:42 ivan Exp $
+# $Id: fs-setup,v 1.41 2001-08-12 19:41:25 jeff Exp $
 #
 # ivan@sisd.com 97-nov-8,9
 #
 # fix radius attributes ivan@sisd.com 98-sep-27
 #
 # $Log: fs-setup,v $
-# Revision 1.40  2001-08-11 05:53:42  ivan
+# Revision 1.41  2001-08-12 19:41:25  jeff
+# merging vpopmail support branch
+#
+# Revision 1.40  2001/08/11 05:53:42  ivan
 # add comments field
 #
 # Revision 1.39  2001/07/30 07:42:39  ivan
@@ -271,7 +274,7 @@ my($part_svc)=$dbdef->table('part_svc');
 #because of svc_acct_pop
 #foreach (grep /^svc_/, $dbdef->tables) { 
 #foreach (qw(svc_acct svc_acct_sm svc_charge svc_domain svc_wo)) {
-foreach (qw(svc_acct svc_acct_sm svc_domain svc_www)) {
+foreach (qw(svc_acct svc_acct_sm svc_domain svc_forward svc_www)) {
   my($table)=$dbdef->table($_);
   my($col);
   foreach $col ( $table->columns ) {
@@ -707,10 +710,11 @@ sub tables_hash_hack {
         'quota',     'varchar',   'NULL',   $char_d,
         'slipip',    'varchar',   'NULL',   15, #four TINYINTs, bah.
         'seconds',   'int', 'NULL',   '', #uhhhh
+        'domsvc',    'int', '',   '',
       ],
       'primary_key' => 'svcnum',
       'unique' => [ [] ],
-      'index' => [ ['username'] ],
+      'index' => [ ['username'], ['domsvc'] ],
     },
 
     'svc_acct_sm' => {
@@ -739,6 +743,7 @@ sub tables_hash_hack {
       'columns' => [
         'svcnum',    'int',    '',   '',
         'domain',    'varchar',    '',   $char_d,
+        'catchall',  'int',    '',    '',
       ],
       'primary_key' => 'svcnum',
       'unique' => [ ['domain'] ],
@@ -759,6 +764,18 @@ sub tables_hash_hack {
       'index'       => [ ['svcnum'] ],
     },
 
+    'svc_forward' => {
+      'columns' => [
+        'svcnum',   'int',    '',  '',
+        'srcsvc',   'int',    '',  '',
+        'dstsvc',   'int',    '',  '',
+        'dst',      'varchar',    'NULL',  $char_d,
+      ],
+      'primary_key' => 'svcnum',
+      'unique'      => [ [] ],
+      'index'       => [ ['srcsvc'], ['dstsvc'] ],
+    },
+
     'svc_www' => {
       'columns' => [
         'svcnum',   'int',    '',  '',
index 7e92c61..a7a21b3 100755 (executable)
@@ -1,99 +1,42 @@
 #!/usr/bin/perl -w
 #
-# $Id: svc_acct.export,v 1.21 2001-07-30 06:07:46 ivan Exp $
+# $Id: svc_acct.export,v 1.22 2001-08-12 19:41:25 jeff Exp $
 #
-# Create and export password files: passwd, passwd.adjunct, shadow,
-# acp_passwd, acp_userinfo, acp_dialup, users
+# Create and export password, radius and vpopmail password files:
+# passwd, passwd.adjunct, shadow, acp_passwd, acp_userinfo, acp_dialup
+# users/assign, domains/vdomain/vpasswd
+# Also export sendmail and qmail config files.
 #
-# ivan@voicenet.com late august/september 96
-# (the password encryption bits were from melody)
 #
-# use a temporary copy of svc_acct to minimize lock time on the real file,
-# and skip blank entries.
-#
-# ivan@voicenet.com 96-Oct-6
-#
-# change users / acp_dialup file formats
-# ivan@voicenet.com 97-jan-28-31
-#
-# change priority (after copies) to 19, not 10
-# ivan@voicenet.com 97-feb-5
-#
-# added exit if stuff is already locked 97-apr-15
-#
-# rewrite ivan@sisd.com 98-mar-9
-#
-# Changed 'password' to '_password' because Pg6.3 reserves this word
-# Added code to create a FreeBSD style master.passwd file
-#   bmccane@maxbaud.net 98-Apr-3
-#
-# don't export non-root 0 UID's, even if they get put in the database
-# ivan@sisd.com 98-jul-14
-#
-# Uses Idle_Timeout, Port_Limit, Framed_Netmask and Framed_Route if they
-# exist; need some way to support arbitrary radius fields.  also 
-# /var/spool/freeside/conf/ ivan@sisd.com 98-jul-26, aug-9
-#
-# OOPS!  added arbitrary radius fields (pry 98-aug-16) but forgot to say so.
-# ivan@sisd.com 98-sep-18
-# 
 # $Log: svc_acct.export,v $
-# Revision 1.21  2001-07-30 06:07:46  ivan
-# allow !! for locked accounts instead of changing to *SUSPENDED*
-#
-# Revision 1.20  2001/06/20 08:33:42  ivan
-# >     Use of uninitialized value in concatenation (.) at svc_acct.export line
-# > 276.
-#
-# Revision 1.19  2001/05/08 10:44:17  ivan
-# fix for OO Net::SCP
-#
-# Revision 1.18  2001/04/22 01:56:15  ivan
-# get rid of FS::SSH.pm (became Net::SSH and Net::SCP on CPAN)
-#
-# Revision 1.17  2001/02/21 23:48:19  ivan
-# add icradius_secrets config file to export to a non-Freeside MySQL database for
-# ICRADIUS
-#
-# Revision 1.16  2000/07/06 13:23:29  ivan
-# tyop
+# Revision 1.22  2001-08-12 19:41:25  jeff
+# merging vpopmail support branch
 #
-# Revision 1.15  2000/07/06 08:57:28  ivan
-# support for radius check attributes (except importing).  poorly documented.
-#
-# Revision 1.14  2000/06/29 15:01:25  ivan
-# another silly typo in svc_acct.export
-#
-# Revision 1.13  2000/06/28 12:37:28  ivan
-# add support for config option textradiusprepend
-#
-# Revision 1.12  2000/06/15 14:07:02  ivan
-# added ICRADIUS radreply table support, courtesy of Kenny Elliott
-#
-# Revision 1.11  2000/03/06 16:00:39  ivan
-# sync up with working versoin
-#
-# Revision 1.2  1998/12/10 07:23:15  ivan
-# use FS::Conf, need user (for datasrc)
 #
 
 use strict;
 use vars qw($conf);
+use Archive::Tar;
 use Fcntl qw(:flock);
+use File::Path;
 use IO::Handle;
-use DBI;
 use FS::Conf;
 use Net::SSH qw(ssh);
 use Net::SCP qw(scp);
 use FS::UID qw(adminsuidsetup datasrc dbh);
-use FS::Record qw(qsearch fields);
+use FS::Record qw(qsearch qsearchs fields);
 use FS::svc_acct;
+use FS::svc_domain;
+use FS::svc_forward;
 
 my $user = shift or die &usage;
 adminsuidsetup $user;
 
 $conf = new FS::Conf;
 
+my $userpolicy = $conf->config('username_policy')
+  if $conf->exists('username_policy');
+
 my @shellmachines = $conf->config('shellmachines')
   if $conf->exists('shellmachines');
 
@@ -130,6 +73,29 @@ my $textradiusprepend =
     ? $conf->config('textradiusprepend')
     : '';
 
+my @vpopmailmachines = $conf->config('vpopmailmachines')
+  if $conf->exists('vpopmailmachines');
+
+my ($machine, $vpopdir, $vpopuid, $vpopgid) = split (/\s+/, $vpopmailmachines[0]);
+
+my($shellmachine, @qmailmachines);
+if ( $conf->exists('qmailmachines') ) {
+  $shellmachine = $conf->config('shellmachine');
+  @qmailmachines = $conf->config('qmailmachines');
+}
+
+my(@sendmailmachines, $sendmailconfigpath, $sendmailrestart);
+if ( $conf->exists('sendmailmachines') ) {
+  @sendmailmachines = $conf->config('sendmailmachines');
+  $sendmailconfigpath = $conf->config('sendmailconfigpath') || '/etc';
+  $sendmailrestart = $conf->config('sendmailrestart');
+}
+
+my $mydomain = $conf->config('domain') if $conf->exists('domain');
+
+
+
+
 my(@saltset)= ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
 require 5.004; #srand(time|$$);
 
@@ -142,42 +108,81 @@ unless ( flock(EXPORT,LOCK_EX|LOCK_NB) ) {
   seek(EXPORT,0,0);
   my($pid)=<EXPORT>;
   chop($pid);
-  #no reason to start loct of blocking processes
+  #no reason to start lots of blocking processes
   die "Is another export process running under pid $pid?\n";
 }
 seek(EXPORT,0,0);
 print EXPORT $$,"\n";
 
-my(@svc_acct)=qsearch('svc_acct',{});
+my(@svc_domain)=qsearch('svc_domain',{});
 
 ( open(MASTER,">$spooldir/master.passwd")
-  and flock(MASTER,LOCK_EX|LOCK_NB)
-) or die "Can't open $spooldir/master.passwd: $!";
+  and flock(MASTER,LOCK_EX|LOCK_NB)  
+) or die "Can't open $spooldir/.master.passwd: $!";
 ( open(PASSWD,">$spooldir/passwd")
   and flock(PASSWD,LOCK_EX|LOCK_NB)  
 ) or die "Can't open $spooldir/passwd: $!";
 ( open(SHADOW,">$spooldir/shadow")
-  and flock(SHADOW,LOCK_EX|LOCK_NB) 
+  and flock(SHADOW,LOCK_EX|LOCK_NB)  
 ) or die "Can't open $spooldir/shadow: $!";
-( open(ACP_PASSWD,">$spooldir/acp_passwd") 
-  and flock (ACP_PASSWD,LOCK_EX|LOCK_NB)
+( open(ACP_PASSWD,">$spooldir/acp_passwd")
+  and flock(ACP_PASSWD,LOCK_EX|LOCK_NB)  
 ) or die "Can't open $spooldir/acp_passwd: $!";
-( open (ACP_DIALUP,">$spooldir/acp_dialup")
-  and flock(ACP_DIALUP,LOCK_EX|LOCK_NB)
+( open(ACP_DIALUP,">$spooldir/acp_dialup")
+  and flock(ACP_DIALUP,LOCK_EX|LOCK_NB)  
 ) or die "Can't open $spooldir/acp_dialup: $!";
-( open (USERS,">$spooldir/users")
-  and flock(USERS,LOCK_EX|LOCK_NB)
+( open(USERS,">$spooldir/users")
+  and flock(USERS,LOCK_EX|LOCK_NB)  
 ) or die "Can't open $spooldir/users: $!";
 
+( open(ASSIGN,">$spooldir/assign")
+  and flock(ASSIGN,LOCK_EX|LOCK_NB)  
+) or die "Can't open $spooldir/assign: $!";
+( open(RCPTHOSTS,">$spooldir/rcpthosts")
+  and flock(RCPTHOSTS,LOCK_EX|LOCK_NB) 
+) or die "Can't open $spooldir/rcpthosts: $!";
+( open(VPOPRCPTHOSTS,">$spooldir/vpoprcpthosts")
+  and flock(VPOPRCPTHOSTS,LOCK_EX|LOCK_NB) 
+) or die "Can't open $spooldir/rcpthosts: $!";
+( open(RECIPIENTMAP,">$spooldir/recipientmap") 
+  and flock(RECIPIENTMAP,LOCK_EX|LOCK_NB) 
+) or die "Can't open $spooldir/recipientmap: $!";
+( open(VIRTUALDOMAINS,">$spooldir/virtualdomains") 
+  and flock(VIRTUALDOMAINS,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/virtualdomains: $!";
+( open(VPOPVIRTUALDOMAINS,">$spooldir/vpopvirtualdomains") 
+  and flock(VPOPVIRTUALDOMAINS,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/virtualdomains: $!";
+( open(VIRTUSERTABLE,">$spooldir/virtusertable")
+  and flock(VIRTUSERTABLE,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/virtusertable: $!";
+( open(SENDMAIL_CW,">$spooldir/sendmail.cw")
+  and flock(SENDMAIL_CW,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/sendmail.cw: $!";
+
+
+
 chmod 0644, "$spooldir/passwd",
             "$spooldir/acp_dialup",
+            "$spooldir/assign",
+            "$spooldir/sendmail.cw",
+            "$spooldir/virtusertable",
+            "$spooldir/rcpthosts",
+            "$spooldir/vpoprcpthosts",
+            "$spooldir/recipientmap",
+            "$spooldir/virtualdomains",
+            "$spooldir/vpopvirtualdomains",
+
 ;
 chmod 0600, "$spooldir/master.passwd",
-           "$spooldir/acp_passwd",
+            "$spooldir/acp_passwd",
             "$spooldir/shadow",
             "$spooldir/users",
 ;
 
+rmtree"$spooldir/domains", 0, 1;
+mkdir "$spooldir/domains", 0700;
+
 if ( $icradiusmachines ) {
   my $sth = $icradius_dbh->prepare("DELETE FROM radcheck");
   $sth->execute or die "Can't reset radcheck table: ". $sth->errstr;
@@ -187,168 +192,321 @@ if ( $icradiusmachines ) {
 
 setpriority(0,0,10);
 
-my($svc_acct);
-foreach $svc_acct (@svc_acct) {
-
-  my($password)=$svc_acct->getfield('_password');
-  my($cpassword,$rpassword);
-  if ( ( length($password) <= 8 )
-       && ( $password ne '*' )
-       && ( $password ne '!!' )
-       && ( $password ne '' )
-     ) {
-    $cpassword=crypt($password,
-                     $saltset[int(rand(64))].$saltset[int(rand(64))]
-    );
-    $rpassword=$password;
-  } else {
-    $cpassword=$password;
-    $rpassword='UNIX';
-  }
-
-  if ( $svc_acct->uid  =~ /^(\d+)$/ ) {
-
-    die "Non-root user ". $svc_acct->username. " has 0 UID!"
-      if $svc_acct->uid == 0 && $svc_acct->username ne 'root';
+my %usernames;  ## this hack helps keep the passwd files sane
+my @sendmail;
+
+my $svc_domain;
+foreach $svc_domain (sort {$a->domain cmp $b->domain} @svc_domain) {
+
+  my($domain)=$svc_domain->domain;
+  print RCPTHOSTS "$domain\n.$domain\n";
+  print VPOPRCPTHOSTS "$domain\n";
+  print SENDMAIL_CW "$domain\n";
+
+  ###
+  # FORMAT OF THE ASSIGN/USERS FILE HERE
+  print ASSIGN join(":",
+    "+" . $domain . "-",
+    $domain,
+    $vpopuid,
+    $vpopgid,
+    $vpopdir . "/domains/" . $domain,
+    "-",
+    "",
+    "",
+  ), "\n";
+
+  (mkdir "$spooldir/domains/" . $domain, 0700)
+    or die "Can't create $spooldir/domains/" . $domain .": $!";
+
+  ( open(QMAILDEFAULT,">$spooldir/domains/" . $domain . "/.qmail-default")
+    and flock(QMAILDEFAULT,LOCK_EX|LOCK_NB)  
+  ) or die "Can't open $spooldir/domains/" . $domain . "/.qmail-default: $!";
+
+  ( open(VPASSWD,">$spooldir/domains/" . $domain . "/vpasswd")
+    and flock(VPASSWD,LOCK_EX|LOCK_NB)  
+  ) or die "Can't open $spooldir/domains/" . $domain . "/vpasswd: $!";
+
+  my ($svc_acct);
+
+  if ($svc_domain->catchall) {
+    $svc_acct = qsearchs('svc_acct', {'svcnum' => $svc_domain->catchall});
+    die "Cannot find catchall account for domain $domain\n" unless $svc_acct;
+
+    my $username = $svc_acct->username;
+    push @sendmail, "\@$domain\t$username\n";
+    print VIRTUALDOMAINS "$domain:$username-$domain\n",
+                         ".$domain:$username-$domain\n",
+    ;
 
     ###
-    # FORMAT OF FreeBSD MASTER PASSWD FILE HERE
-    print MASTER join(":",
-      $svc_acct->username,             # User name
-      $cpassword,                      # Encrypted password
-      $svc_acct->uid,                  # User ID
-      $svc_acct->gid,                  # Group ID
-      "",                              # Login Class
-      "0",                             # Password Change Time
-      "0",                             # Password Expiration Time
-      $svc_acct->finger,               # Users name
-      $svc_acct->dir,                  # Users home directory
-      $svc_acct->shell,                        # shell
-    ), "\n" ;
+    # FORMAT OF THE .QMAIL-DEFAULT FILE HERE
+    print QMAILDEFAULT "| $vpopdir/bin/vdelivermail \"\" $username\@$domain\n";
 
+  }else{
     ###
-    # FORMAT OF THE PASSWD FILE HERE
-    print PASSWD join(":",
-      $svc_acct->username,
-      'x', # "##". $svc_acct->$username,
-      $svc_acct->uid,
-      $svc_acct->gid,
-      $svc_acct->finger,
-      $svc_acct->dir,
-      $svc_acct->shell,
-    ), "\n";
-
-    ###
-    # FORMAT OF THE SHADOW FILE HERE
-    print SHADOW join(":",
-      $svc_acct->username,
-      $cpassword,
-      '',
-      '',
-      '',
-      '',
-      '',
-      '',
-      '',
-    ), "\n";
-
+    # FORMAT OF THE .QMAIL-DEFAULT FILE HERE
+    print QMAILDEFAULT "| $vpopdir/bin/vdelivermail \"\" bounce-no-mailbox\n";
   }
 
-  if ( $svc_acct->slipip ne '' ) {
+  print VPOPVIRTUALDOMAINS "$domain:$domain\n";
+
+  foreach $svc_acct (qsearch('svc_acct', {'domsvc' => $svc_domain->svcnum})) {
+    my($password)=$svc_acct->getfield('_password');
+    my($cpassword,$rpassword);
+    if ( ( length($password) <= 8 )
+         && ( $password ne '*' )
+         && ( $password ne '!!' )
+         && ( $password ne '' )
+       ) {
+      $cpassword=crypt($password,
+                       $saltset[int(rand(64))].$saltset[int(rand(64))]
+      );
+      $rpassword=$password;
+    } else {
+      $cpassword=$password;
+      $rpassword='UNIX';
+    }
+
+    my $username;
 
-    ###
-    # FORMAT OF THE ACP_* FILES HERE
-    print ACP_PASSWD join(":",
-      $svc_acct->username,
-      $cpassword,
-      "0",
-      "0",
-      "",
-      "",
-      "",
-    ), "\n";
-
-    my($ip)=$svc_acct->slipip;
-
-    unless ( $ip eq '0.0.0.0' || $svc_acct->slipip eq '0e0' ) {
-      print ACP_DIALUP $svc_acct->username, "\t*\t", $svc_acct->slipip, "\n";
+    if ($mydomain && ($mydomain eq $svc_domain->domain)) {
+      $username=$svc_acct->username;
+    } elsif ($userpolicy =~ /^prepend domsvc$/) {
+      $username=$svc_acct->domsvc . $svc_acct->username;
+    } elsif ($userpolicy =~ /^append domsvc$/) {
+      $username=$svc_acct->username . $svc_acct->domsvc;
+    } elsif ($userpolicy =~ /^append domain$/) {
+      $username=$svc_acct->username . $svc_domain->domain;
+    } else {
+      die "Unknown policy in username_policy\n";
     }
 
-    my %radreply = $svc_acct->radius_reply;
-    my %radcheck = $svc_acct->radius_check;
+    if ($svc_acct->dir ne '/dev/null' || $svc_acct->slipip ne '') {
+      if ($usernames{$username}++) {
+        die "Duplicate username detected: $username\n";
+      }
+    }
+            
+    if ( $svc_acct->uid  =~ /^(\d+)$/ ) {
+
+      die "Non-root user ". $svc_acct->username. " has 0 UID!"
+        if $svc_acct->uid == 0 && $svc_acct->username ne 'root';
+
+      if ( $svc_acct->dir ne "/dev/null") {
+
+        ###
+        # FORMAT OF FreeBSD MASTER PASSWD FILE HERE
+        print MASTER join(":",
+          $username,                        # User name
+          $cpassword,                       # Encrypted password
+          $svc_acct->uid,                   # User ID
+          $svc_acct->gid,                   # Group ID
+          "",                               # Login Class
+          "0",                              # Password Change Time
+          "0",                              # Password Expiration Time
+          $svc_acct->finger,                # Users name
+          $svc_acct->dir,                   # Users home directory
+          $svc_acct->shell,                 # shell
+        ), "\n" ;
+
+
+        ###
+        # FORMAT OF THE PASSWD FILE HERE
+        print PASSWD join(":",
+          $username,
+          'x', # "##". $username,
+          $svc_acct->uid,
+          $svc_acct->gid,
+          $svc_acct->finger,
+          $svc_acct->dir,
+          $svc_acct->shell,
+        ), "\n";
+
+        ###
+        # FORMAT OF THE SHADOW FILE HERE
+        print SHADOW join(":",
+          $username,
+          $cpassword,
+          '',
+          '',
+          '',
+          '',
+          '',
+          '',
+          '',
+        ), "\n";
+      }
 
-    my $radcheck = join ", ", map { qq($_ = "$radcheck{$_}") } keys %radcheck;
-    $radcheck .= ", " if $radcheck;
+      ###
+      # FORMAT OF THE VPASSWD FILE HERE
+      print VPASSWD join(":",
+        $svc_acct->username,
+        $cpassword,
+        '1',
+        '0',
+        $svc_acct->username,
+        "$vpopdir/domains/" . $svc_domain->domain ."/" . $svc_acct->username,
+        'NOQUOTA',
+      ), "\n";
 
-    ###
-    # FORMAT OF THE USERS FILE HERE
-    print USERS
-      $svc_acct->username,
-      qq(\t${textradiusprepend}),
-      $radcheck,
-      qq(Password = "$rpassword"\n\t),
-      join ",\n\t", map { qq($_ = "$radreply{$_}") } keys %radreply;
-    
-    if ( $ip && $ip ne '0e0' ) {
-      #print USERS qq(,\n\tFramed-Address = "$ip"\n\n);
-      print USERS qq(,\n\tFramed-IP-Address = "$ip"\n\n);
-    } else {
-      print USERS qq(\n\n);
     }
 
-    ###
-    # ICRADIUS export
-    if ( $icradiusmachines ) {
+    if ( $svc_acct->slipip ne '' ) {
 
-      my $sth = $icradius_dbh->prepare(
-        "INSERT INTO radcheck ( id, UserName, Attribute, Value ) VALUES ( ".
-        join(", ", map { $icradius_dbh->quote( $_ ) } (
-          '',
-          $svc_acct->username,
-          "Password",
-          $svc_acct->_password,
-        ) ). " )"
-      );
-      $sth->execute or die "Can't insert into radcheck table: ". $sth->errstr;
+      ###
+      # FORMAT OF THE ACP_* FILES HERE
+      print ACP_PASSWD join(":",
+        $username,
+        $cpassword,
+        "0",
+        "0",
+        "",
+        "",
+        "",
+      ), "\n";
 
-      foreach my $attribute ( keys %radcheck ) {
+      my($ip)=$svc_acct->slipip;
+
+      unless ( $ip eq '0.0.0.0' || $svc_acct->slipip eq '0e0' ) {
+        print ACP_DIALUP $username, "\t*\t", $svc_acct->slipip, "\n";
+      }
+
+      my %radreply = $svc_acct->radius_reply;
+      my %radcheck = $svc_acct->radius_check;
+
+      my $radcheck = join ", ", map { qq($_ = "$radcheck{$_}") } keys %radcheck;
+      $radcheck .= ", " if $radcheck;
+
+      ###
+      # FORMAT OF THE USERS FILE HERE
+      print USERS
+        $username,
+        qq(\t${textradiusprepend}),
+        $radcheck,
+        qq(Password = "$rpassword"\n\t),
+        join ",\n\t", map { qq($_ = "$radreply{$_}") } keys %radreply;
+
+      if ( $ip && $ip ne '0e0' ) {
+        #print USERS qq(,\n\tFramed-Address = "$ip"\n\n);
+        print USERS qq(,\n\tFramed-IP-Address = "$ip"\n\n);
+      } else {
+        print USERS qq(\n\n);
+      }
+
+      ###
+      # ICRADIUS export
+      if ( $icradiusmachines ) {
+  
         my $sth = $icradius_dbh->prepare(
           "INSERT INTO radcheck ( id, UserName, Attribute, Value ) VALUES ( ".
           join(", ", map { $icradius_dbh->quote( $_ ) } (
             '',
-            $svc_acct->username,
-            $attribute,
-            $radcheck{$attribute},
+            $username,
+            "Password",
+            $svc_acct->_password,
           ) ). " )"
         );
         $sth->execute or die "Can't insert into radcheck table: ". $sth->errstr;
+  
+        foreach my $attribute ( keys %radcheck ) {
+          my $sth = $icradius_dbh->prepare(
+            "INSERT INTO radcheck ( id, UserName, Attribute, Value ) VALUES ( ".
+            join(", ", map { $icradius_dbh->quote( $_ ) } (
+              '',
+              $username,
+              $attribute,
+              $radcheck{$attribute},
+            ) ). " )"
+          );
+          $sth->execute or die "Can't insert into radcheck table: ". $sth->errstr;      }
+  
+        foreach my $attribute ( keys %radreply ) {
+          my $sth = $icradius_dbh->prepare(
+            "INSERT INTO radreply (id, UserName, Attribute, Value) VALUES ( ".
+            join(", ", map { $icradius_dbh->quote( $_ ) } (
+              '',
+              $username,
+              $attribute,
+              $radreply{$attribute},
+            ) ). " )"
+          );
+          $sth->execute or die "Can't insert into radreply table: ". $sth->errstr;      }
       }
-
-      foreach my $attribute ( keys %radreply ) {
-        my $sth = $icradius_dbh->prepare(
-          "INSERT INTO radreply (id, UserName, Attribute, Value) VALUES ( ".
-          join(", ", map { $icradius_dbh->quote( $_ ) } (
-            '',
-            $svc_acct->username,
-            $attribute,
-            $radreply{$attribute},
-          ) ). " )"
-        );
-        $sth->execute or die "Can't insert into radreply table: ". $sth->errstr;
+    }
+  
+    ###
+    # vpopmail directory structure creation
+
+    (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username, 0700)
+      or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . ": $!";
+    (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir", 0700)
+      or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir: $!";
+    (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/cur", 0700)
+      or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/cur: $!";
+    (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/new", 0700)
+      or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/new: $!";
+    (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/tmp", 0700)
+      or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/tmp: $!";
+
+    ( open(DOTQMAIL,">$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/.qmail")
+      and flock(DOTQMAIL,LOCK_EX|LOCK_NB)  
+    ) or die "Can't open $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/.qmail: $!";
+
+    my($svc_forward);
+    foreach $svc_forward (qsearch('svc_forward', {'srcsvc' => $svc_acct->svcnum})) {
+      my($destination);
+      if ($svc_forward->dstsvc) {
+        my $dst_acct = qsearchs('svc_acct', {'svcnum' => $svc_forward->dstsvc});
+        my $dst_domain = qsearchs('svc_domain', {'svcnum' => $dst_acct->domsvc});
+        $destination = $dst_acct->username . '@' . $dst_domain->domain;
+
+        if ($dst_domain->domain eq $mydomain) {
+          print VIRTUSERTABLE $svc_acct->username . "@" . $svc_domain->domain .
+            "\t" . $dst_acct->username . "\n";
+          print RECIPIENTMAP $svc_acct->username . "@" . $svc_domain->domain .
+            ":$destination\n";
+        }
+      } else {
+        $destination = $svc_forward->dst;
       }
-
+    
+      ###
+      # FORMAT OF .QMAIL FILES HERE
+      print DOTQMAIL "$destination\n";
     }
 
+    flock(DOTQMAIL,LOCK_UN);
+    close DOTQMAIL;
+
   }
 
+  flock(VPASSWD,LOCK_UN);
+  flock(QMAILDEFAULT,LOCK_UN);
+  close VPASSWD;
+  close QMAILDEFAULT;
+
 }
 
+###
+# FORMAT OF THE ASSIGN/USERS FILE FINAL LINE HERE
+print ASSIGN ".\n";
+
+print VIRTUSERTABLE @sendmail;
+
 flock(MASTER,LOCK_UN);
 flock(PASSWD,LOCK_UN);
 flock(SHADOW,LOCK_UN);
 flock(ACP_DIALUP,LOCK_UN);
 flock(ACP_PASSWD,LOCK_UN);
 flock(USERS,LOCK_UN);
+flock(ASSIGN,LOCK_UN);
+flock(SENDMAIL_CW,LOCK_UN);
+flock(VIRTUSERTABLE,LOCK_UN);
+flock(RCPTHOSTS,LOCK_UN);
+flock(VPOPRCPTHOSTS,LOCK_UN);
+flock(RECIPIENTMAP,LOCK_UN);
+flock(VPOPVIRTUALDOMAINS,LOCK_UN);
 
 close MASTER;
 close PASSWD;
@@ -356,19 +514,26 @@ close SHADOW;
 close ACP_DIALUP;
 close ACP_PASSWD;
 close USERS;
+close ASSIGN;
+close SENDMAIL_CW;
+close VIRTUSERTABLE;
+close RCPTHOSTS;
+close VPOPRCPTHOSTS;
+close RECIPIENTMAP;
+close VPOPVIRTUALDOMAINS;
 
 ###
 # export stuff
 #
 
-my($shellmachine);
-foreach $shellmachine (@shellmachines) {
+my($ashellmachine);
+foreach $ashellmachine (@shellmachines) {
   my $scp = new Net::SCP;
-  $scp->scp("$spooldir/passwd","root\@$shellmachine:/etc/passwd.new")
+  $scp->scp("$spooldir/passwd","root\@$ashellmachine:/etc/passwd.new")
     or die "scp error: ". $scp->{errstr};
-  $scp->scp("$spooldir/shadow","root\@$shellmachine:/etc/shadow.new")
+  $scp->scp("$spooldir/shadow","root\@$ashellmachine:/etc/shadow.new")
     or die "scp error: ". $scp->{errstr};
-  ssh("root\@$shellmachine",
+  ssh("root\@$ashellmachine",
     "( ".
       "mv /etc/passwd.new /etc/passwd; ".
       "mv /etc/shadow.new /etc/shadow; ".
@@ -457,6 +622,65 @@ foreach my $icradiusmachine ( @icradiusmachines ) {
   close WRITER;
 }
 
+my @args = ("/bin/tar", "c", "--force-local", "-C", "$spooldir", "-f", "$spooldir/vpoptarball", "domains");
+
+system {$args[0]} @args;
+
+my($vpopmailmachine);
+foreach $vpopmailmachine (@vpopmailmachines) {
+  my ($machine, $vpopdir, $vpopuid, $vpopgid) = split (/\s+/, $vpopmailmachine);
+  my $scp = new Net::SCP;
+  $scp->scp("$spooldir/vpoptarball","root\@$machine:vpoptarball")
+    or die "scp error: ". $scp->{errstr};
+  ssh("root\@$machine",
+    "( ".
+      "tar xf vpoptarball; ".
+      "chown -R $vpopuid:$vpopgid domains; ".
+      "tar cf vpoptarball domains; ".
+      "cd $vpopdir; ".
+      "tar xf ~/vpoptarball; ".
+    " )"
+  )
+    == 0 or die "ssh error: $!";
+
+  $scp->scp("$spooldir/assign","root\@$machine:/var/qmail/users/assign")
+    or die "scp error: ". $scp->{errstr};
+  $scp->scp("$spooldir/vpopvirtualdomains","root\@$machine:/var/qmail/control/virtualdomains")
+    or die "scp error: ". $scp->{errstr};
+  $scp->scp("$spooldir/vpoprcpthosts","root\@$machine:/var/qmail/control/rcpthosts")
+    or die "scp error: ". $scp->{errstr};
+}
+
+my($sendmailmachine);
+foreach $sendmailmachine (@sendmailmachines) {
+  my $scp = new Net::SCP;
+  $scp->scp("$spooldir/sendmail.cw","root\@$sendmailmachine:$sendmailconfigpath/sendmail.cw.new")
+    or die "scp error: ". $scp->{errstr};
+  $scp->scp("$spooldir/virtusertable","root\@$sendmailmachine:$sendmailconfigpath/virtusertable.new")
+    or die "scp error: ". $scp->{errstr};
+  ssh("root\@$sendmailmachine",
+    "( ".
+      "mv $sendmailconfigpath/sendmail.cw.new $sendmailconfigpath/sendmail.cw; ".
+      "mv $sendmailconfigpath/virtusertable.new $sendmailconfigpath/virtusertable; ".
+      $sendmailrestart.
+    " )"
+  )
+    == 0 or die "ssh error: $!";
+}
+
+my($qmailmachine);
+foreach $qmailmachine (@qmailmachines) {
+  my $scp = new Net::SCP;
+  $scp->scp("$spooldir/recipientmap","root\@$qmailmachine:/var/qmail/control/recipientmap")
+    or die "scp error: ". $scp->{errstr};
+  $scp->scp("$spooldir/virtualdomains","root\@$qmailmachine:/var/qmail/control/virtualdomains")
+    or die "scp error: ". $scp->{errstr};
+  $scp->scp("$spooldir/rcpthosts","root\@$qmailmachine:/var/qmail/control/rcpthosts")
+    or die "scp error: ". $scp->{errstr};
+  #ssh("root\@$qmailmachine","/etc/init.d/qmail restart")
+  #  == 0 or die "ssh error: $!";
+}
+
 unlink $spoollock;
 flock(EXPORT,LOCK_UN);
 close EXPORT;
diff --git a/bin/svc_acct_sm.export b/bin/svc_acct_sm.export
deleted file mode 100755 (executable)
index d7a7840..0000000
+++ /dev/null
@@ -1,254 +0,0 @@
-#!/usr/bin/perl -Tw
-#
-# $Id: svc_acct_sm.export,v 1.10 2001-05-08 10:44:17 ivan Exp $
-# 
-# Create and export config files for sendmail, qmail
-#
-# (used to) Create and export VoiceNet_quasar.m4
-#
-# ivan@voicenet.com late oct 96
-#
-# change priority (after copies) to 19, not 10
-# ivan@voicenet.com 97-feb-5
-#
-# put file in different place and run different script, as per matt and
-# mohamed
-# ivan@voicenet.com 97-mar-10
-#
-# added exit if stuff is already locked ivan@voicenet.com 97-apr-15
-#
-# removed mail2
-# ivan@voicenet.com 97-jul-10
-#
-# rewrote lots of the bits, now exports qmail "virtualdomain",
-# "recipientmap" and "rcpthosts" files as well
-#
-# ivan@voicenet.com 97-sep-4
-#
-# adds ".extra" files
-#
-# ivan@voicenet.com 97-sep-29
-#
-# added ".pp" files, ugh.
-#
-# ivan@voicenet.com 97-oct-1
-#
-# rewrite ivan@sisd.com 98-mar-9
-#
-# now can create .qmail-default files ivan@sisd.com 98-mar-10
-#
-# put example $my_domain declaration in ivan@sisd.com 98-mar-23
-#
-# /var/spool/freeside/conf and sendmail updates ivan@sisd.com 98-aug-14
-#
-# $Log: svc_acct_sm.export,v $
-# Revision 1.10  2001-05-08 10:44:17  ivan
-# fix for OO Net::SCP
-#
-# Revision 1.9  2001/04/22 01:56:15  ivan
-# get rid of FS::SSH.pm (became Net::SSH and Net::SCP on CPAN)
-#
-# Revision 1.8  2000/07/06 03:37:24  ivan
-# don't error out on invalid svc_acct_sm.domuid's that can't be matched in
-# svc_acct.uid - just warn.
-#
-# Revision 1.7  2000/07/03 09:13:10  ivan
-# get rid of double sendmailrestart invocation; no need for multiple sessions
-#
-# Revision 1.6  2000/07/03 09:09:14  ivan
-# typo
-#
-# Revision 1.5  2000/07/03 09:03:14  ivan
-# added sendmailrestart and sendmailconfigpath config files
-#
-# Revision 1.4  2000/06/29 14:02:29  ivan
-# add sendmailrestart configuration file
-#
-# Revision 1.3  2000/06/12 08:37:56  ivan
-# sendmail fix from Jeff Finucane
-#
-# Revision 1.2  1998/12/10 07:23:17  ivan
-# use FS::Conf, need user (for datasrc)
-#
-
-use strict;
-use vars qw($conf);
-use Fcntl qw(:flock);
-use Net::SSH qw(ssh);
-use Net::SCP qw(scp);
-use FS::UID qw(adminsuidsetup datasrc);
-use FS::Record qw(qsearch qsearchs);
-use FS::svc_acct;
-use FS::svc_acct_sm;
-use FS::svc_domain;
-
-my $user = shift or die &usage;
-adminsuidsetup $user;
-
-$conf = new FS::Conf;
-
-my($shellmachine, @qmailmachines);
-if ( $conf->exists('qmailmachines') ) {
-  $shellmachine = $conf->config('shellmachine');
-  @qmailmachines = $conf->config('qmailmachines');
-}
-
-my(@sendmailmachines, $sendmailconfigpath, $sendmailrestart);
-if ( $conf->exists('sendmailmachines') ) {
-  @sendmailmachines = $conf->config('sendmailmachines');
-  $sendmailconfigpath = $conf->config('sendmailconfigpath') || '/etc';
-  $sendmailrestart = $conf->config('sendmailrestart');
-}
-
-my $mydomain = $conf->config('domain');
-
-my $spooldir = "/usr/local/etc/freeside/export.". datasrc;
-my $spoollock = "/usr/local/etc/freeside/svc_acct_sm.export.lock.". datasrc;
-
-umask 066;
-
-open(EXPORT,"+>>$spoollock") or die "Can't open $spoollock: $!";
-select(EXPORT); $|=1; select(STDOUT);
-unless ( flock(EXPORT,LOCK_EX|LOCK_NB) ) {
-  seek(EXPORT,0,0);
-  my($pid)=<EXPORT>;
-  chop($pid);
-  #no reason to start locks of blocking processes
-  die "Is another export process running under pid $pid?\n";
-}
-seek(EXPORT,0,0);
-print EXPORT $$,"\n";
-
-( open(RCPTHOSTS,">$spooldir/rcpthosts")
-  and flock(RCPTHOSTS,LOCK_EX|LOCK_NB) 
-) or die "Can't open $spooldir/rcpthosts: $!";
-( open(RECIPIENTMAP,">$spooldir/recipientmap") 
-  and flock(RECIPIENTMAP,LOCK_EX|LOCK_NB) 
-) or die "Can't open $spooldir/recipientmap: $!";
-( open(VIRTUALDOMAINS,">$spooldir/virtualdomains") 
-  and flock(VIRTUALDOMAINS,LOCK_EX|LOCK_NB)
-) or die "Can't open $spooldir/virtualdomains: $!";
-( open(VIRTUSERTABLE,">$spooldir/virtusertable")
-  and flock(VIRTUSERTABLE,LOCK_EX|LOCK_NB)
-) or die "Can't open $spooldir/virtusertable: $!";
-( open(SENDMAIL_CW,">$spooldir/sendmail.cw")
-  and flock(SENDMAIL_CW,LOCK_EX|LOCK_NB)
-) or die "Can't open $spooldir/sendmail.cw: $!";
-
-setpriority(0,0,10);
-
-my($svc_domain,%domain);
-foreach $svc_domain ( qsearch('svc_domain',{}) ) {
-  my($domain)=$svc_domain->domain;
-  $domain{$svc_domain->svcnum}=$domain;
-  print RCPTHOSTS "$domain\n.$domain\n";
-  print SENDMAIL_CW "$domain\n";
-}
-
-my(@sendmail);
-
-my($svc_acct_sm);
-foreach $svc_acct_sm ( qsearch('svc_acct_sm') ) { 
-  my($domsvc,$domuid,$domuser)=(
-    $svc_acct_sm->domsvc,
-    $svc_acct_sm->domuid,
-    $svc_acct_sm->domuser,
-  );
-  my($domain)=$domain{$domsvc};
-  my($svc_acct)=qsearchs('svc_acct',{'uid'=>$domuid});
-  unless ( $svc_acct ) {
-    warn "WARNING: couldn't find svc_acct.uid $domuid (svc_acct_sm.svcnum ".
-         $svc_acct_sm->svcnum. ") - corruped database?\n";
-    next;
-  }
-  my($username,$dir,$uid,$gid)=(
-    $svc_acct->username,
-    $svc_acct->dir,
-    $svc_acct->uid,
-    $svc_acct->gid,
-  );
-  next unless $username && $domain && $domuser;
-
-  if ($domuser eq '*') {
-    push @sendmail, "\@$domain\t$username\n";
-    print VIRTUALDOMAINS "$domain:$username-$domain\n",
-                         ".$domain:$username-$domain\n",
-    ;
-    ###
-    # qmail
-    ssh("root\@$shellmachine",
-      "[ -e $dir/.qmail-default ] || { touch $dir/.qmail-default; chown $uid:$gid $dir/.qmail-default; }"
-    ) if ( $shellmachine && $dir && $uid );
-
-  } else {
-    print VIRTUSERTABLE "$domuser\@$domain\t$username\n";
-    print RECIPIENTMAP "$domuser\@$domain:$username\@$mydomain\n";
-  }
-
-}
-
-print VIRTUSERTABLE @sendmail;
-
-chmod 0644, "$spooldir/sendmail.cw",
-            "$spooldir/virtusertable",
-            "$spooldir/rcpthosts",
-            "$spooldir/recipientmap",
-            "$spooldir/virtualdomains",
-;
-
-flock(SENDMAIL_CW,LOCK_UN);
-flock(VIRTUSERTABLE,LOCK_UN);
-flock(RCPTHOSTS,LOCK_UN);
-flock(RECIPIENTMAP,LOCK_UN);
-flock(VIRTUALDOMAINS,LOCK_UN);
-
-close SENDMAIL_CW;
-close VIRTUSERTABLE;
-close RCPTHOSTS;
-close RECIPIENTMAP;
-close VIRTUALDOMAINS;
-
-###
-# export stuff
-#
-
-my($sendmailmachine);
-foreach $sendmailmachine (@sendmailmachines) {
-  my $scp = new Net::SCP;
-  $scp->scp("$spooldir/sendmail.cw","root\@$sendmailmachine:$sendmailconfigpath/sendmail.cw.new")
-    or die "scp error: ". $scp->{errstr};
-  $scp->scp("$spooldir/virtusertable","root\@$sendmailmachine:$sendmailconfigpath/virtusertable.new")
-    or die "scp error: ". $scp->{errstr};
-  ssh("root\@$sendmailmachine",
-    "( ".
-      "mv $sendmailconfigpath/sendmail.cw.new $sendmailconfigpath/sendmail.cw; ".
-      "mv $sendmailconfigpath/virtusertable.new $sendmailconfigpath/virtusertable; ".
-      $sendmailrestart.
-    " )"
-  )
-    == 0 or die "ssh error: $!";
-}
-
-my($qmailmachine);
-foreach $qmailmachine (@qmailmachines) {
-  my $scp = new Net::SCP;
-  $scp->scp("$spooldir/recipientmap","root\@$qmailmachine:/var/qmail/control/recipientmap")
-    or die "scp error: ". $scp->{errstr};
-  $scp->scp("$spooldir/virtualdomains","root\@$qmailmachine:/var/qmail/control/virtualdomains")
-    or die "scp error: ". $scp->{errstr};
-  $scp->scp("$spooldir/rcpthosts","root\@$qmailmachine:/var/qmail/control/rcpthosts")
-    or die "scp error: ". $scp->{errstr};
-  #ssh("root\@$qmailmachine","/etc/init.d/qmail restart")
-  #  == 0 or die "ssh error: $!";
-}
-
-unlink $spoollock;
-flock(EXPORT,LOCK_UN);
-close EXPORT;
-
-#
-
-sub usage {
-  die "Usage:\n\n  svc_acct.export user\n";
-}
-
index 9282e82..532a826 100644 (file)
@@ -100,6 +100,8 @@ All further configuration files and directories are located in
   <li><a name="usernamemax">usernamemax</a> - Maximum username length (default is the size of the SQL column, probably specified when fs-setup was run)
   <li><a name="usernamemax">username-letter</a> - The existance of this file will turn on the requirement that usernames contain at least one letter.
   <li><a name="usernamemax">username-letterfirst</a> - The existance of this file will turn on the requirement that usernames start with a letter.
+  <li><a name="username_policy">username_policy</a> - This file controls the mechanism for preventing duplicate usernames in passwd/radius files exported from svc_accts.  This should be one of 'prepend domsvc' 'append domsvc' or 'append domain'
+  <li><a name="vpopmailmachines">vpopmailmachines</a> - Your vpopmail pop toasters, one per line.  Each line is of the form "machinename vpopdir vpopuid vpopgid".  Eg: poptoaster.domain.tld /home/vpopmail 508 508 Note: vpopuid and vpopgid are values taken from the vpopmail machine's /etc/passwd
 </ul>
 </body>
 
diff --git a/htdocs/docs/upgrade8.html b/htdocs/docs/upgrade8.html
new file mode 100644 (file)
index 0000000..3798922
--- /dev/null
@@ -0,0 +1,52 @@
+<head>
+  <title>Upgrading to 1.3.2</title>
+</head>
+<body>
+<h1>Upgrading to 1.3.2 from 1.3.1</h1>
+<ul>
+  <li>If migrating from 1.0.0, see these <a href="upgrade.html">instructions</a> first.
+  <li>If migrating from less than 1.1.4, see these <a href="upgrade2.html">instructions</a> first.
+  <li>If migrating from less than 1.2.0, see these <a href="upgrade3.html">instructions</a> first.
+  <li>If migrating from less than 1.2.2, see these <a href="upgrade4.html">instructions</a> first.
+  <li>If migrating from less than 1.2.3, see these <a href="upgrade5.html">instructions</a> first.
+  <li>If migrating from less than 1.3.0, see these <a href="upgrade6.html">instructions</a> first.
+  <li>If migrating from less than 1.3.1, see these <a href="upgrade7.html">instructions</a> first.
+  <li>Back up your data and current Freeside installation.
+  <li>Apply the following changes to your database:
+<pre>
+CREATE TABLE svc_forward (
+  svcnum int NOT NULL,
+  srcsvc int NOT NULL,
+  dstsvc int NOT NULL,
+  dst varchar(80),
+  PRIMARY KEY (svcnum)
+);
+ALTER TABLE svc_acct ADD domsvc integer NOT NULL;
+ALTER TABLE svc_domain ADD catchall integer;
+ALTER TABLE part_svc ADD svc_acct__domsvc integer NULL;
+ALTER TABLE part_svc ADD svc_acct__domsvc_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_domain__catchall integer NULL;
+ALTER TABLE part_svc ADD svc_domain__catchall_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_forward__srcsvc integer NULL;
+ALTER TABLE part_svc ADD svc_forward__srcsvc_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_forward__dstsvc integer NULL;
+ALTER TABLE part_svc ADD svc_forward__dstsvc_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_forward__dst integer NULL;
+ALTER TABLE part_svc ADD svc_forward__dst_flag char(1) NULL;
+
+</pre>
+  <li>Copy or symlink htdocs to the new copy.
+  <li>Remove the symlink or directory <i>(your_site_perl_directory)</i>/FS.
+  <li>Change to the FS directory in the new tarball, and build and install the
+      Perl modules:
+    <pre>
+$ cd FS/
+$ perl Makefile.PL
+$ make
+$ su
+# make install</pre>
+  <li>Run bin/dbdef-create.
+  <li>create a service based on svc_forward
+  <li>Run bin/fs-migrate-svc_acct_sm
+  <li>create the conf.dbsrc/user_policy as appropriate for your site
+</body>
index 49be720..9526f69 100644 (file)
@@ -101,6 +101,9 @@ All further configuration files and directories are located in
   <li><a name="usernamemax">usernamemax</a> - Maximum username length (default is the size of the SQL column, probably specified when fs-setup was run)
   <li><a name="usernamemax">username-letter</a> - The existance of this file will turn on the requirement that usernames contain at least one letter.
   <li><a name="usernamemax">username-letterfirst</a> - The existance of this file will turn on the requirement that usernames start with a letter.
+   <li><a name="username_policy">username_policy</a> - This file controls the mechanism for preventing duplicate usernames in passwd/radius files exported from svc_accts.  This should be one of 'prepend domsvc' 'append domsvc' or 'append domain'
+   <li><a name="vpopmailmachines">vpopmailmachines</a> - Your vpopmail pop toasters, one per line.  Each line is of the form "machinename vpopdir vpopuid vpopgid".  Eg: poptoaster.domain.tld /home/vpopmail 508 508 Note: vpopuid and vpopgid are values taken from the vpopmail machine's /etc/passwd
+
 </ul>
 </body>
 
index 702da5a..1a5c998 100644 (file)
@@ -61,6 +61,30 @@ $ perl Makefile.PL
 $ make
 $ su
 # make install UNINST=1</pre>
+   <li>Apply the following changes to your database:
+<pre>
+CREATE TABLE svc_forward (
+  svcnum int NOT NULL,
+  srcsvc int NOT NULL,
+  dstsvc int NOT NULL,
+  dst varchar(80),
+  PRIMARY KEY (svcnum)
+);
+ALTER TABLE svc_acct ADD domsvc integer NOT NULL;
+ALTER TABLE svc_domain ADD catchall integer;
+ALTER TABLE part_svc ADD svc_acct__domsvc integer NULL;
+ALTER TABLE part_svc ADD svc_acct__domsvc_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_domain__catchall integer NULL;
+ALTER TABLE part_svc ADD svc_domain__catchall_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_forward__srcsvc integer NULL;
+ALTER TABLE part_svc ADD svc_forward__srcsvc_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_forward__dstsvc integer NULL;
+ALTER TABLE part_svc ADD svc_forward__dstsvc_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_forward__dst integer NULL;
+ALTER TABLE part_svc ADD svc_forward__dst_flag char(1) NULL;
+
+</pre>
+
   <li>If you are using PostgreSQL, apply the following changes to your database:
 <pre>
 CREATE UNIQUE INDEX agent_pkey ON agent ( agentnum );
@@ -112,4 +136,7 @@ ALTER TABLE cust_main ADD COLUMN ship_fax varchar(12) NULL;
 ALTER TABLE cust_main ADD COLUMN comments varchar NULL;
 </pre>
   <li>Run bin/dbdef-create.
+  <li>create a service based on svc_forward
+  <li>Run bin/fs-migrate-svc_acct_sm
+  <li>create the conf.dbsrc/user_policy as appropriate for your site
 </body>