new export! infostreet and sqlradius provisioning switched over
authorivan <ivan>
Wed, 20 Mar 2002 21:31:49 +0000 (21:31 +0000)
committerivan <ivan>
Wed, 20 Mar 2002 21:31:49 +0000 (21:31 +0000)
 (Bug #299 - doesn't close it, but all the groundwork is done)

also removes non-transactional ICRADIUS export from svc_acct.export
  (closes: Bug#347)

15 files changed:
FS/FS/Conf.pm
FS/FS/part_export.pm
FS/FS/part_export_option.pm
FS/FS/part_svc.pm
FS/FS/svc_acct.pm
FS/bin/freeside-queued
README.1.4.0pre12
bin/icradius_reset [new file with mode: 0644]
bin/svc_acct.export
htetc/global.asa
htetc/handler.pl
httemplate/docs/export.html
httemplate/docs/upgrade8.html
httemplate/edit/part_export.cgi
httemplate/edit/process/part_export.cgi

index 91fea7e..645dbf1 100644 (file)
@@ -396,29 +396,29 @@ httemplate/docs/config.html
 
   {
     'key'         => 'icradiusmachines',
-    'section'     => 'radius',
-    'description' => 'Turn this option on to enable radcheck and radreply table population - by default in the Freeside database, or in the database specified by the <a href="http://rootwood.haze.st/aspside/config/config-view.cgi#icradius_secrets">icradius_secrets</a> config option (the radcheck and radreply tables needs to be created manually).  You do not need to use MySQL for your Freeside database to export to an ICRADIUS/FreeRADIUS MySQL database with this option.  <blockquote><b>ADDITIONAL DEPRECATED FUNCTIONALITY</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - your <a href="ftp://ftp.cheapnet.net/pub/icradius">ICRADIUS</a> machines or <a href="http://www.freeradius.org/">FreeRADIUS</a> (with MySQL authentication) machines, one per line.  Machines listed in this file will have the radcheck table exported to them.  Each line should contain four items, separted by whitespace: machine name, MySQL database name, MySQL username, and MySQL password.  For example: <CODE>"radius.isp.tld&nbsp;radius_db&nbsp;radius_user&nbsp;passw0rd"</CODE></blockquote>',
+    'section'     => 'deprecated',
+    'description' => '<b>DEPRECATED</b>, add <i>sqlradius</i> exports to <a href="../browse/part_svc">Service definitions</a> instead.  This option used to enable radcheck and radreply table population - by default in the Freeside database, or in the database specified by the <a href="http://rootwood.haze.st/aspside/config/config-view.cgi#icradius_secrets">icradius_secrets</a> config option (the radcheck and radreply tables needs to be created manually).  You do not need to use MySQL for your Freeside database to export to an ICRADIUS/FreeRADIUS MySQL database with this option.  <blockquote><b>ADDITIONAL DEPRECATED FUNCTIONALITY</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - your <a href="ftp://ftp.cheapnet.net/pub/icradius">ICRADIUS</a> machines or <a href="http://www.freeradius.org/">FreeRADIUS</a> (with MySQL authentication) machines, one per line.  Machines listed in this file will have the radcheck table exported to them.  Each line should contain four items, separted by whitespace: machine name, MySQL database name, MySQL username, and MySQL password.  For example: <CODE>"radius.isp.tld&nbsp;radius_db&nbsp;radius_user&nbsp;passw0rd"</CODE></blockquote>',
     'type'        => [qw( checkbox textarea )],
   },
 
   {
     'key'         => 'icradius_mysqldest',
-    'section'     => 'radius',
+    'section'     => 'deprecated',
     'description' => '<b>DEPRECATED</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - Destination directory for the MySQL databases, on the ICRADIUS/FreeRADIUS machines.  Defaults to "/usr/local/var/".',
     'type'        => 'text',
   },
 
   {
     'key'         => 'icradius_mysqlsource',
-    'section'     => 'radius',
+    'section'     => 'deprecated',
     'description' => '<b>DEPRECATED</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - Source directory for for the MySQL radcheck table files, on the Freeside machine.  Defaults to "/usr/local/var/freeside".',
     'type'        => 'text',
   },
 
   {
     'key'         => 'icradius_secrets',
-    'section'     => 'radius',
-    'description' => 'Optionally specifies a database for ICRADIUS/FreeRADIUS export.  Three lines: DBI data source, username and password.',
+    'section'     => 'deprecated',
+    'description' => '<b>DEPRECATED</b>, add <i>sqlradius</i> exports to <a href="../browse/part_svc">Service definitions</a> instead.  This option used to specify a database for ICRADIUS/FreeRADIUS export.  Three lines: DBI data source, username and password.',
     'type'        => 'textarea',
   },
 
index a0de03b..444e86a 100644 (file)
@@ -122,6 +122,7 @@ sub insert {
   my $options = shift;
   foreach my $optionname ( keys %{$options} ) {
     my $part_export_option = new FS::part_export_option ( {
+      'exportnum'   => $self->exportnum,
       'optionname'  => $optionname,
       'optionvalue' => $options->{$optionname},
     } );
@@ -144,16 +145,92 @@ Delete this record from the database.
 
 =cut
 
-# the delete method can be inherited from FS::Record
+#foreign keys would make this much less tedious... grr dumb mysql
+sub delete {
+  my $self = shift;
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  foreach my $part_export_option ( $self->part_export_option ) {
+    my $error = $part_export_option->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
 
-=item replace OLD_RECORD
+}
+
+=item replace OLD_RECORD HASHREF
 
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
+If a hash reference of options is supplied, part_export_option records are
+created or modified (see L<FS::part_export_option>).
+
 =cut
 
-# the replace method can be inherited from FS::Record
+sub replace {
+  my $self = shift;
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::replace;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  my $options = shift;
+  foreach my $optionname ( keys %{$options} ) {
+    my $old = qsearchs( 'part_export_option', {
+        'exportnum'   => $self->exportnum,
+        'optionname'  => $optionname,
+    } );
+    my $new = new FS::part_export_option ( {
+        'exportnum'   => $self->exportnum,
+        'optionname'  => $optionname,
+        'optionvalue' => $options->{$optionname},
+    } );
+    my $error = $old ? $new->replace($old) : $new->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+};
 
 =item check
 
@@ -198,12 +275,356 @@ sub part_svc {
   qsearchs('part_svc', { svcpart => $self->svcpart } );
 }
 
+=item part_export_option
+
+=cut
+
+sub part_export_option {
+  my $self = shift;
+  qsearch('part_export_option', { 'exportnum' => $self->exportnum } );
+}
+
+=item options 
+
+=cut
+
+sub options {
+  my $self = shift;
+  map { $_->optionname => $_->optionvalue } $self->part_export_option;
+}
+
+=item option
+
+=cut
+
+sub option {
+  my $self = shift;
+  my $part_export_option =
+    qsearchs('part_export_option', {
+      exportnum  => $self->exportnum,
+      optionname => shift,
+  } );
+  $part_export_option ? $part_export_option->optionvalue : '';
+}
+
+=item rebless
+
+=cut
+
+sub rebless {
+  my $self = shift;
+  my $exporttype = $self->exporttype;
+  my $class = ref($self);
+  bless($self, $class."::$exporttype");
+}
+
+=item export_insert SVC_OBJECT
+
+Calls the appropriate export_I<exporttype> for this object's exporttype.
+
+=cut
+
+sub export_insert {
+  my $self = shift;
+  $self->rebless;
+  $self->_export_insert(@_);
+}
+
+#sub AUTOLOAD {
+#  my $self = shift;
+#  $self->rebless;
+#  my $method = $AUTOLOAD;
+#  #$method =~ s/::(\w+)$/::_$1/; #infinite loop prevention
+#  $method =~ s/::(\w+)$/_$1/; #infinite loop prevention
+#  $self->$method(@_);
+#}
+
+=item export_replace
+
+=cut
+
+sub export_replace {
+  my $self = shift;
+  $self->rebless;
+  $self->_export_replace(@_);
+}
+
+=item export_delete
+
+=cut
+
+sub export_delete {
+  my $self = shift;
+  $self->rebless;
+  $self->_export_delete(@_);
+}
+
 =back
 
+#infostreet
+
+package FS::part_export::infostreet;
+use vars qw(@ISA);
+@ISA = qw(FS::part_export);
+
+sub _export_insert {
+  my( $self, $svc_acct ) = (shift, shift);
+  $self->infostreet_queue( $svc_acct->svcnum,
+    'createUser', $svc_acct->username, $svc_acct->password );
+}
+
+sub _export_replace {
+  my( $self, $new, $old ) = (shift, shift, shift);
+  return "can't change username with InfoStreet"
+    if $old->username ne $new->username;
+  return '' unless $old->_password ne $new->_password;
+  $self->infostreet_queue( $new->svcnum,
+    'passwd', $new->username, $new->password );
+}
+
+sub _export_delete {
+  my( $self, $svc_acct ) = (shift, shift);
+  $self->infostreet_queue( $svc_acct->svcnum,
+    'purgeAccount,releaseUsername', $svc_acct->username );
+}
+
+sub infostreet_queue {
+  my( $self, $svcnum, $method ) = (shift, shift, shift);
+  my $queue = new FS::queue {
+    'svcnum' => $svcnum,
+    'job'    => 'FS::part_export::infostreet::infostreet_command',
+  };
+  $queue->insert(
+    $self->option('url'),
+    $self->option('login'),
+    $self->option('password'),
+    $self->option('groupID'),
+    $method,
+    @_,
+  );
+}
+
+sub infostreet_command { #subroutine, not method
+  my($url, $username, $password, $groupID, $method, @args) = @_;
+
+  #quelle hack
+  if ( $method =~ /,/ ) {
+    foreach my $part ( split(/,\s*/, $method) ) {
+      infostreet_command($url, $username, $password, $groupID, $part, @args);
+    }
+    return;
+  }
+
+  eval "use Frontier::Client;";
+
+  my $conn = Frontier::Client->new( url => $url );
+  my $key_result = $conn->call( 'authenticate', $username, $password, $groupID);
+  my %key_result = _infostreet_parse($key_result);
+  die $key_result{error} unless $key_result{success};
+  my $key = $key_result{data};
+
+  my $result = $conn->call($opt{method}, $key, @{$opt{args}});
+  my %result = _infostreet_parse($result);
+  die $result{error} unless $result{success};
+
+}
+
+sub _infostreet_parse { #subroutine, not method
+  my $arg = shift;
+  map {
+    my $value = $arg->{$_};
+    #warn ref($value);
+    $value = $value->value()
+      if ref($value) && $value->isa('Frontier::RPC2::DataType');
+    $_=>$value;
+  } keys %$arg;
+}
+
+#sqlradius
+
+package FS::part_export::sqlradius;
+use vars qw(@ISA);
+@ISA = qw(FS::part_export);
+
+sub _export_insert {
+  my($self, $svc_acct) = (shift, shift);
+  $self->sqlradius_queue( $svc_acct->svcnum, 'insert',
+    'reply', $svc_acct->username, $svc_acct->radius_reply );
+  $self->sqlradius_queue( $svc_acct->svcnum, 'insert',
+    'check', $svc_acct->username, $svc_acct->radius_check );
+}
+
+sub _export_replace {
+  my( $self, $new, $old ) = (shift, shift, shift);
+
+  #return "can't (yet) change username with sqlradius"
+  #  if $old->username ne $new->username;
+  if ( $old->username ne $new->username ) {
+    my $error = $self->sqlradius_queue( $new->svcnum, 'rename',
+      $new->username, $old->username );
+    return $error if $error;
+  }
+
+  foreach my $table (qw(reply check)) {
+    my $method = "radius_$table";
+    my %new = $new->$method;
+    my %old = $old->$method;
+    if ( grep { !exists $old{$_} #new attributes
+                || $new{$n} ne $old{$n} #changed
+              } keys %new
+    ) {
+      my $error = $self->sqlradius_queue( $new->svcnum, 'insert'
+        $table, $new->username, %new );
+      return $error if $error;
+    }
+
+    my @del = grep { !exists $new{$_} } keys %old;
+    my $error = $self->sqlradius_queue( $new->svcnum, 'sqlradius_attrib_delete',
+      $table, $new->username, @del );
+    return $error if $error;
+  }
+
+  '';
+}
+
+sub _export_delete {
+  my( $self, $svc_something ) = (shift, shift);
+  $self->sqlradius_queue( $svc_acct->svcnum, 'delete',
+    $svc_something->username );
+}
+
+sub sqlradius_queue {
+  my( $self, $svcnum, $method ) = (shift, shift, shift);
+  my $queue = new FS::queue {
+    'svcnum' => $svcnum,
+    'job'    => "FS::part_export::sqlradius::sqlradius_$method",
+  };
+  $queue->insert(
+    $self->option('datasrc'),
+    $self->option('username'),
+    $self->option('password'),
+    @_,
+  );
+}
+
+sub sqlradius_insert { #subroutine, not method
+  my $dbh = sqlradius_connect(shift, shift, shift);
+  my( $replycheck, $username, %attributes ) = @_;
+
+  foreach my $attribute ( keys %attributes ) {
+    my $u_sth = $dbh->prepare(
+      "UPDATE rad$replycheck SET Value = ? WHERE UserName = ? AND Attribute = ?"    ) or die $dbh->errstr;
+    my $i_sth = $dbh->prepare(
+      "INSERT INTO rad$replycheck ( id, UserName, Attribute, Value ) ".
+        "VALUES ( ?, ?, ?, ? )" )
+      or die $dbh->errstr;
+    $u_sth->execute($attributes{$attribute}, $username, $attribute) > 0
+      or $i_sth->execute( '', $username, $attribute, $attributes{$attribute} )
+        or die "can't insert into rad$replycheck table: ". $i_sth->errstr;
+  }
+  $dbh->disconnect;
+}
+
+sub sqlradius_rename { #subroutine, not method
+  my $dbh = sqlradius_connect(shift, shift, shift);
+  my($new_username, $old_username) = @_;
+  foreach my $table (qw(radreply radcheck)) {
+    my $sth = $dbh->prepare("UPDATE $table SET Username = ? WHERE UserName = ?")
+      or die $dbh->errstr;
+    $sth->execute($new_username, $old_username)
+      or die "can't update $table: ". $sth->errstr;
+  }
+  $dbh->disconnect;
+}
+
+sub sqlradius_attrib_delete { #subroutine, not method
+  my $dbh = sqlradius_connect(shift, shift, shift);
+  my( $replycheck, $username, @attrib ) = @_;
+
+  foreach my $attribute ( @attrib ) {
+    my $sth = $dbh->prepare(
+        "DELETE FROM $table WHERE UserName = ? AND Attribute = ?" )
+      or die $dbh->errstr;
+    $sth->execute($username,$attribute)
+      or die "can't delete from $table table: ". $sth->errstr;
+  }
+  $dbh->disconnect;
+}
+
+sub sqlradius_delete { #subroutine, not method
+  my $dbh = sqlradius_connect(shift, shift, shift);
+  my $username = shift;
+
+  foreach my $table (qw( radcheck radreply )) {
+    my $sth = $dbh->prepare( "DELETE FROM $table WHERE UserName = ?" );
+    $sth->execute($username)
+      or die "can't delete from $table table: ". $sth->errstr;
+  }
+  $dbh->disconnect;
+}
+
+sub sqlradius_connect {
+  #my($datasrc, $username, $password) = @_;
+  #DBI->connect($datasrc, $username, $password) or die $DBI::errstr;
+  DBI->connect(@_) or die $DBI::errstr;
+}
+
+=head1 NOTES
+
+Writing a new export class:
+
+#myexport
+
+package FS::part_export::myexport;
+use vars qw(@ISA);
+@ISA = qw(FS::part_export);
+
+sub _export_insert {
+  my($self, $svc_something) = (shift, shift);
+  $self->myexport_queue( $svc_acct->svcnum, 'insert',
+    $svc_something->username, $svc_something->password );
+}
+
+sub _export_replace {
+  my( $self, $new, $old ) = (shift, shift, shift);
+  #return "can't change username with myexport"
+  #  if $old->username ne $new->username;
+  #return '' unless $old->_password ne $new->_password;
+  $self->myexport_queue( $new->svcnum,
+    'replace', $new->username, $new->password );
+}
+
+sub _export_delete {
+  my( $self, $svc_something ) = (shift, shift);
+  $self->myexport_queue( $svc_acct->svcnum,
+    'delete', $svc_something->username );
+}
+
+#a good idea to queue anything that could fail or take any time
+sub myexport_queue {
+  my( $self, $svcnum, $method ) = (shift, shift, shift);
+  my $queue = new FS::queue {
+    'svcnum' => $svcnum,
+    'job'    => "FS::part_export::myexport::myexport_$method",
+  };
+  $queue->insert( @_ );
+}
+
+sub myexport_insert { #subroutine, not method
+}
+sub myexport_replace { #subroutine, not method
+}
+sub myexport_delete { #subroutine, not method
+}
+
 =head1 BUGS
 
 Probably.
 
+Hmm, export code has wound up in here.  Move those sub-classes out into their
+own files, at least.  Also hmm... cust_export class (not necessarily a
+database table...) ... ?
+
 =head1 SEE ALSO
 
 L<FS::part_export_option>, L<FS::part_svc>, L<FS::svc_acct>, L<FS::svc_domain>,
index 1ce0de6..61ea956 100644 (file)
@@ -40,7 +40,7 @@ currently supported:
 
 =item optionname - option name
 
-=item opeionvalue - option value
+=item optionvalue - option value
 
 =back
 
index 41ee21d..82935dd 100644 (file)
@@ -295,11 +295,21 @@ sub all_part_svc_column {
   qsearch('part_svc_column', { 'svcpart' => $self->svcpart } );
 }
 
+=item part_export
+
+=cut
+
+sub part_export {
+  my $self = shift;
+  my %search = ( 'svcpart' => $self->svcpart );
+  qsearch('part_export', \%search);
+}
+
 =back
 
 =head1 VERSION
 
-$Id: part_svc.pm,v 1.9 2002-01-28 06:57:23 ivan Exp $
+$Id: part_svc.pm,v 1.10 2002-03-20 21:31:49 ivan Exp $
 
 =head1 BUGS
 
index bb9fe67..2305aeb 100644 (file)
@@ -1,7 +1,8 @@
 package FS::svc_acct;
 
 use strict;
-use vars qw( @ISA $nossh_hack $conf $dir_prefix @shells $usernamemin
+use vars qw( @ISA $nossh_hack $noexport_hack $conf
+             $dir_prefix @shells $usernamemin
              $usernamemax $passwordmin $passwordmax
              $username_ampersand $username_letter $username_letterfirst
              $username_noperiod $username_uppercase
@@ -9,7 +10,6 @@ use vars qw( @ISA $nossh_hack $conf $dir_prefix @shells $usernamemin
              $cyrus_server $cyrus_admin_user $cyrus_admin_pass
              $cp_server $cp_user $cp_pass $cp_workgroup
              $dirhash
-             $icradius_dbh
              @saltset @pw_set
              $rsync $ssh $exportdir $vpopdir);
 use Carp;
@@ -88,18 +88,7 @@ $FS::UID::callback{'FS::svc_acct'} = sub {
     $cp_pass = '';
     $cp_workgroup = '';
   }
-  if ( $conf->exists('icradiusmachines') ) {
-    if ( $conf->exists('icradius_secrets') ) {
-      #need some sort of late binding so it's only connected to when
-      # actually used, hmm
-      $icradius_dbh = DBI->connect($conf->config('icradius_secrets'))
-        or die $DBI::errstr;
-    } else {
-      $icradius_dbh = dbh;
-    }
-  } else {
-    $icradius_dbh = '';
-  }
+
   $dirhash = $conf->config('dirhash') || 0;
   $exportdir = "/usr/local/etc/freeside/export." . datasrc;
   if ( $conf->exists('vpopmailmachines') ) {
@@ -246,6 +235,8 @@ $username, $uid, $gid, $dir, and $shell.
 
 (TODOC: cyrus config file, L<FS::queue> and L<freeside-queued>)
 
+(TODOC: new exports! $noexport_hack)
+
 =cut
 
 sub insert {
@@ -297,6 +288,20 @@ sub insert {
     return $error;
   }
 
+  #new-style exports!
+  unless ( $noexport_hack ) {
+    foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+      my $error = $part_export->export_insert($self);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ". $part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
+    }
+  }
+
+  #old-style exports
+
   my( $username, $uid, $gid, $dir, $shell ) = (
     $self->username,
     $self->uid,
@@ -340,37 +345,6 @@ sub insert {
     }
   }
   
-  if ( $icradius_dbh ) {
-
-    my $radcheck_queue =
-      new FS::queue {
-      'svcnum' => $self->svcnum,
-      'job' => 'FS::svc_acct::icradius_rc_insert'
-    };
-    $error = $radcheck_queue->insert( $self->username,
-                                      $self->_password,
-                                      $self->radius_check
-                                    );
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "queueing job (transaction rolled back): $error";
-    }
-
-    my $radreply_queue =
-      new FS::queue { 
-      'svcnum' => $self->svcnum,
-      'job' => 'FS::svc_acct::icradius_rr_insert'
-    };
-    $error = $radreply_queue->insert( $self->username,
-                                      $self->_password,
-                                      $self->radius_reply
-                                    );
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "queueing job (transaction rolled back): $error";
-    }
-  }
-
   if ( $vpopdir ) {
 
     my $vpopmail_queue =
@@ -390,6 +364,7 @@ sub insert {
 
   }
 
+  #end of old-style exports
 
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   ''; #no error
@@ -451,56 +426,6 @@ sub cp_insert {
   die $app->message."\n" unless $app->ok;
 }
 
-sub icradius_rc_insert {
-  my( $username, $password, %radcheck ) = @_;
-  
-  my $sth = $icradius_dbh->prepare(
-    "INSERT INTO radcheck ( id, UserName, Attribute, Value ) VALUES ( ".
-    join(", ", map { $icradius_dbh->quote($_) } (
-      '',
-      $username,
-      "Password",
-      $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;
-  }
-
-  1;
-}
-
-sub icradius_rr_insert {
-  my( $username, $password, %radreply ) = @_;
-  
-  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;
-  }
-
-  1;
-}
-
-
 sub vpopmail_insert {
   my( $username, $password, $domain, $vpopdir ) = @_;
   
@@ -571,6 +496,8 @@ $username and $dir.
 
 (TODOC: cyrus config file)
 
+(TODOC: new exports! $noexport_hack)
+
 =cut
 
 sub delete {
@@ -590,7 +517,7 @@ sub delete {
   return "Can't delete an account with (svc_www) web service!"
     if qsearch( 'svc_www', { 'usersvc' => $self->usersvc } );
 
-  # what about records in session ?
+  # what about records in session ? (they should refer to history table)
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -639,6 +566,20 @@ sub delete {
     return $error;
   }
 
+  #new-style exports!
+  unless ( $noexport_hack ) {
+    foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+      my $error = $part_export->export_delete($self);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ". $part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
+    }
+  }
+
+  #old-style exports
+
   my( $username, $dir ) = (
     $self->username,
     $self->dir,
@@ -671,24 +612,6 @@ sub delete {
     }
   }
 
-  if ( $icradius_dbh ) {
-
-    my $radcheck_queue =
-      new FS::queue { 'job' => 'FS::svc_acct::icradius_rc_delete' };
-    $error = $radcheck_queue->insert( $self->username );
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "queueing job (transaction rolled back): $error";
-    }
-
-    my $radreply_queue =
-      new FS::queue { 'job' => 'FS::svc_acct::icradius_rr_delete' };
-    $error = $radreply_queue->insert( $self->username );
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "queueing job (transaction rolled back): $error";
-    }
-  }
   if ( $vpopdir ) {
     my $queue = new FS::queue { 'job' => 'FS::svc_acct::vpopmail_delete' };
     $error = $queue->insert( $self->username, $self->domain );
@@ -699,6 +622,8 @@ sub delete {
 
   }
 
+  #end of old-style exports
+
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 }
@@ -742,30 +667,6 @@ sub cp_delete {
   die $app->message."\n" unless $app->ok;
 }
 
-sub icradius_rc_delete {
-  my $username = shift;
-  
-  my $sth = $icradius_dbh->prepare(
-    'DELETE FROM radcheck WHERE UserName = ?'
-  );
-  $sth->execute($username)
-    or die "can't delete from radcheck table: ". $sth->errstr;
-
-  1;
-}
-
-sub icradius_rr_delete {
-  my $username = shift;
-  
-  my $sth = $icradius_dbh->prepare(
-    'DELETE FROM radreply WHERE UserName = ?'
-  );
-  $sth->execute($username)
-    or die "can't delete from radreply table: ". $sth->errstr;
-
-  1;
-}
-
 sub vpopmail_delete {
   my( $username, $domain ) = @_;
   
@@ -858,6 +759,20 @@ sub replace {
     return $error if $error;
   }
 
+  #new-style exports!
+  unless ( $noexport_hack ) {
+    foreach my $part_export ( $new->cust_svc->part_svc->part_export ) {
+      my $error = $part_export->export_replace($new,$old);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ". $part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
+    }
+  }
+
+  #old-style exports
+
   my ( $old_dir, $new_dir, $uid, $gid ) = (
     $old->getfield('dir'),
     $new->getfield('dir'),
@@ -900,19 +815,6 @@ sub replace {
     }
   }
 
-  if ( $icradius_dbh ) {
-    my $queue = new FS::queue {  
-      'svcnum' => $new->svcnum,
-      'job' => 'FS::svc_acct::icradius_rc_replace'
-    };
-    $error = $queue->insert( $new->username,
-                             $new->_password,
-                           );
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "queueing job (transaction rolled back): $error";
-    }
-  }
   if ( $vpopdir ) {
     my $cpassword = crypt(
       $new->_password,$saltset[int(rand(64))].$saltset[int(rand(64))]
@@ -938,23 +840,12 @@ sub replace {
     }
   }
 
+  #end of old-style exports
 
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   ''; #no error
 }
 
-sub icradius_rc_replace {
-  my( $username, $new_password ) = @_;
-   my $sth = $icradius_dbh->prepare(
-     "UPDATE radcheck SET Value = ? WHERE UserName = ? and Attribute = ?"
-   );
-   $sth->execute($new_password, $username, 'Password' )
-     or die "can't update radcheck table: ". $sth->errstr;
-
-  1;
-}
-
 sub cp_rename {
   my ( $old_username, $new_username ) = @_;
 
@@ -1305,19 +1196,22 @@ sub radius_reply {
 Returns key/value pairs, suitable for assigning to a hash, for any RADIUS
 check attributes of this record.
 
-Accessing RADIUS attributes directly is not supported and will break in the
-future.
+Note that this is now the preferred method for reading RADIUS attributes - 
+accessing the columns directly is discouraged, as the column names are
+expected to change in the future.
 
 =cut
 
 sub radius_check {
   my $self = shift;
-  map {
-    /^(rc_(.*))$/;
-    my($column, $attrib) = ($1, $2);
-    #$attrib =~ s/_/\-/g;
-    ( $FS::raddb::attrib{lc($attrib)}, $self->getfield($column) );
-  } grep { /^rc_/ && $self->getfield($_) } fields( $self->table );
+  ( 'Password' => $self->_password,
+    map {
+      /^(rc_(.*))$/;
+      my($column, $attrib) = ($1, $2);
+      #$attrib =~ s/_/\-/g;
+      ( $FS::raddb::attrib{lc($attrib)}, $self->getfield($column) );
+    } grep { /^rc_/ && $self->getfield($_) } fields( $self->table )
+  );
 }
 
 =item domain
index 56475d0..c3c9240 100644 (file)
@@ -15,6 +15,7 @@ use FS::queue;
 use FS::cust_main;
 use FS::svc_acct;
 use Net::SSH 0.05;
+use FS::part_export;
 
 my $pid_file = '/var/run/freeside-queued.pid';
 
@@ -85,7 +86,7 @@ while (1) {
 
     my $eval = "&". $ljob->job. '(@args);';
     warn "running $eval";
-    eval $eval;
+    eval $eval; #throw away return value?  suppose so
     if ( $@ ) {
       warn "job $eval failed";
       my %hash = $ljob->hash;
index c801a26..d61656c 100644 (file)
@@ -21,9 +21,16 @@ Run bin/create-history-tables
 
 Run bin/dbdef-create again
 
+the mxmachines, nsmachines, arecords and cnamerecords configuration values have been deprecated.  Use the defaultrecords configuration value instead.
+
+New export code has landed!  If you were using the icradiusmachines,
+icradius_mysqldest, icradius_mysqlsource, or icradius_secrets files, see
+the "sqlradius" export instead.  Use MySQL replication 
+<http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication>
+or point the "sqlradius" export directly at your external ICRADIUS or
+FreeRADIUS (or through an SSL-encrypting proxy...)
+
 Arrange for freeside-expiration-alerter to be run daily, if desired.
 
 Restart Apache and freeside-queued
 
-the mxmachines, nsmachines, arecords and cnamerecords configuration values have been deprecated.  Use the defaultrecords configuration value instead.
-
diff --git a/bin/icradius_reset b/bin/icradius_reset
new file mode 100644 (file)
index 0000000..58663ee
--- /dev/null
@@ -0,0 +1,7 @@
+
+  my $sth = $icradius_dbh->prepare("DELETE FROM radcheck");
+  $sth->execute or die "Can't reset radcheck table: ". $sth->errstr;
+  my $sth2 = $icradius_dbh->prepare("DELETE FROM radreply");
+  $sth2->execute or die "Can't reset radreply table: ". $sth2->errstr;
+
+
index 82a8935..261f499 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 #
-# $Id: svc_acct.export,v 1.34 2002-02-23 02:14:26 jeff Exp $
+# $Id: svc_acct.export,v 1.35 2002-03-20 21:31:49 ivan Exp $
 #
 # Create and export password, radius and vpopmail password files:
 # passwd, passwd.adjunct, shadow, acp_passwd, acp_userinfo, acp_dialup
@@ -47,22 +47,6 @@ my @erpcdmachines = $conf->config('erpcdmachines')
 my @radiusmachines = $conf->config('radiusmachines')
   if $conf->exists('radiusmachines');
 
-my $icradiusmachines = $conf->exists('icradiusmachines');
-my @icradiusmachines = $conf->config('icradiusmachines') if $icradiusmachines;
-my $icradius_mysqldest =
-  $conf->config('icradius_mysqldest') || "/usr/local/var"
-    if $icradiusmachines;
-my $icradius_mysqlsource =
-  $conf->config('icradius_mysqlsource') || "/usr/local/var/freeside"
-    if $icradiusmachines;
-my $icradius_dbh;
-if ( $icradiusmachines && $conf->exists('icradius_secrets') ) {
-  $icradius_dbh = DBI->connect($conf->config('icradius_secrets'))
-    or die $DBI::errstr;
-} else {
-  $icradius_dbh = dbh;
-}
-
 my $textradiusprepend =
   $conf->exists('textradiusprepend')
     ? $conf->config('textradiusprepend')
@@ -189,13 +173,6 @@ chmod 0600, "$spooldir/master.passwd",
 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;
-  my $sth2 = $icradius_dbh->prepare("DELETE FROM radreply");
-  $sth2->execute or die "Can't reset radreply table: ". $sth2->errstr;
-}
-
 setpriority(0,0,10);
 
 print USERS "$radiusprepend\n";
@@ -399,7 +376,7 @@ foreach $svc_domain (sort {$a->domain cmp $b->domain} @svc_domain) {
         $username,
         qq(\t${textradiusprepend}),
         $radcheck,
-        qq(Password = "$rpassword"\n\t),
+#        qq(Password = "$rpassword"\n\t),
         join ",\n\t", map { qq($_ = "$radreply{$_}") } keys %radreply;
 
       if ( $ip && $ip ne '0e0' ) {
@@ -409,57 +386,6 @@ foreach $svc_domain (sort {$a->domain cmp $b->domain} @svc_domain) {
         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( $_ ) } (
-            '',
-            $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;      }
-  
-        if ( $ip && $ip ne '0e0' ) {
-          my $sth = $icradius_dbh->prepare(
-            "INSERT INTO radreply (id, UserName, Attribute, Value) VALUES ( ".
-            join(", ", map { $icradius_dbh->quote( $_ ) } (
-              '',
-              $username,
-              'Framed-IP-Address',
-              $ip,
-            ) ). " )"
-          );
-          $sth->execute or die "Can't insert into radreply table: ". $sth->errstr;      }
-      }
     }
   
     ###
@@ -629,27 +555,6 @@ foreach $radiusmachine (@radiusmachines) {
     == 0 or die "ssh error: $!";
 }
 
-foreach my $icradiusmachine ( @icradiusmachines ) {
-  my( $machine, $db, $user, $pass ) = split(/\s+/, $icradiusmachine);
-  chdir $icradius_mysqlsource or die "Can't cd $icradius_mysqlsource: $!";
-  open(WRITER,"|ssh root\@$machine mysql -v --user=$user -p $db");
-  my $oldfh = select WRITER; $|=1; select $oldfh;
-  print WRITER "$pass\n";
-  sleep 2;
-  print WRITER "LOCK TABLES radcheck WRITE, radreply WRITE;\n";
-  foreach my $file ( glob("radcheck.*") ) {
-    my $scp = new Net::SCP;
-    $scp->scp($file,"root\@$machine:$icradius_mysqldest/$db/$file")
-      or die "scp error: ". $scp->{errstr};
-  }
-  foreach my $file ( glob("radreply.*") ) {
-    my $scp = new Net::SCP;
-    $scp->scp($file,"root\@$machine:$icradius_mysqldest/$db/$file")
-      or die "scp error: ". $scp->{errstr};
-  }
-  close WRITER;
-}
-
 #my @args = ("/bin/tar", "c", "--force-local", "-C", "$spooldir", "-f", "$spooldir/vpoptarball", "domains");
 
 #system {$args[0]} @args;
index 083f225..94d6b7e 100644 (file)
@@ -47,7 +47,7 @@ use FS::svc_forward;
 use FS::svc_www;
 use FS::type_pkgs;
 use FS::part_export;
-#use FS::part_export_option;
+use FS::part_export_option;
 
 sub Script_OnStart {
   $Response->AddHeader('Pragma' => 'no-cache');
index 5f24492..8e097c6 100644 (file)
@@ -104,7 +104,7 @@ sub handler
       use FS::svc_www;
       use FS::type_pkgs;
       use FS::part_export;
-      #use FS::part_export_option;
+      use FS::part_export_option;
 
       *CGI::redirect = sub {
         my( $self, $location ) = @_;
index c7f1b4c..71e3acf 100755 (executable)
@@ -3,6 +3,7 @@
 </head>
 <body>
   <h1>File exporting</h1>
+  <font size="+2">NOTE: This file is OUT OF DATE with the landing of the new export code and is only here for reference.  DO NOT follow these instructions.  Instead use the new exports in the web interface.</font>
   <ul>
     <li>bin/svc_acct.export will create UNIX <b>passwd</b>, <b>shadow</b> and <b>master.passwd</b> files, ERPCD <b>acp_passwd</b> and <b>acp_dialup</b> files and a RADIUS <b>users</b> file in the <b>/usr/local/etc/freeside/export.<i>datasrc</i></b> directory.  Some RADIUS servers (such as <a href="http://www.open.com.au/radiator/">Radiator</a>, <a href="ftp://ftp.cheapnet.net/pub/icradius/">ICRADIUS</a> and <a href="http://www.freeradius.org/">FreeRADIUS</a>) will authenticate directly out of an SQL database.  In these cases,
 it is reccommended that you replicate (<a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">Replication in MySQL</a>) the data to an external RADIUS machine or point icradius_secrets to the external machine rather than running the RADIUS server on your Freeside machine.  Using the appropriate <a href="../config/config-view.cgi">configuration settings</a>, you can export these files to your remote machines unattended:
index 25e766b..e52b69a 100644 (file)
@@ -319,5 +319,11 @@ ALTER TABLE cust_refund DROP COLUMN crednum;
   <b>freeside-expiration-alerter</b> to be run daily by cron or similar
   facility.  The message it sends can be configured from the
   <u>Configuration</u> choice of the main menu as <u>alerter_template</u>.
+  <li>Export has been rewritten.  If you were using the icradiusmachines,
+  icradius_mysqldest, icradius_mysqlsource, or icradius_secrets files, add
+  an appropriate "sqlradius" export to all relevant Service Definitions
+  instead.  Use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or
+  point the "sqlradius" export directly at your external ICRADIUS or FreeRADIUS
+  database (or through an SSL-necrypting proxy...)
 </ul>
 </body>
index ffce043..a44bd39 100644 (file)
@@ -22,7 +22,7 @@ if ( $cgi->param('error') ) {
   } );
 }
 
-warn "***$query***";
+#warn "***$query***";
 if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {
   $action = 'Add';
   my $old_part_export = qsearchs('part_export', { 'exportnum' => $1 } );
@@ -37,7 +37,112 @@ if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {
 }
 $action ||= $part_export->exportnum ? 'Edit' : 'Add';
 
-my @types = qw(shell bsdshell textradius sqlradius cp);
+my %exports = (
+  'svc_acct' => {
+    'sysvshell' => {
+      'desc' =>
+        'Batch export of /etc/passwd and /etc/shadow files (Linux/SysV)',
+      'options' => {},
+    },
+    'bsdshell' => {
+      'desc' =>
+        'Batch export of /etc/passwd and /etc/master.passwd files (BSD)',
+      'options' => {},
+    },
+#    'nis' => {
+#      'desc' =>
+#        'Batch export of /etc/global/passwd and /etc/global/shadow for NIS ',
+#      'options' => {},
+#    },
+    'bsdshell' => {
+      'desc' =>
+        'Batch export of /etc/passwd and /etc/master.passwd files (BSD)',
+      'options' => {},
+    },
+    'textradius' => {
+      'desc' => 'Batch export of a text /etc/raddb/users file (Livingston, Cistron)',
+    },
+    'sqlradius' => {
+      'desc' => 'Real-time export to SQL-backed RADIUS (ICRADIUS, FreeRADIUS)',
+      'options' => {
+        'datasrc'  => { label=>'DBI data source' },
+        'username' => { label=>'Database username' },
+        'password' => { label=>'Database password' },
+      },
+      'nodomain' => 'Y',
+      'notes' => 'Not specifying datasrc will export to the freeside database? (no...  notes on MySQL replication, etc., from Conf.pm && export.html etc.',
+    },
+    'cyrus' => {
+      'desc' => 'Real-time export to Cyrus IMAP server',
+    },
+    'cp' => {
+      'desc' => 'Real-time export to Critical Path Account Provisioning Protocol',
+    },
+    'infostreet' => {
+      'desc' => 'Real-time export to InfoStreet streetSmartAPI',
+      'options' => {
+        'url'      => { label=>'XML-RPC Access URL', },
+        'login'    => { label=>'InfoStreet login', },
+        'password' => { label=>'InfoStreet password', },
+        'groupID'  => { label=>'InfoStreet groupID', },
+      },
+      'nodomain' => 'Y',
+      'notes' => 'http://www.infostreet.com/ .... install Frontier::Client',
+    }
+  },
+
+  'svc_domain' => {},
+
+  'svc_acct_sm' => {},
+
+  'svc_forward' => {},
+
+  'svc_www' => {},
+
+);
+
+my $svcdb = $part_export->part_svc->svcdb;
+my %layers = map { $_ => "$_ - ". $exports{$svcdb}{$_}{desc} }
+               keys %{$exports{$svcdb}};
+$layers{''}='';
+
+my $widget = new HTML::Widgets::SelectLayers(
+  'selected_layer' => $part_export->exporttype,
+  'selected_layer' => $part_export->exporttype,
+  'options'        => \%layers,
+  'form_name'      => 'dummy',
+  'form_action'    => 'process/part_export.cgi',
+  'form_text'      => [qw( exportnum svcpart machine )],
+#  'form_checkbox'  => [qw()],
+  'html_between'    => "</TD></TR></TABLE>\n",
+  'layer_callback'  => sub {
+    my $layer = shift;
+    my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!.
+               ntable("#cccccc",2);
+    foreach my $option ( keys %{$exports{$svcdb}->{$layer}{options}} ) {
+#    foreach my $option ( qw(url login password groupID ) ) {
+      my $optinfo = $exports{$svcdb}->{$layer}{options}{$option};
+      my $label = $optinfo->{label};
+      my $value = $part_export->option($option);
+      $html .= qq!<TR><TD ALIGN="right">$label</TD><TD>!.
+               qq!<TD><INPUT TYPE="text" NAME="$option" VALUE="$value"></TD>!.
+               '</TR>';
+    }
+    $html .= '</TABLE>';
+
+    $html .= '<INPUT TYPE="hidden" NAME="options" VALUE="'.
+             join(',', keys %{$exports{$svcdb}->{$layer}{options}} ). '">';
+
+    $html .= '<INPUT TYPE="hidden" NAME="nodomain" VALUE="'.
+             $exports{$svcdb}->{$layer}{nodomain}. '">';
+
+    $html .= '<INPUT TYPE="submit" VALUE="'.
+             ( $part_export->exportnum ? "Apply changes" : "Add export" ).
+             '">';
+
+    $html;
+  },
+);
 
 %>
 <%= header("$action Export", menubar(
@@ -46,35 +151,30 @@ my @types = qw(shell bsdshell textradius sqlradius cp);
 %>
 
 <% if ( $cgi->param('error') ) { %>
-<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT>
+  <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT>
+  <BR><BR>
 <% } %>
 
-<FORM ACTION="<%= popurl(1) %>process/part_export.cgi" METHOD=POST>
-<% #print '<FORM NAME="dummy">'; %>
+<FORM NAME="dummy">
+<INPUT TYPE="hidden" NAME="exportnum" VALUE="<%= $part_export->exportnum %>">
 
 <%= ntable("#cccccc",2) %>
 <TR>
   <TD ALIGN="right">Service</TD>
   <TD BGCOLOR="#ffffff">
     <%= $part_export->svcpart %> - <%= $part_export->part_svc->svc %>
+    <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $part_export->svcpart %>">
   </TD>
 </TR>
 <TR>
-  <TD ALIGN="right">Export</TD>
-  <TD><SELECT NAME="exporttype"><OPTION></OPTION>
-<% foreach my $type ( @types ) { %>
-    <OPTION><%= $type %></OPTION>
-<% } %>
-  </SELECT></TD>
-</TR>
-<TR>
   <TD ALIGN="right">Export host</TD>
   <TD>
     <INPUT TYPE="text" NAME="machine" VALUE="<%= $part_export->machine %>">
   </TD>
 </TR>
-</TABLE>
-</FORM>
+<TR>
+  <TD ALIGN="right">Export</TD>
+  <TD><%= $widget->html %>
 </BODY>
 </HTML>
 
index 9ee1c59..8160527 100644 (file)
@@ -6,6 +6,9 @@ my $exportnum = $cgi->param('exportnum');
 
 my $old = qsearchs('part_export', { 'exportnum'=>$exportnum } ) if $exportnum;
 
+#fixup options
+my %options = map { $_=>$cgi->param($_) } $cgi->param('options');
+
 my $new = new FS::part_export ( {
   map {
     $_, scalar($cgi->param($_));