1 package FS::part_export::domreg_opensrs;
3 use vars qw(@ISA %info %options $conf);
5 use FS::Record qw(qsearchs qsearch);
7 use FS::part_export::null;
13 FS::part_export::domreg_opensrs - Register or transfer domains with Tucows OpenSRS
17 This module handles registering and transferring domains using a registration service provider (RSP) account
18 at Tucows OpenSRS, an ICANN-approved domain registrar.
20 As a part_export, this module can be designated for use with svc_domain services. When the svc_domain object
21 is inserted into the Freeside database, registration or transferring of the domain may be initiated, depending
22 on the setting of the svc_domain's action field.
26 =item N - Register the domain
28 =item M - Transfer the domain
30 =item I - Ignore the domain for registration purposes
34 This export uses Net::OpenSRS. Registration and transfer attempts will fail unless Net::OpenSRS is installed
35 and LWP::UserAgent is able to make HTTPS posts. You can turn on debugging messages and use the OpenSRS test
36 gateway when setting up this export.
40 @ISA = qw(FS::part_export::null);
42 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/;
44 tie %options, 'Tie::IxHash',
45 'username' => { label => 'Reseller user name at OpenSRS',
47 'privatekey' => { label => 'Private key',
49 'password' => { label => 'Password for management account',
51 'masterdomain' => { label => 'Master domain at OpenSRS',
53 'debug_level' => { label => 'Net::OpenSRS debug level',
55 options => [ 0, 1, 2, 3 ],
57 # 'register' => { label => 'Use for registration',
60 # 'transfer' => { label => 'Use for transfer',
63 'tlds' => { label => 'Use this export for these top-level domains (TLDs)',
66 size => scalar(@tldlist),
67 options => [ @tldlist ],
68 default => 'com net org' },
72 'svc' => 'svc_domain',
73 'desc' => 'Domain registration via Tucows OpenSRS',
74 'options' => \%options,
76 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>).
77 All of the Net::OpenSRS restrictions apply:
79 <LI>You must have a reseller account with Tucows.
80 <LI>You must add the public IP address of the Freeside server to the 'Script API allow' list in the OpenSRS web interface.
81 <LI>You must generate an API access key in the OpenSRS web interface and enter it below.
82 <LI>All domains are managed using the same user name and password, but you can create sub-accounts for clients.
83 <LI>The user name must be the same as your OpenSRS reseller ID.
84 <LI>You must enter a master domain that all other domains are associated with. That domain must be registered through your OpenSRS account.
86 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.
87 <BR><BR>Use these buttons for some useful presets:
90 <INPUT TYPE="button" VALUE="OpenSRS Live System (rr-n1-tor.opensrs.net)" onClick='
91 document.dummy.machine.value = "rr-n1-tor.opensrs.net";
92 this.form.machine.value = "rr-n1-tor.opensrs.net";
95 <INPUT TYPE="button" VALUE="OpenSRS Test System (horizon.opensrs.net)" onClick='
96 document.dummy.machine.value = "horizon.opensrs.net";
97 this.form.machine.value = "horizon.opensrs.net";
103 install_callback FS::UID sub {
104 $conf = new FS::Conf;
113 Reformats a phone number according to registry rules. Currently Freeside stores phone numbers
114 in NANPA format and the registry prefers "+CCC.NPANPXNNNN"
121 #if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})( x(\d+))?$/) {
122 if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})$/) {
124 # if $tel .= "$4" if $4;
129 =item gen_contact_info
131 Generates contact data for the domain based on the customer data.
133 Currently relies on Net::OpenSRS to format the telephone number for OpenSRS.
141 my @invoicing_list = $co->invoicing_list_emailonly;
142 if ( $conf->exists('emailinvoiceautoalways')
143 || $conf->exists('emailinvoiceauto') && ! @invoicing_list
144 || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
145 push @invoicing_list, $co->all_emails;
148 my $email = ($conf->exists('business-onlinepayment-email-override'))
149 ? $conf->config('business-onlinepayment-email-override')
150 : $invoicing_list[0];
153 firstname => $co->first,
154 lastname => $co->last,
155 company => $co->company,
156 address => $co->address1,
158 state => $co->state(),
160 country => uc($co->country()),
162 #phone => format_tel($co->daytime()),
163 phone => $co->daytime() || $co->night,
168 =item validate_contact_info
170 Attempts to validate contact data for the domain based on OpenSRS rules.
172 Returns undef if the contact data is acceptable, an error message if the contact
173 data lacks one or more required fields.
177 sub validate_contact_info {
181 firstname => "first name",
182 lastname => "last name",
183 address => "street address",
186 zip => "ZIP/postal code",
187 country => "country",
188 email => "email address",
189 phone => "phone number",
192 foreach (keys %fields) {
193 if (!defined($c->{$_}) || !$c->{$_}) {
194 push @err, $fields{$_};
197 if (scalar(@err) > 0) {
198 return "Contact information needs: " . join(', ', @err);
205 Returns the Net::OpenSRS-required test mode string based on whether the export
206 is configured to use the live or the test gateway.
213 return 'live' if $self->machine eq "rr-n1-tor.opensrs.net";
214 return 'test' if $self->machine eq "horizon.opensrs.net";
220 Attempts to "export" the domain, i.e. register or transfer it if the user selected
221 that option when editing the domain.
223 Returns an error message on failure or undef on success.
225 May also return an error message if it cannot load the required Perl module Net::OpenSRS,
226 or if the domain is not registerable, or if insufficient data is provided in the customer
227 record to generate the required contact information to register or transfer the domain.
232 my( $self, $svc_domain ) = ( shift, shift );
234 return if $svc_domain->action eq 'I'; # Ignoring registration, just doing DNS
236 if ($svc_domain->action eq 'N') {
237 return $self->register( $svc_domain );
238 } elsif ($svc_domain->action eq 'M') {
239 return $self->transfer( $svc_domain );
241 return "Unknown domain action " . $svc_domain->action;
244 ## Domain registration exports do nothing on replace. Mainly because we haven't decided what they should do.
245 #sub _export_replace {
246 # my( $self, $new, $old ) = (shift, shift, shift);
252 ## Domain registration exports do nothing on delete. You're just removing the domain from Freeside, not the registry
253 #sub _export_delete {
254 # my( $self, $svc_domain ) = ( shift, shift );
259 =item is_supported_domain
261 Return undef if the domain name uses a TLD or SLD that is supported by this registrar.
262 Otherwise return an error message explaining what's wrong.
266 sub is_supported_domain {
268 my $svc_domain = shift;
270 # Get the TLD of the new domain
271 my @bits = split /\./, $svc_domain->domain;
273 return "Can't register subdomains: " . $svc_domain->domain if scalar(@bits) != 2;
277 # See if it's one this export supports
278 my @tlds = split /\s+/, $self->option('tlds');
279 @tlds = map { s/\.//; $_ } @tlds;
280 return "Can't register top-level domain $tld, restricted to: " . $self->option('tlds') if ! grep { $_ eq $tld } @tlds;
291 my $srs = Net::OpenSRS->new();
293 $srs->debug_level( $self->option('debug_level') ); # Output should be in the Apache error log
295 $srs->environment( $self->testmode() );
296 $srs->set_key( $self->option('privatekey') );
298 $srs->set_manage_auth( $self->option('username'), $self->option('password') );
304 Returns a reference to a hashref containing information on the domain's status. The keys
305 defined depend on the status.
307 'unregistered' means the domain is not registered.
309 Otherwise, if the domain is in an asynchronous operation such as a transfer, returns the state
312 Otherwise returns a value indicating if the domain can be managed through our reseller account.
317 my ( $self, $svc_domain ) = @_;
320 eval "use Net::OpenSRS;";
323 my $srs = $self->get_srs;
325 if ($srs->is_available( $svc_domain->domain )) {
326 $rslt->{'unregistered'} = 1;
328 $rslt = $srs->check_transfer( $svc_domain->domain );
329 if (defined($rslt->{'reason'})) {
330 my $rv = $srs->make_request(
332 action => 'belongs_to_rsp',
335 domain => $svc_domain->domain
340 $self->_set_response;
341 if ( $rv->{attributes}->{'domain_expdate'} ) {
342 $rslt->{'expdate'} = $rv->{attributes}->{'domain_expdate'};
348 return $rslt; # Success
353 Attempts to register the domain through the reseller account associated with this export.
355 Like most export functions, returns an error message on failure or undef on success.
360 my ( $self, $svc_domain, $years ) = @_;
362 return "Net::OpenSRS does not support period other than 1 year" if $years != 1;
364 eval "use Net::OpenSRS;";
367 my $err = $self->is_supported_domain( $svc_domain );
370 my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
372 my $c = gen_contact_info($cust_main);
374 $err = validate_contact_info($c);
377 my $srs = $self->get_srs;
379 my $cookie = $srs->get_cookie( $self->option('masterdomain') );
381 return "Unable to get cookie at OpenSRS: " . $srs->last_response();
384 # return "Domain registration not enabled" if !$self->option('register');
385 return $srs->last_response() if !$srs->register_domain( $svc_domain->domain, $c);
387 return ''; # Should only get here if register succeeded
392 Attempts to transfer the domain into the reseller account associated with this export.
394 Like most export functions, returns an error message on failure or undef on success.
399 my ( $self, $svc_domain ) = @_;
401 eval "use Net::OpenSRS;";
404 my $err = $self->is_supported_domain( $svc_domain );
407 my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
409 my $c = gen_contact_info($cust_main);
411 $err = validate_contact_info($c);
414 my $srs = $self->get_srs;
416 my $cookie = $srs->get_cookie( $self->option('masterdomain') );
418 return "Unable to get cookie at OpenSRS: " . $srs->last_response();
421 # return "Domain transfer not enabled" if !$self->option('transfer');
422 return $srs->last_response() if !$srs->transfer_domain( $svc_domain->domain, $c);
424 return ''; # Should only get here if transfer succeeded
429 Attempts to renew the domain for the specified number of years.
431 Like most export functions, returns an error message on failure or undef on success.
436 my ( $self, $svc_domain, $years ) = @_;
438 eval "use Net::OpenSRS;";
441 my $err = $self->is_supported_domain( $svc_domain );
444 my $srs = $self->get_srs;
446 my $cookie = $srs->get_cookie( $self->option('masterdomain') );
448 return "Unable to get cookie at OpenSRS: " . $srs->last_response();
451 # return "Domain renewal not enabled" if !$self->option('renew');
452 return $srs->last_response() if !$srs->renew_domain( $svc_domain->domain, $years );
454 return ''; # Should only get here if renewal succeeded
459 Attempts to revoke the domain registration. Only succeeds if invoked during the OpenSRS
460 grace period immediately after registration.
462 Like most export functions, returns an error message on failure or undef on success.
467 my ( $self, $svc_domain ) = @_;
469 eval "use Net::OpenSRS;";
472 my $err = $self->is_supported_domain( $svc_domain );
475 my $srs = $self->get_srs;
477 my $cookie = $srs->get_cookie( $self->option('masterdomain') );
479 return "Unable to get cookie at OpenSRS: " . $srs->last_response();
482 # return "Domain registration revocation not enabled" if !$self->option('revoke');
483 return $srs->last_response() if !$srs->revoke_domain( $svc_domain->domain);
485 return ''; # Should only get here if transfer succeeded
490 Should return a full-blown object representing OpenSRS, but current just returns a hashref
491 containing the registrar name.
505 L<Net::OpenSRS>, L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_domain>,
506 L<FS::Record>, schema.html from the base documentation.