summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS/Schema.pm20
-rw-r--r--FS/FS/cust_svc.pm11
-rw-r--r--FS/FS/export_cust_svc.pm134
-rw-r--r--FS/FS/part_export.pm87
-rw-r--r--FS/FS/part_export/ispconfig3.pm361
5 files changed, 613 insertions, 0 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index e38ebff..f28864c 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -4470,6 +4470,26 @@ sub tables_hashref {
],
},
+ 'export_cust_svc' => {
+ 'columns' => [
+ 'exportcustsvcnum', 'serial', '', '', '', '',
+ 'exportnum', 'int', '', '', '', '',
+ 'svcnum', 'int', '', '', '', '',
+ 'remoteid', 'varchar', '', 512, '', '',
+ ],
+ 'primary_key' => 'exportcustsvcnum',
+ 'unique' => [ [ 'exportnum', 'svcnum' ] ],
+ 'index' => [ [ 'exportnum', 'svcnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
+ },
+
'export_device' => {
'columns' => [
'exportdevicenum' => 'serial', '', '', '', '',
diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm
index d91fa0d..c06b302 100644
--- a/FS/FS/cust_svc.pm
+++ b/FS/FS/cust_svc.pm
@@ -169,6 +169,17 @@ sub delete {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ # delete associated export_cust_svc
+ foreach my $export_cust_svc (
+ qsearch('export_cust_svc',{ 'svcnum' => $self->svcnum })
+ ) {
+ my $error = $export_cust_svc->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
my $error = $self->SUPER::delete;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
diff --git a/FS/FS/export_cust_svc.pm b/FS/FS/export_cust_svc.pm
new file mode 100644
index 0000000..7cfdcc6
--- /dev/null
+++ b/FS/FS/export_cust_svc.pm
@@ -0,0 +1,134 @@
+package FS::export_cust_svc;
+use base qw(FS::Record);
+
+use strict;
+use FS::Record qw( qsearchs );
+
+=head1 NAME
+
+FS::export_cust_svc - Object methods for export_cust_svc records
+
+=head1 SYNOPSIS
+
+ use FS::export_cust_svc;
+
+ $record = new FS::export_cust_svc \%hash;
+ $record = new FS::export_cust_svc { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::export_cust_svc object represents information unique
+to a given part_export and cust_svc pair.
+FS::export_cust_svc inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item exportcustsvcnum - primary key
+
+=item exportnum - export (see L<FS::part_export>)
+
+=item svcnum - service (see L<FS::cust_svc>)
+
+=item remoteid - id for accessing service on export remote system
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new export_cust_svc object. To add the object to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'export_cust_svc'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ return "export_cust_svc for exportnum ".$self->exportnum.
+ " svcnum ".$self->svcnum." already exists"
+ if qsearchs('export_cust_svc',{ 'exportnum' => $self->exportnum,
+ 'svcnum' => $self->svcnum });
+ $self->SUPER::insert;
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid export option. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('exportcustsvcnum')
+ || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum')
+ || $self->ut_foreign_key('svcnum', 'cust_svc', 'svcnum')
+ || $self->ut_text('remoteid')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Possibly.
+
+=head1 SEE ALSO
+
+L<FS::part_export>, L<FS::cust_svc>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm
index daeff43..572a1b6 100644
--- a/FS/FS/part_export.pm
+++ b/FS/FS/part_export.pm
@@ -10,6 +10,7 @@ use FS::part_svc;
use FS::part_export_option;
use FS::part_export_machine;
use FS::svc_export_machine;
+use FS::export_cust_svc;
#for export modules, though they should probably just use it themselves
use FS::queue;
@@ -162,6 +163,17 @@ sub delete {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ # delete associated export_cust_svc
+ foreach my $export_cust_svc (
+ qsearch('export_cust_svc',{ 'exportnum' => $self->exportnum })
+ ) {
+ my $error = $export_cust_svc->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
# clean up export_nas records
my $error = $self->process_m2m(
'link_table' => 'export_nas',
@@ -637,6 +649,81 @@ sub _export_unsuspend {
$self->_export_replace( $svc_x, $old );
}
+=item get_remoteid SVC
+
+Returns the remote id for this export for the given service.
+
+=cut
+
+sub get_remoteid {
+ my ($self, $svc_x) = @_;
+
+ my $export_cust_svc = qsearchs('export_cust_svc',{
+ 'exportnum' => $self->exportnum,
+ 'svcnum' => $svc_x->svcnum
+ });
+
+ return $export_cust_svc ? $export_cust_svc->remoteid : '';
+}
+
+=item set_remoteid SVC VALUE
+
+Sets the remote id for this export for the given service.
+See L<FS::export_cust_svc>.
+
+If value is true, inserts or updates export_cust_svc record.
+If value is false, deletes any existing record.
+
+Returns error message, blank on success.
+
+=cut
+
+sub set_remoteid {
+ my ($self, $svc_x, $value) = @_;
+
+ my $export_cust_svc = qsearchs('export_cust_svc',{
+ 'exportnum' => $self->exportnum,
+ 'svcnum' => $svc_x->svcnum
+ });
+
+ 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 = '';
+ if ($value) {
+ if ($export_cust_svc) {
+ $export_cust_svc->set('remoteid',$value);
+ $error = $export_cust_svc->replace;
+ } else {
+ $export_cust_svc = new FS::export_cust_svc {
+ 'exportnum' => $self->exportnum,
+ 'svcnum' => $svc_x->svcnum,
+ 'remoteid' => $value
+ };
+ $error = $export_cust_svc->insert;
+ }
+ } else {
+ if ($export_cust_svc) {
+ $error = $export_cust_svc->delete;
+ } #otherwise, it already doesn't exist
+ }
+
+ if ($oldAutoCommit) {
+ $dbh->rollback if $error;
+ $dbh->commit unless $error;
+ }
+
+ return $error;
+}
+
=item export_links SVC_OBJECT ARRAYREF
Adds a list of web elements to ARRAYREF specific to this export and SVC_OBJECT.
diff --git a/FS/FS/part_export/ispconfig3.pm b/FS/FS/part_export/ispconfig3.pm
new file mode 100644
index 0000000..2878c51
--- /dev/null
+++ b/FS/FS/part_export/ispconfig3.pm
@@ -0,0 +1,361 @@
+package FS::part_export::ispconfig3;
+
+use strict;
+
+use base qw( FS::part_export );
+
+use Data::Dumper;
+use SOAP::Lite;
+
+=pod
+
+=head1 NAME
+
+FS::part_export::ispconfig3
+
+=head1 SYNOPSIS
+
+ISPConfig 3 integration for Freeside
+
+=head1 DESCRIPTION
+
+This export offers basic svc_acct provisioning for ISPConfig 3.
+All email accounts will be assigned to a single specified client.
+
+This module also provides generic methods for working through the L</ISPConfig3 API>.
+
+=cut
+
+use vars qw( %info );
+
+my @yesno = (
+ options => ['y','n'],
+ option_labels => { 'y' => 'yes', 'n' => 'no' },
+);
+
+tie my %options, 'Tie::IxHash',
+ 'soap_location' => { label => 'SOAP Location' },
+ 'username' => { label => 'User Name',
+ default => '' },
+ 'password' => { label => 'Password',
+ default => '' },
+ 'debug' => { type => 'checkbox',
+ label => 'Enable debug warnings' },
+ 'subheading' => { type => 'title',
+ label => 'Account defaults' },
+ 'client_id' => { label => 'Client ID' },
+ 'server_id' => { label => 'Server ID' },
+ 'maildir' => { label => 'Maildir (substitutions from svc_acct, e.g. /mail/$domain/$username)', },
+ 'cc' => { label => 'Cc' },
+ 'autoresponder_text' => { label => 'Autoresponder text',
+ default => 'Out of Office Reply' },
+ 'move_junk' => { type => 'select',
+ options => ['y','n'],
+ option_labels => { 'y' => 'yes', 'n' => 'no' },
+ label => 'Move junk' },
+ 'postfix' => { type => 'select',
+ @yesno,
+ label => 'Postfix' },
+ 'access' => { type => 'select',
+ @yesno,
+ label => 'Access' },
+ 'disableimap' => { type => 'select',
+ @yesno,
+ label => 'Disable IMAP' },
+ 'disablepop3' => { type => 'select',
+ @yesno,
+ label => 'Disable POP3' },
+ 'disabledeliver' => { type => 'select',
+ @yesno,
+ label => 'Disable deliver' },
+ 'disablesmtp' => { type => 'select',
+ @yesno,
+ label => 'Disable SMTP' },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Export email account to ISPConfig 3',
+ 'options' => \%options,
+ 'no_machine' => 1,
+ 'notes' => <<'END',
+All email accounts will be assigned to a single specified client and server.
+END
+);
+
+sub _mail_user_params {
+ my ($self, $svc_acct) = @_;
+ # all available api fields are in comments below, even if we don't use them
+ return {
+ #server_id (int(11))
+ 'server_id' => $self->option('server_id'),
+ #email (varchar(255))
+ 'email' => $svc_acct->username.'@'.$svc_acct->domain,
+ #login (varchar(255))
+ 'login' => $svc_acct->username.'@'.$svc_acct->domain,
+ #password (varchar(255))
+ 'password' => $svc_acct->_password,
+ #name (varchar(255))
+ 'name' => $svc_acct->finger,
+ #uid (int(11))
+ 'uid' => $svc_acct->uid,
+ #gid (int(11))
+ 'gid' => $svc_acct->gid,
+ #maildir (varchar(255))
+ 'maildir' => $self->_substitute($self->option('maildir'),$svc_acct),
+ #quota (bigint(20))
+ 'quota' => $svc_acct->quota,
+ #cc (varchar(255))
+ 'cc' => $self->option('cc'),
+ #homedir (varchar(255))
+ 'homedir' => $svc_acct->dir,
+
+ ## initializing with autoresponder off, but this could become an export option...
+ #autoresponder (enum('n','y'))
+ 'autoresponder' => 'n',
+ #autoresponder_start_date (datetime)
+ #autoresponder_end_date (datetime)
+ #autoresponder_text (mediumtext)
+ 'autoresponder_text' => $self->option('autoresponder_text'),
+
+ #move_junk (enum('n','y'))
+ 'move_junk' => $self->option('move_junk'),
+ #postfix (enum('n','y'))
+ 'postfix' => $self->option('postfix'),
+ #access (enum('n','y'))
+ 'access' => $self->option('access'),
+
+ ## not needed right now, not sure what it is
+ #custom_mailfilter (mediumtext)
+
+ #disableimap (enum('n','y'))
+ 'disableimap' => $self->option('disableimap'),
+ #disablepop3 (enum('n','y'))
+ 'disablepop3' => $self->option('disablepop3'),
+ #disabledeliver (enum('n','y'))
+ 'disabledeliver' => $self->option('disabledeliver'),
+ #disablesmtp (enum('n','y'))
+ 'disablesmtp' => $self->option('disablesmtp'),
+ };
+}
+
+sub _export_insert {
+ my ($self, $svc_acct) = @_;
+ my $params = $self->_mail_user_params($svc_acct);
+ $self->api_login;
+ my $remoteid = $self->api_call('mail_user_add',$self->option('client_id'),$params);
+ return $self->api_error_logout if $self->api_error;
+ my $error = $self->set_remoteid($svc_acct,$remoteid);
+ $error = "Remote system updated, but error setting remoteid ($remoteid): $error"
+ if $error;
+ $self->api_logout;
+ $error ||= "Systems updated, but error logging out: ".$self->api_error
+ if $self->api_error;
+ return $error;
+}
+
+sub _export_replace {
+ my ($self, $svc_acct, $svc_acct_old) = @_;
+ my $remoteid = $self->get_remoteid($svc_acct_old);
+ return "Could not load remoteid for old service" unless $remoteid;
+ my $params = $self->_mail_user_params($svc_acct);
+ #API docs claim "Returns the number of affected rows"
+ my $success = $self->api_call('mail_user_update',$self->option('client_id'),$remoteid,$params);
+ return $self->api_error_logout if $self->api_error;
+ return "Server returned no rows updated, but no other error message" unless $success;
+ my $error = '';
+ unless ($svc_acct->svcnum eq $svc_acct_old->svcnum) { # are these ever not equal?
+ $error = $self->set_remoteid($svc_acct,$remoteid);
+ $error = "Remote system updated, but error setting remoteid ($remoteid): $error"
+ if $error;
+ }
+ $self->api_logout;
+ $error ||= "Systems updated, but error logging out: ".$self->api_error
+ if $self->api_error;
+ return $error;
+}
+
+sub _export_delete {
+ my ($self, $svc_acct) = @_;
+ my $remoteid = $self->get_remoteid($svc_acct);
+ return "Could not load remoteid for old service" unless $remoteid;
+ #API docs claim "Returns the number of deleted records"
+ my $success = $self->api_call('mail_user_delete',$remoteid);
+ return $self->api_error_logout if $self->api_error;
+ my $error = $success ? '' : "Server returned no records deleted";
+ $self->api_logout;
+ $error ||= "Systems updated, but error logging out: ".$self->api_error
+ if $self->api_error;
+ return $error;
+}
+
+sub _export_suspend {
+ my ($self, $svc_acct) = @_;
+ return '';
+}
+
+sub _export_unsuspend {
+ my ($self, $svc_acct) = @_;
+ return '';
+}
+
+=head1 ISPConfig3 API
+
+These methods allow access to the ISPConfig3 API using the credentials
+set in the export options.
+
+=cut
+
+=head2 api_call
+
+Accepts I<$method> and I<@params>. Places an api call to the specified
+method with the specified params. Returns the result of that call
+(empty on failure.) Retrieve error messages using L</api_error>.
+
+Do not include session id in list of params; it will be included
+automatically. Must run L</api_login> first.
+
+=cut
+
+sub api_call {
+ my ($self,$method,@params) = @_;
+ $self->{'__ispconfig_response'} = undef;
+ # This does get used by api_login,
+ # to retrieve the session id after it sets the client,
+ # so we only check for existence of client,
+ # and we only include session id if we have one
+ my $client = $self->{'__ispconfig_client'};
+ unless ($client) {
+ $self->{'__ispconfig_error'} = 'Not logged in';
+ return;
+ }
+ if ($self->{'__ispconfig_session'}) {
+ unshift(@params,$self->{'__ispconfig_session'});
+ }
+ # Contact server in eval, to trap connection errors
+ warn "Calling SOAP method $method with params:\n".Dumper(\@params)."\n"
+ if $self->option('debug');
+ my $response = eval { $client->$method(@params) };
+ unless ($response) {
+ $self->{'__ispconfig_error'} = "Error contacting server: $@";
+ return;
+ }
+ # Set results and return
+ $self->{'__ispconfig_error'} = $response->fault
+ ? "Error from server: " . $response->faultstring
+ : '';
+ $self->{'__ispconfig_response'} = $response;
+ return $response->result;
+}
+
+=head2 api_error
+
+Returns the error string set by L</ISPConfig3 API> methods,
+or a blank string if most recent call produced no errors.
+
+=cut
+
+sub api_error {
+ my $self = shift;
+ return $self->{'__ispconfig_error'} || '';
+}
+
+=head2 api_error_logout
+
+Attempts L</api_logout>, but returns L</api_error> message from
+before logout was attempted. Useful for logging out
+properly after an error.
+
+=cut
+
+sub api_error_logout {
+ my $self = shift;
+ my $error = $self->api_error;
+ $self->api_logout;
+ return $error;
+}
+
+=head2 api_login
+
+Initializes an api session using the credentials for this export.
+Returns true on success, false on failure.
+Retrieve error messages using L</api_error>.
+
+=cut
+
+sub api_login {
+ my $self = shift;
+ if ($self->{'__ispconfig_session'} || $self->{'__ispconfig_client'}) {
+ $self->{'__ispconfig_error'} = 'Already logged in';
+ return;
+ }
+ $self->{'__ispconfig_session'} = undef;
+ $self->{'__ispconfig_client'} =
+ SOAP::Lite->proxy($self->option('soap_location'), ssl_opts => [ verify_hostname => 0 ] )
+ || undef;
+ unless ($self->{'__ispconfig_client'}) {
+ $self->{'__ispconfig_error'} = 'Error creating SOAP client';
+ return;
+ }
+ $self->{'__ispconfig_session'} =
+ $self->api_call('login',$self->option('username'),$self->option('password'))
+ || undef;
+ return unless $self->{'__ispconfig_session'};
+ return 1;
+}
+
+=head2 api_logout
+
+Ends the current api session established by L</api_login>.
+Returns true on success, false on failure.
+
+=cut
+
+sub api_logout {
+ my $self = shift;
+ unless ($self->{'__ispconfig_session'}) {
+ $self->{'__ispconfig_error'} = 'Not logged in';
+ return;
+ }
+ my $result = $self->api_call('logout');
+ # clear these even if there was a failure to logout
+ $self->{'__ispconfig_client'} = undef;
+ $self->{'__ispconfig_session'} = undef;
+ return if $self->api_error;
+ return 1;
+}
+
+# false laziness with portaone export
+sub _substitute {
+ my ($self, $string, @objects) = @_;
+ return '' unless $string;
+ foreach my $object (@objects) {
+ next unless $object;
+ my @fields = $object->fields;
+ push(@fields,'domain') if $object->table eq 'svc_acct';
+ foreach my $field (@fields) {
+ next unless $field;
+ my $value = $object->$field;
+ $string =~ s/\$$field/$value/g;
+ }
+ }
+ # strip leading/trailing whitespace
+ $string =~ s/^\s//g;
+ $string =~ s/\s$//g;
+ return $string;
+}
+
+=head1 SEE ALSO
+
+L<FS::part_export>
+
+=head1 AUTHOR
+
+Jonathan Prykop
+jonathan@freeside.biz
+
+=cut
+
+1;
+
+