summaryrefslogtreecommitdiff
path: root/FS/FS/part_export
diff options
context:
space:
mode:
Diffstat (limited to 'FS/FS/part_export')
-rw-r--r--FS/FS/part_export/acct_plesk.pm2
-rw-r--r--FS/FS/part_export/amazon_ec2.pm169
-rw-r--r--FS/FS/part_export/domreg_net_dri.pm614
-rw-r--r--FS/FS/part_export/domreg_opensrs.pm512
-rw-r--r--FS/FS/part_export/globalpops_voip.pm2
-rw-r--r--FS/FS/part_export/netsapiens.pm308
-rw-r--r--FS/FS/part_export/prizm.pm12
-rw-r--r--FS/FS/part_export/shellcommands.pm71
-rw-r--r--FS/FS/part_export/shellcommands_withdomain.pm26
-rw-r--r--FS/FS/part_export/www_plesk.pm2
10 files changed, 1701 insertions, 17 deletions
diff --git a/FS/FS/part_export/acct_plesk.pm b/FS/FS/part_export/acct_plesk.pm
index 1be820a..d8d70a3 100644
--- a/FS/FS/part_export/acct_plesk.pm
+++ b/FS/FS/part_export/acct_plesk.pm
@@ -23,7 +23,7 @@ Real-time export to
<a href="http://www.swsoft.com/">Plesk</a> managed server.
Requires installation of
<a href="http://search.cpan.org/dist/Net-Plesk">Net::Plesk</a>
-from CPAN.
+from CPAN and proper <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration:acct_plesk.pm">configuration</a>.
END
);
diff --git a/FS/FS/part_export/amazon_ec2.pm b/FS/FS/part_export/amazon_ec2.pm
new file mode 100644
index 0000000..0e65ca0
--- /dev/null
+++ b/FS/FS/part_export/amazon_ec2.pm
@@ -0,0 +1,169 @@
+package FS::part_export::amazon_ec2;
+
+use base qw( FS::part_export );
+
+use vars qw(@ISA %info $replace_ok_kludge);
+use Tie::IxHash;
+use FS::Record qw( qsearchs );
+use FS::svc_external;
+
+tie my %options, 'Tie::IxHash',
+ 'access_key' => { label => 'AWS access key', },
+ 'secret_key' => { label => 'AWS secret key', },
+ 'ami' => { label => 'AMI', 'default' => 'ami-ff46a796', },
+ 'keyname' => { label => 'Keypair name', },
+ #option to turn off (or on) ip address allocation
+;
+
+%info = (
+ 'svc' => 'svc_external',
+ 'desc' =>
+ 'Export to Amazon EC2',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Create instances in the Amazon EC2 (Elastic compute cloud). Install
+Net::Amazon::EC2 perl module. Advisable to set svc_external-skip_manual config
+option.
+END
+);
+
+$replace_ok_kludge = 0;
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_external) = (shift, shift);
+ $err_or_queue = $self->amazon_ec2_queue( $svc_external->svcnum, 'insert',
+ $svc_external->svcnum,
+ $self->option('ami'),
+ $self->option('keyname'),
+ );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return '' if $replace_ok_kludge;
+ return "can't change instance id or IP address";
+ #$err_or_queue = $self->amazon_ec2_queue( $new->svcnum,
+ # 'replace', $new->username, $new->_password );
+ #ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_delete {
+ my( $self, $svc_external ) = (shift, shift);
+ my( $instance_id, $ip ) = split(/:/, $svc_external->title );
+ $err_or_queue = $self->amazon_ec2_queue( $svc_external->svcnum, 'delete',
+ $instance_id,
+ $ip,
+ );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+#these three are optional
+# fallback for svc_acct will change and restore password
+#sub _export_suspend {
+# my( $self, $svc_something ) = (shift, shift);
+# $err_or_queue = $self->amazon_ec2_queue( $svc_something->svcnum,
+# 'suspend', $svc_something->username );
+# ref($err_or_queue) ? '' : $err_or_queue;
+#}
+#
+#sub _export_unsuspend {
+# my( $self, $svc_something ) = (shift, shift);
+# $err_or_queue = $self->amazon_ec2_queue( $svc_something->svcnum,
+# 'unsuspend', $svc_something->username );
+# ref($err_or_queue) ? '' : $err_or_queue;
+#}
+
+sub export_links {
+ my($self, $svc_external, $arrayref) = (shift, shift, shift);
+ my( $instance_id, $ip ) = split(/:/, $svc_external->title );
+
+ push @$arrayref, qq!<A HREF="http://$ip/">http://$ip/</A>!;
+ '';
+}
+
+###
+
+#a good idea to queue anything that could fail or take any time
+sub amazon_ec2_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::amazon_ec2::amazon_ec2_$method",
+ };
+ $queue->insert( $self->option('access_key'),
+ $self->option('secret_key'),
+ @_
+ )
+ or $queue;
+}
+
+sub amazon_ec2_new {
+ my( $access_key, $secret_key, @rest ) = @_;
+
+ eval 'use Net::Amazon::EC2;';
+ die $@ if $@;
+
+ my $ec2 = new Net::Amazon::EC2 'AWSAccessKeyId' => $access_key,
+ 'SecretAccessKey' => $secret_key;
+
+ ( $ec2, @rest );
+}
+
+sub amazon_ec2_insert { #subroutine, not method
+ my( $ec2, $svcnum, $ami, $keyname ) = amazon_ec2_new(@_);
+
+ my $reservation_info = $ec2->run_instances( 'ImageId' => $ami,
+ 'KeyName' => $keyname,
+ 'MinCount' => 1,
+ 'MaxCount' => 1,
+ );
+
+ my $instance_id = $reservation_info->instances_set->[0]->instance_id;
+
+ my $ip = $ec2->allocate_address
+ or die "can't allocate address";
+ $ec2->associate_address('InstanceId' => $instance_id,
+ 'PublicIp' => $ip,
+ )
+ or die "can't assocate IP address $ip with instance $instance_id";
+
+ my $svc_external = qsearchs('svc_external', { 'svcnum' => $svcnum } )
+ or die "can't find svc_external.svcnum $svcnum\n";
+
+ $svc_external->title("$instance_id:$ip");
+
+ local($replace_ok_kludge) = 1;
+ my $error = $svc_external->replace;
+ die $error if $error;
+
+}
+
+#sub amazon_ec2_replace { #subroutine, not method
+#}
+
+sub amazon_ec2_delete { #subroutine, not method
+ my( $ec2, $id, $ip ) = amazon_ec2_new(@_);
+
+ my $instance_id = sprintf('i-%x', $id);
+ $ec2->disassociate_address('PublicIp'=>$ip)
+ or die "can't dissassocate $ip";
+
+ $ec2->release_address('PublicIp'=>$ip)
+ or die "can't release $ip";
+
+ my $result = $ec2->terminate_instances('InstanceId'=>$instance_id);
+ #check for instance_id match or something?
+
+}
+
+#sub amazon_ec2_suspend { #subroutine, not method
+#}
+
+#sub amazon_ec2_unsuspend { #subroutine, not method
+#}
+
+1;
+
diff --git a/FS/FS/part_export/domreg_net_dri.pm b/FS/FS/part_export/domreg_net_dri.pm
new file mode 100644
index 0000000..bf01602
--- /dev/null
+++ b/FS/FS/part_export/domreg_net_dri.pm
@@ -0,0 +1,614 @@
+package FS::part_export::domreg_net_dri;
+
+use vars qw(@ISA %info %options $conf);
+use Tie::IxHash;
+use FS::part_export::null;
+
+=head1 NAME
+
+FS::part_export::domreg_net_dri - Register or transfer domains with Net::DRI
+
+=head1 DESCRIPTION
+
+This module handles registering and transferring domains with select registrars or registries supported
+by L<Net::DRI>.
+
+As a part_export, this module can be designated for use with svc_domain services. When the svc_domain object
+is inserted into the Freeside database, registration or transferring of the domain may be initiated, depending
+on the setting of the svc_domain's action field. Further operations can be performed from the View Domain screen.
+
+Logging information is written to the Freeside log folder.
+
+For correct operation you must add name/value pairs to the protcol and transport options fields. The setttings
+depend on the domain registry driver (DRD) selected.
+
+=over 4
+
+=item N - Register the domain
+
+=item M - Transfer the domain
+
+=item I - Ignore the domain for registration purposes
+
+=back
+
+=cut
+
+@ISA = qw(FS::part_export::null);
+
+my @tldlist = qw/com net org biz info name mobi at be ca cc ch cn de dk es eu fr it mx nl tv uk us/;
+
+my $opensrs_protocol_opts=<<'END';
+username=
+password=
+auto_renew=0
+affiliate_id=
+reseller_id=
+END
+
+my $opensrs_transport_opts=<<'END';
+client_login=
+client_password=
+END
+
+tie %options, 'Tie::IxHash',
+ 'drd' => { label => 'Domain Registry Driver (DRD)',
+ type => 'select',
+ options => [ qw/BookMyName CentralNic Gandi OpenSRS OVH VNDS/ ],
+ default => 'OpenSRS' },
+ 'log_level' => { label => 'Logging',
+ type => 'select',
+ options => [ qw/debug info notice warning error critical alert emergency/ ],
+ default => 'warning' },
+ 'protocol_opts' => {
+ label => 'Protocol Options',
+ type => 'textarea',
+ default => $opensrs_protocol_opts,
+ },
+ 'transport_opts' => {
+ label => 'Transport Options',
+ type => 'textarea',
+ default => $opensrs_transport_opts,
+ },
+# 'register' => { label => 'Use for registration',
+# type => 'checkbox',
+# default => '1' },
+# 'transfer' => { label => 'Use for transfer',
+# type => 'checkbox',
+# default => '1' },
+# 'delete' => { label => 'Use for deletion',
+# type => 'checkbox',
+# default => '1' },
+# 'renew' => { label => 'Use for renewals',
+# type => 'checkbox',
+# default => '1' },
+ 'tlds' => { label => 'Use this export for these top-level domains (TLDs)',
+ type => 'select',
+ multi => 1,
+ size => scalar(@tldlist),
+ options => [ @tldlist ],
+ default => 'com net org' },
+;
+
+my $opensrs_protocol_defaults = $opensrs_protocol_opts;
+$opensrs_protocol_defaults =~ s|\n|\\n|g;
+
+my $opensrs_transport_defaults = $opensrs_transport_opts;
+$opensrs_transport_defaults =~ s|\n|\\n|g;
+
+%info = (
+ 'svc' => 'svc_domain',
+ 'desc' => 'Domain registration via Net::DRI',
+ 'options' => \%options,
+ 'notes' => <<"END"
+Registers and transfers domains via a Net::DRI registrar or registry.
+<a href="http://search.cpan.org/search?dist=Net-DRI">Net::DRI</a>
+must be installed. You must have an account at the selected registrar/registry.
+<BR />
+Some top-level domains have additional business rules not supported by this export. These TLDs cannot be registered or transfered with this export.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS Live System (rr-n1-tor.opensrs.net)" onClick='
+ document.dummy.machine.value = "rr-n1-tor.opensrs.net";
+ this.form.machine.value = "rr-n1-tor.opensrs.net";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS Test System (horizon.opensrs.net)" onClick='
+ document.dummy.machine.value = "horizon.opensrs.net";
+ this.form.machine.value = "horizon.opensrs.net";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS protocol/transport options" onClick='
+ this.form.protocol_opts.value = "$opensrs_protocol_defaults";
+ this.form.transport_opts.value = "$opensrs_transport_defaults";
+ '>
+</UL>
+END
+);
+
+install_callback FS::UID sub {
+ $conf = new FS::Conf;
+};
+
+#sub rebless { shift; }
+
+# experiment: want the status of these right away, so no queueing
+
+sub _export_insert {
+ my( $self, $svc_domain ) = ( shift, shift );
+
+ return if $svc_domain->action eq 'I'; # Ignoring registration, just doing DNS
+
+ if ($svc_domain->action eq 'N') {
+ return $self->register( $svc_domain );
+ } elsif ($svc_domain->action eq 'M') {
+ return $self->transfer( $svc_domain );
+ }
+ return "Unknown domain action " . $svc_domain->action;
+}
+
+=item get_portfolio_credentials
+
+Returns, in list context, the user name and password for the domain portfolio.
+
+This is currently specified via the username and password keys in the protocol options.
+
+=cut
+
+sub get_portfolio_credentials {
+ my $self = shift;
+
+ my %opts = $self->get_protocol_options();
+ return ($opts{username}, $opts{password});
+}
+
+=item format_tel
+
+Reformats a phone number according to registry rules. Currently Freeside stores phone numbers
+in NANPA format and most registries prefer "+CCC.NPANPXNNNN"
+
+=cut
+
+sub format_tel {
+ my $tel = shift;
+
+ #if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})\s*(x\s*(\d+))?$/) {
+ if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})$/) {
+ $tel = "+1.$1$2$3"; # TBD: other country codes
+# if $tel .= "$4" if $4;
+ }
+ return $tel;
+}
+
+sub gen_contact_set {
+ my ($self, $dri, $cust_main) = @_;
+
+ my @invoicing_list = $cust_main->invoicing_list_emailonly;
+ if ( $conf->exists('emailinvoiceautoalways')
+ || $conf->exists('emailinvoiceauto') && ! @invoicing_list
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $cust_main->all_emails;
+ }
+
+ my $email = ($conf->exists('business-onlinepayment-email-override'))
+ ? $conf->config('business-onlinepayment-email-override')
+ : $invoicing_list[0];
+
+ my $cs=$dri->local_object('contactset');
+ my $co=$dri->local_object('contact');
+
+ my ($user, $pass) = $self->get_portfolio_credentials();
+
+ $co->srid($user); # Portfolio user name for OpenSRS?
+ $co->auth($pass); # Portfolio password for OpenSRS?
+
+ $co->firstname($cust_main->first);
+ $co->name($cust_main->last);
+ $co->org($cust_main->company || '-');
+ $co->street([$cust_main->address1, $cust_main->address2]);
+ $co->city($cust_main->city);
+ $co->sp($cust_main->state);
+ $co->pc($cust_main->zip);
+ $co->cc($cust_main->country);
+ $co->voice(format_tel($cust_main->daytime()));
+ $co->email($email);
+
+ $cs->set($co, 'registrant');
+ $cs->set($co, 'admin');
+ $cs->set($co, 'billing');
+
+ return $cs;
+}
+
+=item validate_contact_set
+
+Attempts to validate contact data for the domain based on OpenSRS rules.
+
+Returns undef if the contact data is acceptable, an error message if the contact
+data lacks one or more required fields.
+
+=cut
+
+sub validate_contact_set {
+ my $c = shift;
+
+ my %fields = (
+ firstname => "first name",
+ name => "last name",
+ street => "street address",
+ city => "city",
+ sp => "state",
+ pc => "ZIP/postal code",
+ cc => "country",
+ email => "email address",
+ voice => "phone number",
+ );
+ my @err = ();
+ foreach my $which (qw/registrant admin billing/) {
+ my $co = $c->get($which);
+ foreach (keys %fields) {
+ if (!$co->$_()) {
+ push @err, $fields{$_};
+ }
+ }
+ }
+ if (scalar(@err) > 0) {
+ return "Contact information needs: " . join(', ', @err);
+ }
+ undef;
+}
+
+#sub _export_replace {
+# my( $self, $new, $old ) = (shift, shift, shift);
+#
+# return '';
+#
+#}
+
+## Domain registration exports do nothing on delete. You're just removing the domain from Freeside, not the registry
+#sub _export_delete {
+# my( $self, $www ) = ( shift, shift );
+#
+# return '';
+#}
+
+=item split_textarea_options
+
+Split textarea contents into lines, split lines on =, and then trim the results;
+
+=cut
+
+sub split_textarea_options {
+ my ($self, $optname) = @_;
+ my %opts = map {
+ my ($key, $value) = split /=/, $_;
+ $key =~ s/^\s*//;
+ $key =~ s/\s*$//;
+ $value =~ s/^\s*//;
+ $value =~ s/\s*$//;
+ $key => $value } split /\n/, $self->option($optname);
+ %opts;
+}
+
+=item get_protocol_options
+
+Return a hash of protocol options
+
+=cut
+
+sub get_protocol_options {
+ my $self = shift;
+ my %opts = $self->split_textarea_options('protocol_opts');
+ if ($self->machine =~ /opensrs\.net/) {
+ my %topts = $self->get_transport_options;
+ $opts{reseller_id} = $topts{client_login};
+ }
+ %opts;
+}
+
+=item get_transport_options
+
+Return a hash of transport options
+
+=cut
+
+sub get_transport_options {
+ my $self = shift;
+ my %opts = $self->split_textarea_options('transport_opts');
+ $opts{remote_url} = "https://" . $self->machine . ":55443/resellers" if $self->machine =~ /opensrs\.net/;
+ %opts;
+}
+
+=item is_supported_domain
+
+Return undef if the domain name uses a TLD or SLD that is supported by this registrar.
+Otherwise return an error message explaining what's wrong.
+
+=cut
+
+sub is_supported_domain {
+ my $self = shift;
+ my $svc_domain = shift;
+
+ # Get the TLD of the new domain
+ my @bits = split /\./, $svc_domain->domain;
+
+ return "Can't register subdomains: " . $svc_domain->domain if scalar(@bits) != 2;
+
+ my $tld = pop @bits;
+
+ # See if it's one this export supports
+ my @tlds = split /\s+/, $self->option('tlds');
+ @tlds = map { s/\.//; $_ } @tlds;
+ return "Can't register top-level domain $tld, restricted to: " . $self->option('tlds') if ! grep { $_ eq $tld } @tlds;
+ return undef;
+}
+
+=item get_dri
+
+=cut
+
+sub get_dri {
+ my $self = shift;
+ my $dri;
+
+# return $self->{dri} if $self->{dri}; #!!!TBD!!! connection caching.
+
+ eval "use Net::DRI 0.95;";
+ return $@ if $@;
+
+# $dri=Net::DRI->new(...) to create the global object. Save the result,
+
+ eval {
+ #$dri = Net::DRI::TrapExceptions->new(10);
+ $dri = Net::DRI->new({logging => [ 'files', { output_directory => '%%%FREESIDE_LOG%%%' } ]}); #!!!TBD!!!
+ $dri->logging->level( $self->option('log_level') );
+ $dri->add_registry( $self->option('drd') );
+ my $protocol;
+ $protocol = 'xcp' if $self->option('drd') eq 'OpenSRS';
+
+ $dri->target( $self->option('drd') )->add_current_profile($self->option('drd') . '1',
+# 'Net::DRI::Protocol::' . $self->option('protocol_type'),
+# $self->option('protocol_type'),
+# 'xcp', #TBD!!!!
+ $protocol, # Implies transport
+# 'Net::DRI::Transport::' . $self->option('transport_type'),
+ { $self->get_transport_options() },
+# [ $self->get_protocol_options() ]
+ );
+ };
+ return $@ if $@;
+
+ $self->{dri} = $dri;
+ return $dri;
+}
+
+=item get_status
+
+Returns a reference to a hashref containing information on the domain's status. The keys
+defined depend on the status.
+
+'unregistered' means the domain is not registered.
+
+Otherwise, if the domain is in an asynchronous operation such as a transfer, returns the state
+of that operation.
+
+Otherwise returns a value indicating if the domain can be managed through our reseller account.
+
+=cut
+
+sub get_status {
+ my ( $self, $svc_domain ) = @_;
+ my $rc;
+ my $rslt = {};
+
+ my $dri = $self->get_dri;
+
+ if (UNIVERSAL::isa($dri, 'Net::DRI::Exception')) {
+ $rslt->{'message'} = $dri->as_string;
+ return $rslt;
+ }
+ eval {
+ $rc = $dri->domain_check( $svc_domain->domain );
+ if (!$rc->is_success()) {
+ # Problem accessing the registry/registrar
+ $rslt->{'message'} = $rc->message;
+ } elsif (!$dri->get_info('exist')) {
+ # Domain is not registered
+ $rslt->{'unregistered'} = 1;
+ } else {
+ $rc = $dri->domain_transfer_query( $svc_domain->domain );
+ if ($rc->is_success() && $dri->get_info('status')) {
+ # Transfer in progress
+ $rslt->{status} = $dri->get_info('status');
+ $rslt->{contact_email} = $dri->get_info('request_address');
+ $rslt->{last_update_time} = $dri->get_info('unixtime');
+ } elsif ($dri->get_info('reason')) {
+ $rslt->{'reason'} = $dri->get_info('reason');
+ # Domain is not being transferred...
+ $rc = $dri->domain_info( $svc_domain->domain, { $self->get_protocol_options() } );
+ if ($rc->is_success() && $dri->get_info('exDate')) {
+ $rslt->{'expdate'} = $dri->get_info('exDate');
+ }
+ } else {
+ $rslt->{status} = 'Unknown';
+ }
+ }
+ };
+# rslt->{'message'} = $@->as_string if $@;
+ if ($@) {
+ $rslt->{'message'} = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->as_string : $@->message;
+ }
+
+ return $rslt; # Success
+}
+
+=item register
+
+Attempts to register the domain through the reseller account associated with this export.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub register {
+ my ( $self, $svc_domain, $years ) = @_;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $dri = $self->get_dri;
+ return $dri->as_string if (UNIVERSAL::isa($dri, 'Net::DRI::Exception'));
+
+ eval { # All $dri methods can throw an exception.
+
+# Call methods
+ my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
+
+ my $cs = $self->gen_contact_set($dri, $cust_main);
+
+ $err = validate_contact_set($cs);
+ return $err if $err;
+
+# !!!TBD!!! add custom name servers when supported; add ns => $ns to hash passed to domain_create
+
+ $res = $dri->domain_create($svc_domain->domain, { $self->get_protocol_options(), pure_create => 1, contact => $cs, duration => DateTime::Duration->new(years => $years) });
+ $err = $res->is_success ? '' : $res->message;
+ };
+ if ($@) {
+ $err = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->msg : $@->message;
+ }
+
+ return $err;
+}
+
+=item transfer
+
+Attempts to transfer the domain into the reseller account associated with this export.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub transfer {
+ my ( $self, $svc_domain ) = @_;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+# $dri=Net::DRI->new(...) to create the global object. Save the result,
+ my $dri = $self->get_dri;
+ return $dri->as_string if (UNIVERSAL::isa($dri, 'Net::DRI::Exception'));
+
+ eval { # All $dri methods can throw an exception
+
+# Call methods
+ my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
+
+ my $cs = $self->gen_contact_set($dri, $cust_main);
+
+ $err = validate_contact_set($cs);
+ return $err if $err;
+
+# !!!TBD!!! add custom name servers when supported; add ns => $ns to hash passed to domain_transfer_start
+
+ $res = $dri->domain_transfer_start($svc_domain->domain, { $self->get_protocol_options(), contact => $cs });
+ $err = $res->is_success ? '' : $res->message;
+ };
+ if ($@) {
+ $err = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->msg : $@->message;
+ }
+
+ return $err;
+}
+
+=item renew
+
+Attempts to renew the domain for the specified number of years.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub renew {
+ my ( $self, $svc_domain, $years ) = @_;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $dri = $self->get_dri;
+ return $dri->as_string if (UNIVERSAL::isa($dri, 'Net::DRI::Exception'));
+
+ eval { # All $dri methods can throw an exception
+ my $expdate;
+ my $res = $dri->domain_info( $svc_domain->domain, { $self->get_protocol_options() } );
+ if ($res->is_success() && $dri->get_info('exDate')) {
+ $expdate = $dri->get_info('exDate');
+
+# return "Domain renewal not enabled" if !$self->option('renew');
+ $res = $dri->domain_renew( $svc_domain->domain, { $self->get_protocol_options(), duration => DateTime::Duration->new(years => $years), current_expiration => $expdate });
+ }
+ $err = $res->is_success ? '' : $res->message;
+ };
+ if ($@) {
+ $err = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->msg : $@->message;
+ }
+
+ return $err;
+}
+
+=item revoke
+
+Attempts to revoke the domain registration. Only succeeds if invoked during the DRI
+grace period immediately after registration.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub revoke {
+ my ( $self, $svc_domain ) = @_;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $dri = $self->get_dri;
+ return $dri->as_string if (UNIVERSAL::isa($dri, 'Net::DRI::Exception'));
+
+ eval { # All $dri methods can throw an exception
+
+# return "Domain registration revocation not enabled" if !$self->option('revoke');
+ my $res = $dri->domain_delete( $svc_domain->domain, { $self->get_protocol_options(), domain => $svc_domain->domain, pure_delete => 1 });
+ $err = $res->is_success ? '' : $res->message;
+ };
+ if ($@) {
+ $err = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->msg : $@->message;
+ }
+
+ return $err;
+}
+
+=item registrar
+
+Should return a full-blown object representing the Net::DRI DRD, but current just returns a hashref
+containing the registrar name.
+
+=cut
+
+sub registrar {
+ my $self = shift;
+ return {
+ name => $self->option('drd'),
+ };
+}
+
+=head1 SEE ALSO
+
+L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_domain>,
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_export/domreg_opensrs.pm b/FS/FS/part_export/domreg_opensrs.pm
new file mode 100644
index 0000000..1799ed0
--- /dev/null
+++ b/FS/FS/part_export/domreg_opensrs.pm
@@ -0,0 +1,512 @@
+package FS::part_export::domreg_opensrs;
+
+use vars qw(@ISA %info %options $conf);
+use Tie::IxHash;
+use FS::Record qw(qsearchs qsearch);
+use FS::Conf;
+use FS::part_export::null;
+use FS::svc_domain;
+use FS::part_pkg;
+
+=head1 NAME
+
+FS::part_export::domreg_opensrs - Register or transfer domains with Tucows OpenSRS
+
+=head1 DESCRIPTION
+
+This module handles registering and transferring domains using a registration service provider (RSP) account
+at Tucows OpenSRS, an ICANN-approved domain registrar.
+
+As a part_export, this module can be designated for use with svc_domain services. When the svc_domain object
+is inserted into the Freeside database, registration or transferring of the domain may be initiated, depending
+on the setting of the svc_domain's action field.
+
+=over 4
+
+=item N - Register the domain
+
+=item M - Transfer the domain
+
+=item I - Ignore the domain for registration purposes
+
+=back
+
+This export uses Net::OpenSRS. Registration and transfer attempts will fail unless Net::OpenSRS is installed
+and LWP::UserAgent is able to make HTTPS posts. You can turn on debugging messages and use the OpenSRS test
+gateway when setting up this export.
+
+=cut
+
+@ISA = qw(FS::part_export::null);
+
+my @tldlist = qw/com net org biz info name mobi at be ca cc ch cn de dk es eu fr it mx nl tv uk us/;
+
+tie %options, 'Tie::IxHash',
+ 'username' => { label => 'Reseller user name at OpenSRS',
+ },
+ 'privatekey' => { label => 'Private key',
+ },
+ 'password' => { label => 'Password for management account',
+ },
+ 'masterdomain' => { label => 'Master domain at OpenSRS',
+ },
+ 'debug_level' => { label => 'Net::OpenSRS debug level',
+ type => 'select',
+ options => [ 0, 1, 2, 3 ],
+ default => 0 },
+# 'register' => { label => 'Use for registration',
+# type => 'checkbox',
+# default => '1' },
+# 'transfer' => { label => 'Use for transfer',
+# type => 'checkbox',
+# default => '1' },
+ 'tlds' => { label => 'Use this export for these top-level domains (TLDs)',
+ type => 'select',
+ multi => 1,
+ size => scalar(@tldlist),
+ options => [ @tldlist ],
+ default => 'com net org' },
+;
+
+%info = (
+ 'svc' => 'svc_domain',
+ 'desc' => 'Domain registration via Tucows OpenSRS',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Registers and transfers domains via the <a href="http://opensrs.com/">Tucows OpenSRS</a> registrar (using <a href="http://search.cpan.org/dist/Net-OpenSRS">Net::OpenSRS</a>).
+All of the Net::OpenSRS restrictions apply:
+<UL>
+ <LI>You must have a reseller account with Tucows.
+ <LI>You must add the public IP address of the Freeside server to the 'Script API allow' list in the OpenSRS web interface.
+ <LI>You must generate an API access key in the OpenSRS web interface and enter it below.
+ <LI>All domains are managed using the same user name and password, but you can create sub-accounts for clients.
+ <LI>The user name must be the same as your OpenSRS reseller ID.
+ <LI>You must enter a master domain that all other domains are associated with. That domain must be registered through your OpenSRS account.
+</UL>
+Some top-level domains offered by OpenSRS have additional business rules not supported by this export. These TLDs cannot be registered or transfered with this export.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS Live System (rr-n1-tor.opensrs.net)" onClick='
+ document.dummy.machine.value = "rr-n1-tor.opensrs.net";
+ this.form.machine.value = "rr-n1-tor.opensrs.net";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS Test System (horizon.opensrs.net)" onClick='
+ document.dummy.machine.value = "horizon.opensrs.net";
+ this.form.machine.value = "horizon.opensrs.net";
+ '>
+</UL>
+END
+);
+
+install_callback FS::UID sub {
+ $conf = new FS::Conf;
+};
+
+=head1 METHODS
+
+=over 4
+
+=item format_tel
+
+Reformats a phone number according to registry rules. Currently Freeside stores phone numbers
+in NANPA format and the registry prefers "+CCC.NPANPXNNNN"
+
+=cut
+
+sub format_tel {
+ my $tel = shift;
+
+ #if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})( x(\d+))?$/) {
+ if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})$/) {
+ $tel = "+1.$1$2$3";
+# if $tel .= "$4" if $4;
+ }
+ return $tel;
+}
+
+=item gen_contact_info
+
+Generates contact data for the domain based on the customer data.
+
+Currently relies on Net::OpenSRS to format the telephone number for OpenSRS.
+
+=cut
+
+sub gen_contact_info
+{
+ my ($co)=@_;
+
+ my @invoicing_list = $co->invoicing_list_emailonly;
+ if ( $conf->exists('emailinvoiceautoalways')
+ || $conf->exists('emailinvoiceauto') && ! @invoicing_list
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $co->all_emails;
+ }
+
+ my $email = ($conf->exists('business-onlinepayment-email-override'))
+ ? $conf->config('business-onlinepayment-email-override')
+ : $invoicing_list[0];
+
+ my $c = {
+ firstname => $co->first,
+ lastname => $co->last,
+ company => $co->company,
+ address => $co->address1,
+ city => $co->city(),
+ state => $co->state(),
+ zip => $co->zip(),
+ country => uc($co->country()),
+ email => $email,
+ #phone => format_tel($co->daytime()),
+ phone => $co->daytime() || $co->night,
+ };
+ return $c;
+}
+
+=item validate_contact_info
+
+Attempts to validate contact data for the domain based on OpenSRS rules.
+
+Returns undef if the contact data is acceptable, an error message if the contact
+data lacks one or more required fields.
+
+=cut
+
+sub validate_contact_info {
+ my $c = shift;
+
+ my %fields = (
+ firstname => "first name",
+ lastname => "last name",
+ address => "street address",
+ city => "city",
+ state => "state",
+ zip => "ZIP/postal code",
+ country => "country",
+ email => "email address",
+ phone => "phone number",
+ );
+ my @err = ();
+ foreach (keys %fields) {
+ if (!defined($c->{$_}) || !$c->{$_}) {
+ push @err, $fields{$_};
+ }
+ }
+ if (scalar(@err) > 0) {
+ return "Contact information needs: " . join(', ', @err);
+ }
+ undef;
+}
+
+=item testmode
+
+Returns the Net::OpenSRS-required test mode string based on whether the export
+is configured to use the live or the test gateway.
+
+=cut
+
+sub testmode {
+ my $self = shift;
+
+ return 'live' if $self->machine eq "rr-n1-tor.opensrs.net";
+ return 'test' if $self->machine eq "horizon.opensrs.net";
+ undef;
+}
+
+=item _export_insert
+
+Attempts to "export" the domain, i.e. register or transfer it if the user selected
+that option when editing the domain.
+
+Returns an error message on failure or undef on success.
+
+May also return an error message if it cannot load the required Perl module Net::OpenSRS,
+or if the domain is not registerable, or if insufficient data is provided in the customer
+record to generate the required contact information to register or transfer the domain.
+
+=cut
+
+sub _export_insert {
+ my( $self, $svc_domain ) = ( shift, shift );
+
+ return if $svc_domain->action eq 'I'; # Ignoring registration, just doing DNS
+
+ if ($svc_domain->action eq 'N') {
+ return $self->register( $svc_domain );
+ } elsif ($svc_domain->action eq 'M') {
+ return $self->transfer( $svc_domain );
+ }
+ return "Unknown domain action " . $svc_domain->action;
+}
+
+## Domain registration exports do nothing on replace. Mainly because we haven't decided what they should do.
+#sub _export_replace {
+# my( $self, $new, $old ) = (shift, shift, shift);
+#
+# return '';
+#
+#}
+
+## Domain registration exports do nothing on delete. You're just removing the domain from Freeside, not the registry
+#sub _export_delete {
+# my( $self, $svc_domain ) = ( shift, shift );
+#
+# return '';
+#}
+
+=item is_supported_domain
+
+Return undef if the domain name uses a TLD or SLD that is supported by this registrar.
+Otherwise return an error message explaining what's wrong.
+
+=cut
+
+sub is_supported_domain {
+ my $self = shift;
+ my $svc_domain = shift;
+
+ # Get the TLD of the new domain
+ my @bits = split /\./, $svc_domain->domain;
+
+ return "Can't register subdomains: " . $svc_domain->domain if scalar(@bits) != 2;
+
+ my $tld = pop @bits;
+
+ # See if it's one this export supports
+ my @tlds = split /\s+/, $self->option('tlds');
+ @tlds = map { s/\.//; $_ } @tlds;
+ return "Can't register top-level domain $tld, restricted to: " . $self->option('tlds') if ! grep { $_ eq $tld } @tlds;
+ return undef;
+}
+
+=item get_srs
+
+=cut
+
+sub get_srs {
+ my $self = shift;
+
+ my $srs = Net::OpenSRS->new();
+
+ $srs->debug_level( $self->option('debug_level') ); # Output should be in the Apache error log
+
+ $srs->environment( $self->testmode() );
+ $srs->set_key( $self->option('privatekey') );
+
+ $srs->set_manage_auth( $self->option('username'), $self->option('password') );
+ return $srs;
+}
+
+=item get_status
+
+Returns a reference to a hashref containing information on the domain's status. The keys
+defined depend on the status.
+
+'unregistered' means the domain is not registered.
+
+Otherwise, if the domain is in an asynchronous operation such as a transfer, returns the state
+of that operation.
+
+Otherwise returns a value indicating if the domain can be managed through our reseller account.
+
+=cut
+
+sub get_status {
+ my ( $self, $svc_domain ) = @_;
+ my $rslt = {};
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $srs = $self->get_srs;
+
+ if ($srs->is_available( $svc_domain->domain )) {
+ $rslt->{'unregistered'} = 1;
+ } else {
+ $rslt = $srs->check_transfer( $svc_domain->domain );
+ if (defined($rslt->{'reason'})) {
+ my $rv = $srs->make_request(
+ {
+ action => 'belongs_to_rsp',
+ object => 'domain',
+ attributes => {
+ domain => $svc_domain->domain
+ }
+ }
+ );
+ if ($rv) {
+ $self->_set_response;
+ if ( $rv->{attributes}->{'domain_expdate'} ) {
+ $rslt->{'expdate'} = $rv->{attributes}->{'domain_expdate'};
+ }
+ }
+ }
+ }
+
+ return $rslt; # Success
+}
+
+=item register
+
+Attempts to register the domain through the reseller account associated with this export.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub register {
+ my ( $self, $svc_domain, $years ) = @_;
+
+ return "Net::OpenSRS does not support period other than 1 year" if $years != 1;
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
+
+ my $c = gen_contact_info($cust_main);
+
+ $err = validate_contact_info($c);
+ return $err if $err;
+
+ my $srs = $self->get_srs;
+
+ my $cookie = $srs->get_cookie( $self->option('masterdomain') );
+ if (!$cookie) {
+ return "Unable to get cookie at OpenSRS: " . $srs->last_response();
+ }
+
+# return "Domain registration not enabled" if !$self->option('register');
+ return $srs->last_response() if !$srs->register_domain( $svc_domain->domain, $c);
+
+ return ''; # Should only get here if register succeeded
+}
+
+=item transfer
+
+Attempts to transfer the domain into the reseller account associated with this export.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub transfer {
+ my ( $self, $svc_domain ) = @_;
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
+
+ my $c = gen_contact_info($cust_main);
+
+ $err = validate_contact_info($c);
+ return $err if $err;
+
+ my $srs = $self->get_srs;
+
+ my $cookie = $srs->get_cookie( $self->option('masterdomain') );
+ if (!$cookie) {
+ return "Unable to get cookie at OpenSRS: " . $srs->last_response();
+ }
+
+# return "Domain transfer not enabled" if !$self->option('transfer');
+ return $srs->last_response() if !$srs->transfer_domain( $svc_domain->domain, $c);
+
+ return ''; # Should only get here if transfer succeeded
+}
+
+=item renew
+
+Attempts to renew the domain for the specified number of years.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub renew {
+ my ( $self, $svc_domain, $years ) = @_;
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $srs = $self->get_srs;
+
+ my $cookie = $srs->get_cookie( $self->option('masterdomain') );
+ if (!$cookie) {
+ return "Unable to get cookie at OpenSRS: " . $srs->last_response();
+ }
+
+# return "Domain renewal not enabled" if !$self->option('renew');
+ return $srs->last_response() if !$srs->renew_domain( $svc_domain->domain, $years );
+
+ return ''; # Should only get here if renewal succeeded
+}
+
+=item revoke
+
+Attempts to revoke the domain registration. Only succeeds if invoked during the OpenSRS
+grace period immediately after registration.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub revoke {
+ my ( $self, $svc_domain ) = @_;
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $srs = $self->get_srs;
+
+ my $cookie = $srs->get_cookie( $self->option('masterdomain') );
+ if (!$cookie) {
+ return "Unable to get cookie at OpenSRS: " . $srs->last_response();
+ }
+
+# return "Domain registration revocation not enabled" if !$self->option('revoke');
+ return $srs->last_response() if !$srs->revoke_domain( $svc_domain->domain);
+
+ return ''; # Should only get here if transfer succeeded
+}
+
+=item registrar
+
+Should return a full-blown object representing OpenSRS, but current just returns a hashref
+containing the registrar name.
+
+=cut
+
+sub registrar {
+ return {
+ name => 'OpenSRS',
+ };
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<Net::OpenSRS>, L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_domain>,
+L<FS::Record>, schema.html from the base documentation.
+
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_export/globalpops_voip.pm b/FS/FS/part_export/globalpops_voip.pm
index 3bd5783..67b48bb 100644
--- a/FS/FS/part_export/globalpops_voip.pm
+++ b/FS/FS/part_export/globalpops_voip.pm
@@ -32,7 +32,7 @@ sub get_dids {
my $self = shift;
my %opt = ref($_[0]) ? %{$_[0]} : @_;
- my %search = ();
+ my %getdids = ();
# 'orderby' => 'npa', #but it doesn't seem to work :/
if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
diff --git a/FS/FS/part_export/netsapiens.pm b/FS/FS/part_export/netsapiens.pm
new file mode 100644
index 0000000..cf4b5e3
--- /dev/null
+++ b/FS/FS/part_export/netsapiens.pm
@@ -0,0 +1,308 @@
+package FS::part_export::netsapiens;
+
+use vars qw(@ISA $me %info);
+use URI;
+use MIME::Base64;
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+$me = '[FS::part_export::netsapiens]';
+
+tie my %options, 'Tie::IxHash',
+ 'login' => { label=>'NetSapiens tac2 User API username' },
+ 'password' => { label=>'NetSapiens tac2 User API password' },
+ 'url' => { label=>'NetSapiens tac2 User URL' },
+ 'device_login' => { label=>'NetSapiens tac2 Device API username' },
+ 'device_password' => { label=>'NetSapiens tac2 Device API password' },
+ 'device_url' => { label=>'NetSapiens tac2 Device URL' },
+ 'domain' => { label=>'NetSapiens Domain' },
+ 'debug' => { label=>'Enable debugging', type=>'checkbox' },
+;
+
+%info = (
+ 'svc' => 'svc_phone',
+ 'desc' => 'Provision phone numbers to NetSapiens',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Requires installation of
+<a href="http://search.cpan.org/dist/REST-Client">REST::Client</a>
+from CPAN.
+END
+);
+
+sub rebless { shift; }
+
+sub ns_command {
+ my $self = shift;
+ $self->_ns_command('', @_);
+}
+
+sub ns_device_command {
+ my $self = shift;
+ $self->_ns_command('device_', @_);
+}
+
+sub _ns_command {
+ my( $self, $prefix, $method, $command ) = splice(@_,0,4);
+
+ eval 'use REST::Client';
+ die $@ if $@;
+
+ my $ns = new REST::Client 'host'=>$self->option($prefix.'url');
+
+ my @args = ( $command );
+
+ if ( $method eq 'PUT' ) {
+ my $content = $ns->buildQuery( { @_ } );
+ $content =~ s/^\?//;
+ push @args, $content;
+ } elsif ( $method eq 'GET' ) {
+ $args[0] .= $ns->buildQuery( { @_ } );
+ }
+
+ warn "$me $method ". $self->option($prefix.'url').
+ " $command ". join(', ', @_). "\n"
+ if $self->option('debug');
+
+ my $auth = encode_base64( $self->option($prefix.'login'). ':'.
+ $self->option($prefix.'password') );
+ push @args, { 'Authorization' => "Basic $auth" };
+
+ $ns->$method( @args );
+ $ns;
+}
+
+sub ns_subscriber {
+ my($self, $svc_phone) = (shift, shift);
+
+ my $domain = $self->option('domain');
+ my $phonenum = $svc_phone->phonenum;
+
+ "/domains_config/$domain/subscriber_config/$phonenum";
+}
+
+sub ns_registrar {
+ my($self, $svc_phone) = (shift, shift);
+
+ $self->ns_subscriber($svc_phone).
+ '/registrar_config/'. $self->ns_devicename($svc_phone);
+}
+
+sub ns_devicename {
+ my( $self, $svc_phone ) = (shift, shift);
+
+ my $domain = $self->option('domain');
+ #my $countrycode = $svc_phone->countrycode;
+ my $phonenum = $svc_phone->phonenum;
+
+ #"sip:$countrycode$phonenum\@$domain";
+ "sip:$phonenum\@$domain";
+}
+
+sub ns_dialplan {
+ my($self, $svc_phone) = (shift, shift);
+
+ #my $countrycode = $svc_phone->countrycode;
+ my $phonenum = $svc_phone->phonenum;
+
+ #"/dialplans/DID+Table/dialplan_config/sip:$countrycode$phonenum\@*"
+ "/dialplans/DID+Table/dialplan_config/sip:$phonenum\@*"
+}
+
+sub ns_device {
+ my($self, $svc_phone, $phone_device ) = (shift, shift, shift);
+
+ #my $countrycode = $svc_phone->countrycode;
+ #my $phonenum = $svc_phone->phonenum;
+
+ "/phones_config/". lc($phone_device->mac_addr);
+}
+
+sub ns_create_or_update {
+ my($self, $svc_phone, $dial_policy) = (shift, shift, shift);
+
+ my $domain = $self->option('domain');
+ #my $countrycode = $svc_phone->countrycode;
+ my $phonenum = $svc_phone->phonenum;
+
+ my( $firstname, $lastname );
+ if ( $svc_phone->phone_name =~ /^\s*(\S+)\s+(\S.*\S)\s*$/ ) {
+ $firstname = $1;
+ $lastname = $2;
+ } else {
+ #deal w/unaudited netsapiens services?
+ my $cust_main = $svc_phone->cust_svc->cust_pkg->cust_main;
+ $firstname = $cust_main->get('first');
+ $lastname = $cust_main->get('last');
+ }
+
+ # Piece 1 (already done) - User creation
+
+ my $ns = $self->ns_command( 'PUT', $self->ns_subscriber($svc_phone),
+ 'subscriber_login' => $phonenum.'@'.$domain,
+ 'firstname' => $firstname,
+ 'lastname' => $lastname,
+ 'subscriber_pin' => $svc_phone->pin,
+ 'dial_plan' => 'Default', #config?
+ 'dial_policy' => $dial_policy,
+ );
+
+ if ( $ns->responseCode !~ /^2/ ) {
+ return $ns->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns->responseContent ) );
+ }
+
+ #Piece 2 - sip device creation
+
+ my $ns2 = $self->ns_command( 'PUT', $self->ns_registrar($svc_phone),
+ 'termination_match' => $self->ns_devicename($svc_phone)
+ );
+
+ if ( $ns2->responseCode !~ /^2/ ) {
+ return $ns2->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns2->responseContent ) );
+ }
+
+ #Piece 3 - DID mapping to user
+
+ my $ns3 = $self->ns_command( 'PUT', $self->ns_dialplan($svc_phone),
+ 'to_user' => $phonenum,
+ 'to_host' => $domain,
+ );
+
+ if ( $ns3->responseCode !~ /^2/ ) {
+ return $ns3->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns3->responseContent ) );
+ }
+
+ '';
+}
+
+sub ns_delete {
+ my($self, $svc_phone) = (shift, shift);
+
+ my $ns = $self->ns_command( 'DELETE', $self->ns_subscriber($svc_phone) );
+
+ #delete other things?
+
+ if ( $ns->responseCode !~ /^2/ ) {
+ return $ns->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns->responseContent ) );
+ }
+
+ '';
+
+}
+
+sub ns_parse_response {
+ my( $self, $content ) = ( shift, shift );
+
+ #try to screen-scrape something useful
+ tie my %hash, Tie::IxHash;
+ while ( $content =~ s/^.*?<p>\s*<b>(.+?)<\/b>\s*(.+?)\s*<\/p>//is ) {
+ ( $hash{$1} = $2 ) =~ s/^\s*<(\w+)>(.+?)<\/\1>/$2/is;
+ }
+
+ %hash;
+}
+
+sub _export_insert {
+ my($self, $svc_phone) = (shift, shift);
+ $self->ns_create_or_update($svc_phone, 'Permit All');
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't change phonenum with NetSapiens (unprovision and reprovision?)"
+ if $old->phonenum ne $new->phonenum;
+ $self->_export_insert($new);
+}
+
+sub _export_delete {
+ my( $self, $svc_phone ) = (shift, shift);
+
+ $self->ns_delete($svc_phone);
+}
+
+sub _export_suspend {
+ my( $self, $svc_phone ) = (shift, shift);
+ $self->ns_create_or_update($svc_phone, 'Deny');
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_phone ) = (shift, shift);
+ #$self->ns_create_or_update($svc_phone, 'Permit All');
+ $self->_export_insert($svc_phone);
+}
+
+sub export_device_insert {
+ my( $self, $svc_phone, $phone_device ) = (shift, shift, shift);
+
+ #my $domain = $self->option('domain');
+ my $countrycode = $svc_phone->countrycode;
+ my $phonenum = $svc_phone->phonenum;
+
+ my $device = $self->ns_devicename($svc_phone);
+
+ my $ns = $self->ns_device_command(
+ 'PUT', $self->ns_device($svc_phone, $phone_device),
+ 'line1_enable' => 'yes',
+ 'device1' => $self->ns_devicename($svc_phone),
+ 'line1_ext' => $phonenum,
+,
+ #'line2_enable' => 'yes',
+ #'device2' =>
+ #'line2_ext' =>
+
+ #'notes' =>
+ 'server' => 'SiPbx',
+ 'domain' => $self->option('domain'),
+
+ 'brand' => $phone_device->part_device->devicename,
+
+ );
+
+ if ( $ns->responseCode !~ /^2/ ) {
+ return $ns->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns->responseContent ) );
+ }
+
+ '';
+
+}
+
+sub export_device_delete {
+ my( $self, $svc_phone, $phone_device ) = (shift, shift, shift);
+
+ my $ns = $self->ns_device_command(
+ 'DELETE', $self->ns_device($svc_phone, $phone_device),
+ );
+
+ if ( $ns->responseCode !~ /^2/ ) {
+ return $ns->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns->responseContent ) );
+ }
+
+ '';
+
+}
+
+
+sub export_device_replace {
+ my( $self, $svc_phone, $new_phone_device, $old_phone_device ) =
+ (shift, shift, shift, shift);
+
+ #?
+ $self->export_device_insert( $svc_phone, $new_phone_device );
+
+}
+
+sub export_links {
+ my($self, $svc_phone, $arrayref) = (shift, shift, shift);
+ #push @$arrayref, qq!<A HREF="http://example.com/~!. $svc_phone->username.
+ # qq!">!. $svc_phone->username. qq!</A>!;
+ '';
+}
+
+1;
diff --git a/FS/FS/part_export/prizm.pm b/FS/FS/part_export/prizm.pm
index 2d4d858..9705440 100644
--- a/FS/FS/part_export/prizm.pm
+++ b/FS/FS/part_export/prizm.pm
@@ -200,6 +200,9 @@ sub _export_insert {
# }
# }
+ my $performance_profile = $svc->performance_profile;
+ $performance_profile ||= $svc->cust_svc->cust_pkg->part_pkg->pkg;
+
my $element_name_length = 50;
$element_name_length = $1
if $self->option('element_name_length') =~ /^\s*(\d+)\s*$/;
@@ -211,7 +214,7 @@ sub _export_insert {
$location,
$contact,
sprintf("%032X", $svc->authkey),
- $svc->cust_svc->cust_pkg->part_pkg->pkg,
+ $performance_profile,
$svc->vlan_profile,
($self->option('ems') ? 1 : 0 ),
);
@@ -256,7 +259,7 @@ sub _export_insert {
$err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
[ $element ],
- $svc->cust_svc->cust_pkg->part_pkg->pkg,
+ $performance_profile,
0,
1,
);
@@ -395,9 +398,12 @@ sub _export_replace {
return $err_or_som
unless ref($err_or_som);
+ my $performance_profile = $new->performance_profile;
+ $performance_profile ||= $new->cust_svc->cust_pkg->part_pkg->pkg;
+
$err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
[ $element ],
- $new->cust_svc->cust_pkg->part_pkg->pkg,
+ $performance_profile,
0,
1,
);
diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm
index c55fa36..7baf2da 100644
--- a/FS/FS/part_export/shellcommands.pm
+++ b/FS/FS/part_export/shellcommands.pm
@@ -14,6 +14,9 @@ tie my %options, 'Tie::IxHash',
default=>'useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username'
#default=>'cp -pr /etc/skel $dir; chown -R $uid.$gid $dir'
},
+ 'useradd_no_queue' => { label=>'Run immediately',
+ type => 'checkbox',
+ },
'useradd_stdin' => { label=>'Insert command STDIN',
type =>'textarea',
default=>'',
@@ -22,6 +25,9 @@ tie my %options, 'Tie::IxHash',
default=>'userdel -r $username',
#default=>'rm -rf $dir',
},
+ 'userdel_no_queue' => { label=>'Run immediately',
+ type =>'checkbox',
+ },
'userdel_stdin' => { label=>'Delete command STDIN',
type =>'textarea',
default=>'',
@@ -35,6 +41,9 @@ tie my %options, 'Tie::IxHash',
# 'rm -rf $old_dir'.
#')'
},
+ 'usermod_no_queue' => { label=>'Run immediately',
+ type =>'checkbox',
+ },
'usermod_stdin' => { label=>'Modify command STDIN',
type =>'textarea',
default=>'',
@@ -48,12 +57,18 @@ tie my %options, 'Tie::IxHash',
'suspend' => { label=>'Suspension command',
default=>'usermod -L $username',
},
+ 'suspend_no_queue' => { label=>'Run immediately',
+ type =>'checkbox',
+ },
'suspend_stdin' => { label=>'Suspension command STDIN',
default=>'',
},
'unsuspend' => { label=>'Unsuspension command',
default=>'usermod -U $username',
},
+ 'unsuspend_no_queue' => { label=>'Run immediately',
+ type =>'checkbox',
+ },
'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
default=>'',
},
@@ -65,6 +80,9 @@ tie my %options, 'Tie::IxHash',
'Radius group mapping to reason (via template user)',
type => 'textarea',
},
+# 'no_queue' => { label => 'Run command immediately',
+# type => 'checkbox',
+# },
;
%info = (
@@ -172,6 +190,8 @@ old_ for replace operations):
<LI><code>$reasontext (when suspending)</code>
<LI><code>$reasontypenum (when suspending)</code>
<LI><code>$reasontypetext (when suspending)</code>
+ <LI><code>$pkgnum</code>
+ <LI><code>$custnum</code>
<LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.
</UL>
END
@@ -296,15 +316,27 @@ sub _export_command {
$finger = shell_quote $finger;
$crypt_password = shell_quote $crypt_password;
$ldap_password = shell_quote $ldap_password;
+ $pkgnum = $cust_pkg ? $cust_pkg->pkgnum : '';
+ $custnum = $cust_pkg ? $cust_pkg->custnum : '';
my $command_string = eval(qq("$command"));
-
- $self->shellcommands_queue( $svc_acct->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => $command_string,
- stdin_string => $stdin_string,
+ my @ssh_cmd_args = (
+ user => $self->option('user') || 'root',
+ host => $self->machine,
+ command => $command_string,
+ stdin_string => $stdin_string,
);
+
+ if($self->option($action . '_no_queue')) {
+ # discard return value just like freeside-queued.
+ eval { ssh_cmd(@ssh_cmd_args) };
+ $error = $@;
+ return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')'
+ if $error;
+ }
+ else {
+ $self->shellcommands_queue( $svc_acct->svcnum, @ssh_cmd_args );
+ }
}
sub _export_replace {
@@ -317,6 +349,8 @@ sub _export_replace {
${"old_$_"} = $old->getfield($_) foreach $old->fields;
${"new_$_"} = $new->getfield($_) foreach $new->fields;
}
+ my $old_cust_pkg = $old->cust_svc->cust_pkg;
+ my $new_cust_pkg = $new->cust_svc->cust_pkg;
$new_finger =~ /^(.*)\s+(\S+)$/ or $new_finger =~ /^((.*))$/;
($new_first, $new_last ) = ( $1, $2 );
$quoted_new__password = shell_quote $new__password; #old, wrong?
@@ -364,15 +398,30 @@ sub _export_replace {
$new_finger = shell_quote $new_finger;
$new_crypt_password = shell_quote $new_crypt_password;
$new_ldap_password = shell_quote $new_ldap_password;
+ $old_pkgnum = $old_cust_pkg ? $old_cust_pkg->pkgnum : '';
+ $old_custnum = $old_cust_pkg ? $old_cust_pkg->custnum : '';
+ $new_pkgnum = $new_cust_pkg ? $new_cust_pkg->pkgnum : '';
+ $new_custnum = $new_cust_pkg ? $new_cust_pkg->custnum : '';
my $command_string = eval(qq("$command"));
- $self->shellcommands_queue( $new->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => $command_string,
- stdin_string => $stdin_string,
+ my @ssh_cmd_args = (
+ user => $self->option('user') || 'root',
+ host => $self->machine,
+ command => $command_string,
+ stdin_string => $stdin_string,
);
+
+ if($self->option('usermod_no_queue')) {
+ # discard return value just like freeside-queued.
+ eval { ssh_cmd(@ssh_cmd_args) };
+ $error = $@;
+ return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')'
+ if $error;
+ }
+ else {
+ $self->shellcommands_queue( $new->svcnum, @ssh_cmd_args );
+ }
}
#a good idea to queue anything that could fail or take any time
diff --git a/FS/FS/part_export/shellcommands_withdomain.pm b/FS/FS/part_export/shellcommands_withdomain.pm
index 7c5d904..c209002 100644
--- a/FS/FS/part_export/shellcommands_withdomain.pm
+++ b/FS/FS/part_export/shellcommands_withdomain.pm
@@ -15,6 +15,9 @@ tie my %options, 'Tie::IxHash',
type =>'textarea',
#default=>"$_password\n$_password\n",
},
+ 'useradd_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
'userdel' => { label=>'Delete command',
#default=>'',
},
@@ -22,6 +25,9 @@ tie my %options, 'Tie::IxHash',
type =>'textarea',
#default=>'',
},
+ 'userdel_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
'usermod' => { label=>'Modify command',
default=>'',
},
@@ -29,6 +35,9 @@ tie my %options, 'Tie::IxHash',
type =>'textarea',
#default=>"$_password\n$_password\n",
},
+ 'usermod_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
'usermod_pwonly' => { label=>'Disallow username, domain, uid, dir and RADIUS group changes',
type =>'checkbox',
},
@@ -41,12 +50,18 @@ tie my %options, 'Tie::IxHash',
'suspend_stdin' => { label=>'Suspension command STDIN',
default=>'',
},
+ 'suspend_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
'unsuspend' => { label=>'Unsuspension command',
default=>'',
},
'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
default=>'',
},
+ 'unsuspend_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
'crypt' => { label => 'Default password encryption',
type=>'select', options=>[qw(crypt md5)],
default => 'crypt',
@@ -84,6 +99,17 @@ the same username with different domains. You will need to
this.form.usermod_stdin.value = "";
this.form.usermod_pwonly.checked = true;
'>
+ <LI><INPUT TYPE="button" VALUE="MagicMail" onClick='
+ this.form.useradd.value = "/usr/bin/mm_create_email_service -e $svcnum -d $domain -u $username -p $quoted_password -f $first -l $last -m $svcnum -g EMAIL";
+ this.form.useradd_stdin.value = "";
+ this.form.useradd_no_queue.checked = 1;
+ this.form.userdel.value = "/usr/bin/mm_delete_user -e ${username}\\\@${domain}";
+ this.form.userdel_stdin.value = "";
+ this.form.suspend.value = "/usr/bin/mm_suspend_user -e ${username}\\\@${domain}";
+ this.form.suspend_stdin.value = "";
+ this.form.unsuspend.value = "/usr/bin/mm_activate_user -e ${username}\\\@${domain}";
+ this.form.unsuspend_stdin.value = "";
+ '>
</UL>
The following variables are available for interpolation (prefixed with
diff --git a/FS/FS/part_export/www_plesk.pm b/FS/FS/part_export/www_plesk.pm
index 82d5557..ccf9b3e 100644
--- a/FS/FS/part_export/www_plesk.pm
+++ b/FS/FS/part_export/www_plesk.pm
@@ -26,7 +26,7 @@ Real-time export to
<a href="http://www.swsoft.com/">Plesk</a> managed server.
Requires installation of
<a href="http://search.cpan.org/dist/Net-Plesk">Net::Plesk</a>
-from CPAN.
+from CPAN and proper <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration:www_plesk.pm">configuration</a>.
END
);