From 88a835db0da84b00a5f0030d48a85e23975a2c16 Mon Sep 17 00:00:00 2001 From: Pinga <121483313+getpinga@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:44:11 +0200 Subject: [PATCH] First version --- README.md | 59 +- VeriSign.php | 1651 ++++++++++++++++++++++++++++++++++++ VeriSignSync.php | 836 ++++++++++++++++++ install_verisign_module.sh | 33 + 4 files changed, 2577 insertions(+), 2 deletions(-) create mode 100644 VeriSign.php create mode 100644 VeriSignSync.php create mode 100644 install_verisign_module.sh diff --git a/README.md b/README.md index b0a6e21..fb0b029 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,57 @@ -# fossbilling-epp-verisign -EPP Registrar Module for FOSSBilling (VeriSign) +# Compatibility + +This module supports **all gTLDs** that use the VeriSign platform. + +# FOSSBilling Module Installation instructions + +## 1. Download and Install FOSSBilling: + +Start by downloading the latest version of FOSSBilling from the official website (https://fossbilling.org/). Then follow the instructions below to install the module, or run for automated installation: + +```bash +wget https://raw.githubusercontent.com/getnamingo/fossbilling-epp-verisign/main/install_verisign_module.sh -O install_verisign_module.sh && chmod +x install_verisign_module.sh && ./install_verisign_module.sh +``` + +## 2. Installation and Configuration of Registrar Adapter: + +First, download this repository which contains the VeriSign.php file. After successfully downloading the repository, move the VeriSign.php file into the `[FOSSBilling]/library/Registrar/Adapter` directory. + +## 3. Addition of Synchronization Script: + +There is one additional script in the repository: **VeriSignSync.php**. It needs to be placed in the main `[FOSSBilling]` directory. + +## 4. Setting Up the Cron Job: + +You need to set up a cron job that runs the sync module twice a day. Open crontab using the command `crontab -e` in your terminal. + +Add the following cron job: + +`0 0,12 * * * php /var/www/html/VeriSignSync.php` + +This command schedules the synchronization script to run once every 12 hours (at midnight and noon). + +## 5. Activate the Domain Registrar Module: + +Within FOSSBilling, go to **System -> Domain Registration -> New Domain Registrar** and activate the new domain registrar. + +## 6. Registrar Configuration: + +Next, head to the "**Registrars**" tab. Here, you'll need to enter your specific configuration details, including the path to your SSL certificate and key. + +## 7. Adding a New TLD: + +Finally, add a new Top Level Domain (TLD) using your module from the "**New Top Level Domain**" tab. Make sure to configure all necessary details, such as pricing, within this tab. + +# Troubleshooting + +If you experience problems connecting to your EPP server, follow these steps: + +1. Ensure your server's IP (IPv4 and IPv6) is whitelisted by the EPP server. + +2. Confirm your client and server support IPv6 if required. If needed, disable IPv6 support in EPP server. + +3. Reload the EPP module or restart the web server after any changes. + +4. Ensure certificates have the correct permissions: `chown www-data:www-data cert.pem` and `chown www-data:www-data key.pem`. + +5. Verify the EPP module is configured with the chosen registrar prefix. \ No newline at end of file diff --git a/VeriSign.php b/VeriSign.php new file mode 100644 index 0000000..90e5353 --- /dev/null +++ b/VeriSign.php @@ -0,0 +1,1651 @@ +config['username'] = $options['username']; + } + if(isset($options['password'])) { + $this->config['password'] = $options['password']; + } + if(isset($options['host'])) { + $this->config['host'] = $options['host']; + } + if(isset($options['port'])) { + $this->config['port'] = $options['port']; + } + if(isset($options['registrarprefix'])) { + $this->config['registrarprefix'] = $options['registrarprefix']; + } + if(isset($options['ssl_cert'])) { + $this->config['ssl_cert'] = $options['ssl_cert']; + } + if(isset($options['ssl_key'])) { + $this->config['ssl_key'] = $options['ssl_key']; + } + if(isset($options['ssl_ca'])) { + $this->config['ssl_ca'] = $options['ssl_ca']; + } + if(isset($options['min_data_set'])) { + $this->config['min_data_set'] = (bool)$options['min_data_set']; + } else { + $this->config['min_data_set'] = false; + } + if(isset($options['use_tls_12'])) { + $this->config['use_tls_12'] = (bool)$options['use_tls_12']; + } else { + $this->config['use_tls_12'] = false; + } + } + + public function getTlds() + { + return array(); + } + + public static function getConfig() + { + return array( + 'label' => 'An EPP registry module allows registrars to manage and register domain names using the Extensible Provisioning Protocol (EPP). All details below are typically provided by the domain registry and are used to authenticate your account when connecting to the EPP server.', + 'form' => array( + 'username' => array('text', array( + 'label' => 'EPP Server Username', + 'required' => true, + ), + ), + 'password' => array('password', array( + 'label' => 'EPP Server Password', + 'required' => true, + 'renderPassword' => true, + ), + ), + 'host' => array('text', array( + 'label' => 'EPP Server Host', + 'required' => true, + ), + ), + 'port' => array('text', array( + 'label' => 'EPP Server Port', + 'required' => true, + ), + ), + 'registrarprefix' => array('text', array( + 'label' => 'Registrar Prefix', + 'required' => true, + ), + ), + 'ssl_cert' => array('text', array( + 'label' => 'SSL Certificate Path', + 'required' => true, + ), + ), + 'ssl_key' => array('text', array( + 'label' => 'SSL Key Path', + 'required' => true, + ), + ), + 'ssl_ca' => array('text', array( + 'label' => 'SSL CA Path', + 'required' => false, + ), + ), + 'min_data_set' => array('radio', array( + 'multiOptions' => array('1'=>'Yes', '0'=>'No'), + 'label' => 'Enable Minimum Data Set', + ), + ), + 'use_tls_12' => array('radio', array( + 'multiOptions' => array('1'=>'Yes', '0'=>'No'), + 'label' => 'Use TLS 1.2 instead of 1.3', + ), + ), + ), + ); + } + + public function isDomaincanBeTransferred(Registrar_Domain $domain) + { + $this->getLog()->debug('Checking if domain can be transferred: ' . $domain->getName()); + return true; + } + + public function isDomainAvailable(Registrar_Domain $domain) + { + $this->getLog()->debug('Checking domain availability: ' . $domain->getName()); + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1) , 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-check-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->chkData; + $reason = (string)$r->cd[0]->reason; + + if ($reason) + { + return false; + } else { + return true; + } + if (!empty($s)) + { + $this->logout(); + } + + return true; + } + + public function modifyNs(Registrar_Domain $domain) + { + $this->getLog()->debug('Modifying nameservers: ' . $domain->getName()); + $this->getLog()->debug('Ns1: ' . $domain->getNs1()); + $this->getLog()->debug('Ns2: ' . $domain->getNs2()); + $this->getLog()->debug('Ns3: ' . $domain->getNs3()); + $this->getLog()->debug('Ns4: ' . $domain->getNs4()); + $return = array(); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-info-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $add = $rem = array(); + $i = 0; + foreach($r->ns->hostObj as $ns) { + $i++; + $ns = (string)$ns; + if (!$ns) { + continue; + } + + $rem["ns{$i}"] = $ns; + } + + foreach (range(1, 4) as $i) { + $k = "getNs$i"; + $v = $domain->{$k}(); + if (!$v) { + continue; + } + + if ($k0 = array_search($v, $rem)) { + unset($rem[$k0]); + } else { + $add["ns$i"] = $v; + } + } + + if (!empty($add) || !empty($rem)) { + $from = $to = array(); + $text = ''; + foreach($add as $k => $v) { + $text.= '' . $v . '' . "\n"; + } + + $from[] = '/{{ add }}/'; + $to[] = (empty($text) ? '' : "\n{$text}\n"); + $text = ''; + foreach($rem as $k => $v) { + $text.= '' . $v . '' . "\n"; + } + + $from[] = '/{{ rem }}/'; + $to[] = (empty($text) ? '' : "\n{$text}\n"); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-update-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + {{ add }} + {{ rem }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + } + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + throw new Registrar_Exception('Error: ' . $e->getMessage()); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function transferDomain(Registrar_Domain $domain) + { + $this->getLog()->debug('Transfering domain: ' . $domain->getName()); + $this->getLog()->debug('Epp code: ' . $domain->getEpp()); + $return = array(); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ authInfo_pw }}/'; + $to[] = htmlspecialchars($domain->getEpp()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-transfer-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + 1 + + {{ authInfo_pw }} + + + + + + dotCOM + + + {{ clTRID }} + +'); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->trnData; + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function getDomainDetails(Registrar_Domain $domain) + { + $this->getLog()->debug('Getting domain details: ' . $domain->getName()); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-info-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $crDate = (string)$r->crDate; + $exDate = (string)$r->exDate; + $eppcode = (string)$r->authInfo->pw; + + $status = array(); + $i = 0; + foreach ($r->status as $e) { + $i++; + $status[$i] = (string)$e->attributes()->s; + } + $ns = array(); + $i = 0; + if (isset($r->ns->hostObj) && (is_array($r->ns->hostObj) || is_object($r->ns->hostObj))) { + foreach ($r->ns->hostObj as $hostObj) { + $i++; + $ns[$i] = (string)$hostObj; + } + } else { + $ns = []; + } + + $crDate = strtotime($crDate); + $exDate = strtotime($exDate); + + $domain->setRegistrationTime($crDate); + $domain->setExpirationTime($exDate); + $domain->setEpp($eppcode); + + $domain->setNs1(isset($ns[0]) ? $ns[0] : ''); + $domain->setNs2(isset($ns[1]) ? $ns[1] : ''); + $domain->setNs3(isset($ns[2]) ? $ns[2] : ''); + $domain->setNs4(isset($ns[3]) ? $ns[3] : ''); + } + + catch(exception $e) { + throw new Registrar_Exception('Error: ' . $e->getMessage()); + } + + if (!empty($s)) { + $this->logout(); + } + + return $domain; + } + + public function deleteDomain(Registrar_Domain $domain) + { + $this->getLog()->debug('Removing domain: ' . $domain->getName()); + $return = array(); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-delete-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + +'); + $r = $this->write($xml, __FUNCTION__); + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function registerDomain(Registrar_Domain $domain) + { + $this->getLog()->debug('Registering domain: ' . $domain->getName(). ' for '.$domain->getRegistrationPeriod(). ' years'); + $client = $domain->getContactRegistrar(); + + $return = array(); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-check-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->chkData; + $reason = (string)$r->cd[0]->reason; + if (!$reason) { + $reason = 'Domain is not available'; + } + + if (0 == (int)$r->cd[0]->name->attributes()->avail) { + throw new Registrar_Exception($r->cd[0]->name . ' ' . $reason); + } + + if ($this->config['min_data_set'] === false) { + // contact:create + $from = $to = array(); + $from[] = '/{{ id }}/'; + $c_id = strtoupper($this->generateRandomString()); + $to[] = $c_id; + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($client->getFirstName() . ' ' . $client->getLastName()); + $from[] = '/{{ org }}/'; + $to[] = htmlspecialchars($client->getCompany()); + $from[] = '/{{ street1 }}/'; + $to[] = htmlspecialchars($client->getAddress1()); + $from[] = '/{{ city }}/'; + $to[] = htmlspecialchars($client->getCity()); + $from[] = '/{{ state }}/'; + $to[] = htmlspecialchars($client->getState()); + $from[] = '/{{ postcode }}/'; + $to[] = htmlspecialchars($client->getZip()); + $from[] = '/{{ country }}/'; + $to[] = htmlspecialchars($client->getCountry()); + $from[] = '/{{ phonenumber }}/'; + $to[] = htmlspecialchars('+'.$client->getTelCc().'.'.$client->getTel()); + $from[] = '/{{ email }}/'; + $to[] = htmlspecialchars($client->getEmail()); + $from[] = '/{{ authInfo }}/'; + $to[] = htmlspecialchars($this->generateObjectPW()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-contact-create-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ id }} + + {{ name }} + {{ org }} + + {{ street1 }} + + + {{ city }} + {{ state }} + {{ postcode }} + {{ country }} + + + {{ phonenumber }} + + {{ email }} + + {{ authInfo }} + + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:contact-1.0')->creData; + $contacts = $r->id; + } + + //host create + foreach (['ns1', 'ns2', 'ns3', 'ns4'] as $ns) { + if ($domain->{'get' . ucfirst($ns)}()) { + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = $domain->{'get' . ucfirst($ns)}(); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-host-check-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:host-1.0')->chkData; + + if (0 == (int)$r->cd[0]->name->attributes()->avail) { + continue; + } + + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = $domain->{'get' . ucfirst($ns)}(); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-host-create-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + } + } + + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ period }}/'; + $to[] = htmlspecialchars($domain->getRegistrationPeriod()); + if($domain->getNs1()) { + $from[] = '/{{ ns1 }}/'; + $to[] = htmlspecialchars($domain->getNs1()); + } else { + $from[] = '/{{ ns1 }}/'; + $to[] = ''; + } + if($domain->getNs2()) { + $from[] = '/{{ ns2 }}/'; + $to[] = htmlspecialchars($domain->getNs2()); + } else { + $from[] = '/{{ ns2 }}/'; + $to[] = ''; + } + if($domain->getNs3()) { + $from[] = '/{{ ns3 }}/'; + $to[] = htmlspecialchars($domain->getNs3()); + } else { + $from[] = '/{{ ns3 }}/'; + $to[] = ''; + } + if($domain->getNs4()) { + $from[] = '/{{ ns4 }}/'; + $to[] = htmlspecialchars($domain->getNs4()); + } else { + $from[] = '/{{ ns4 }}/'; + $to[] = ''; + } + if ($this->config['min_data_set'] === false) { + $from[] = '/{{ cID_1 }}/'; + $to[] = htmlspecialchars($contacts); + $from[] = '/{{ cID_2 }}/'; + $to[] = htmlspecialchars($contacts); + $from[] = '/{{ cID_3 }}/'; + $to[] = htmlspecialchars($contacts); + $from[] = '/{{ cID_4 }}/'; + $to[] = htmlspecialchars($contacts); + } + $from[] = '/{{ authInfo }}/'; + $to[] = htmlspecialchars($this->generateObjectPW()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-create-' . $clTRID); + $from[] = "/<\w+:\w+>\s*<\/\w+:\w+>\s+/ims"; + $to[] = ''; + + $contact_section = ''; + if ($this->config['min_data_set'] === false) { + $contact_section = ' + {{ cID_1 }} + {{ cID_2 }} + {{ cID_3 }} + {{ cID_4 }}'; + } + + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + {{ period }} + + {{ ns1 }} + {{ ns2 }} + {{ ns3 }} + {{ ns4 }} + ' . $contact_section . ' + + {{ authInfo }} + + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + throw new Registrar_Exception('Error: ' . $e->getMessage()); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function renewDomain(Registrar_Domain $domain) + { + $this->getLog()->debug('Renewing domain: ' . $domain->getName()); + $return = array(); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-info-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $expDate = (string)$r->exDate; + $expDate = preg_replace("/^(\d+\-\d+\-\d+)\D.*$/", "$1", $expDate); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ expDate }}/'; + $to[] = htmlspecialchars($expDate); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-renew-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + {{ expDate }} + 1 + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function modifyContact(Registrar_Domain $domain) + { + $this->getLog()->debug('Updating contact info: ' . $domain->getName()); + + if ($this->config['min_data_set'] === true) { + throw new Registrar_Exception("Contact update not possible as the Minimum Data Set is enabled."); + } + + $client = $domain->getContactRegistrar(); + $return = array(); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-info-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $registrant = (string)$r->registrant; + $from = $to = array(); + $from[] = '/{{ id }}/'; + $to[] = $registrant; + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($client->getFirstName() . ' ' . $client->getLastName()); + $from[] = '/{{ org }}/'; + $to[] = htmlspecialchars($client->getCompany()); + $from[] = '/{{ street1 }}/'; + $to[] = htmlspecialchars($client->getAddress1()); + $from[] = '/{{ street2 }}/'; + $to[] = htmlspecialchars($client->getAddress2()); + $from[] = '/{{ city }}/'; + $to[] = htmlspecialchars($client->getCity()); + $from[] = '/{{ state }}/'; + $to[] = htmlspecialchars($client->getState()); + $from[] = '/{{ postcode }}/'; + $to[] = htmlspecialchars($client->getZip()); + $from[] = '/{{ country }}/'; + $to[] = htmlspecialchars($client->getCountry()); + $from[] = '/{{ phonenumber }}/'; + $to[] = htmlspecialchars('+'.$client->getTelCc().'.'.$client->getTel()); + $from[] = '/{{ email }}/'; + $to[] = htmlspecialchars($client->getEmail()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-contact-update-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ id }} + + + {{ name }} + {{ org }} + + {{ street1 }} + {{ street2 }} + + {{ city }} + {{ state }} + {{ postcode }} + {{ country }} + + + {{ phonenumber }} + + {{ email }} + + + + + + dotCOM + + + {{ clTRID }} + +'); + $r = $this->write($xml, __FUNCTION__); + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function enablePrivacyProtection(Registrar_Domain $domain) + { + $this->getLog()->debug('Enabling Privacy protection: ' . $domain->getName()); + $return = array(); + $processedContacts = []; + + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-info-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $dcontact = array(); + $dcontact['registrant'] = (string)$r->registrant; + foreach($r->contact as $e) { + $type = (string)$e->attributes()->type; + $dcontact[$type] = (string)$e; + } + + $contact = array(); + foreach($dcontact as $id) { + // If the contact ID has already been processed, skip the update + if (in_array($id, $processedContacts)) { + continue; + } + $from = $to = array(); + $from[] = '/{{ id }}/'; + $to[] = htmlspecialchars($id); + $from[] = '/{{ flag }}/'; + $to[] = 0; + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1) , 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-contact-update-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ id }} + + + + + + + + + + + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + + // Mark this contact ID as processed to avoid duplicate updates + $processedContacts[] = $id; + } + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function disablePrivacyProtection(Registrar_Domain $domain) + { + $this->getLog()->debug('Disabling Privacy protection: ' . $domain->getName()); + $return = array(); + $processedContacts = []; + + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-info-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $dcontact = array(); + $dcontact['registrant'] = (string)$r->registrant; + foreach($r->contact as $e) { + $type = (string)$e->attributes()->type; + $dcontact[$type] = (string)$e; + } + + $contact = array(); + foreach($dcontact as $id) { + // If the contact ID has already been processed, skip the update + if (in_array($id, $processedContacts)) { + continue; + } + $from = $to = array(); + $from[] = '/{{ id }}/'; + $to[] = htmlspecialchars($id); + $from[] = '/{{ flag }}/'; + $to[] = 1; + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1) , 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-contact-update-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ id }} + + + + + + + + + + + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + + // Mark this contact ID as processed to avoid duplicate updates + $processedContacts[] = $id; + } + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function getEpp(Registrar_Domain $domain) + { + $this->getLog()->debug('Retrieving domain transfer code: ' . $domain->getName()); + $return = array(); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-info-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $eppcode = (string)$r->authInfo->pw; + + if (!empty($s)) { + $this->logout(); + } + return $eppcode; + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function lock(Registrar_Domain $domain) + { + $this->getLog()->debug('Locking domain: ' . $domain->getName()); + $return = array(); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-info-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $status = array(); + foreach($r->status as $e) { + $st = (string)$e->attributes()->s; + if (!preg_match("/^client.+Prohibited$/i", $st)) { + continue; + } + + $status[$st] = true; + } + + $add = array(); + foreach(array( + 'clientDeleteProhibited', + 'clientTransferProhibited' + ) as $st) { + if (!isset($status[$st])) { + $add[] = $st; + } + } + + if (!empty($add)) { + $text = ''; + foreach($add as $st) { + $text.= '' . "\n"; + } + $from = $to = array(); + $from[] = '/{{ add }}/'; + $to[] = (empty($text) ? '' : "\n{$text}\n"); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-update-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + {{ add }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + } + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function unlock(Registrar_Domain $domain) + { + $this->getLog()->debug('Unlocking: ' . $domain->getName()); + $return = array(); + try { + $s = $this->connect(); + $this->login(); + $from = $to = array(); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-info-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $status = array(); + foreach($r->status as $e) { + $st = (string)$e->attributes()->s; + if (!preg_match("/^client.+Prohibited$/i", $st)) { + continue; + } + + $status[$st] = true; + } + + $rem = array(); + foreach(array( + 'clientDeleteProhibited', + 'clientTransferProhibited' + ) as $st) { + if (isset($status[$st])) { + $rem[] = $st; + } + } + + if (!empty($rem)) { + $text = ''; + foreach($rem as $st) { + $text.= '' . "\n"; + } + $from = $to = array(); + $from[] = '/{{ rem }}/'; + $to[] = (empty($text) ? '' : "\n{$text}\n"); + $from[] = '/{{ name }}/'; + $to[] = htmlspecialchars($domain->getName()); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-domain-update-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + + {{ name }} + {{ rem }} + + + + + dotCOM + + + {{ clTRID }} + + '); + $r = $this->write($xml, __FUNCTION__); + } + } + + catch(exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + if (!empty($s)) { + $this->logout(); + } + + return $return; + } + + public function connect() + { + $host = $this->config['host']; + $port = $this->config['port']; + $timeout = 30; + + $opts = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'verify_host' => false, + 'allow_self_signed' => true, + 'local_cert' => $this->config['ssl_cert'], + 'local_pk' => $this->config['ssl_key'] + ) + ); + $context = stream_context_create($opts); + if ($this->config['use_tls_12'] === true) { + $tls = 'tlsv1.2'; + } else { + $tls = 'tlsv1.3'; + } + $this->socket = stream_socket_client($tls."://{$host}:{$port}", $errno, $errmsg, $timeout, STREAM_CLIENT_CONNECT, $context); + + if (!$this->socket) { + throw new Registrar_Exception("Cannot connect to server '{$host}': {$errmsg}"); + } + + return $this->read(); + } + + public function login() + { + $from = $to = array(); + $from[] = '/{{ clID }}/'; + $to[] = htmlspecialchars($this->config['username']); + $from[] = '/{{ pw }}/'; + $to[] = $this->config['password']; + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-login-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + {{ clID }} + + + 1.0 + en + + + urn:ietf:params:xml:ns:domain-1.0 + urn:ietf:params:xml:ns:contact-1.0 + urn:ietf:params:xml:ns:host-1.0 + http://www.verisign.com/epp/registry-1.0 + http://www.verisign.com/epp/lowbalance-poll-1.0 + http://www.verisign.com/epp/rgp-poll-1.0 + + urn:ietf:params:xml:ns:secDNS-1.1 + urn:ietf:params:xml:ns:epp:loginSec-1.0 + http://www.verisign.com/epp/whoisInf-1.0 + http://www.verisign.com/epp/idnLang-1.0 + urn:ietf:params:xml:ns:coa-1.0 + http://www.verisign-grs.com/epp/namestoreExt-1.1 + http://www.verisign.com/epp/sync-1.0 + http://www.verisign.com/epp/relatedDomain-1.0 + urn:ietf:params:xml:ns:verificationCode-1.0 + urn:ietf:params:xml:ns:rgp-1.0 + urn:ietf:params:xml:ns:changePoll-1.0 + + + + {{ clTRID }} + +'); + $r = $this->write($xml, __FUNCTION__); + $this->isLogined = true; + return true; + } + + public function logout() + { + if (!$this->isLogined) { + return true; + } + + $from = $to = array(); + $from[] = '/{{ clTRID }}/'; + $clTRID = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->config['registrarprefix'] . '-logout-' . $clTRID); + $xml = preg_replace($from, $to, ' + + + + {{ clTRID }} + +'); + $r = $this->write($xml, __FUNCTION__); + $this->isLogined = false; + return true; + } + + public function read() + { + $hdr = stream_get_contents($this->socket, 4); + if ($hdr === false) { + throw new Registrar_Exception('Connection appears to have closed.'); + } + if (strlen($hdr) < 4) { + throw new Registrar_Exception('Failed to read header from the connection.'); + } + $unpacked = unpack('N', $hdr); + $xml = fread($this->socket, ($unpacked[1] - 4)); + $xml = preg_replace('/>\n<", $xml); + return $xml; + } + + public function write($xml) + { + if (fwrite($this->socket, pack('N', (strlen($xml) + 4)) . $xml) === false) { + throw new Registrar_Exception('Error writing to the connection.'); + } + $xml_string = $this->read(); + libxml_use_internal_errors(true); + + $r = simplexml_load_string($xml_string, 'SimpleXMLElement', LIBXML_DTDLOAD | LIBXML_NOENT); + if ($r instanceof SimpleXMLElement) { + $r->registerXPathNamespace('e', 'urn:ietf:params:xml:ns:epp-1.0'); + $r->registerXPathNamespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $r->registerXPathNamespace('domain', 'urn:ietf:params:xml:ns:domain-1.0'); + $r->registerXPathNamespace('contact', 'urn:ietf:params:xml:ns:contact-1.0'); + $r->registerXPathNamespace('host', 'urn:ietf:params:xml:ns:host-1.0'); + $r->registerXPathNamespace('rgp', 'urn:ietf:params:xml:ns:rgp-1.0'); + } + + if (isset($r->response) && $r->response->result->attributes()->code >= 2000) { + throw new Registrar_Exception($r->response->result->msg); + } + return $r; + } + + + public function disconnect() + { + $result = fclose($this->socket); + if (!$result) { + throw new Registrar_Exception('Error closing the connection.'); + } + $this->socket = null; + return $result; + } + + function generateObjectPW($objType = 'none') + { + $result = ''; + $uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + $lowercaseChars = "abcdefghijklmnopqrstuvwxyz"; + $numbers = "1234567890"; + $specialSymbols = "!=+-"; + $minLength = 13; + $maxLength = 13; + $length = mt_rand($minLength, $maxLength); + + // Include at least one character from each set + $result .= $uppercaseChars[mt_rand(0, strlen($uppercaseChars) - 1)]; + $result .= $lowercaseChars[mt_rand(0, strlen($lowercaseChars) - 1)]; + $result .= $numbers[mt_rand(0, strlen($numbers) - 1)]; + $result .= $specialSymbols[mt_rand(0, strlen($specialSymbols) - 1)]; + + // Append random characters to reach the desired length + while (strlen($result) < $length) { + $chars = $uppercaseChars . $lowercaseChars . $numbers . $specialSymbols; + $result .= $chars[mt_rand(0, strlen($chars) - 1)]; + } + + return 'aA1' . $result; + } + + public function generateRandomString() + { + $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $randomString = ''; + for ($i = 0; $i < 12; $i++) { + $randomString .= $characters[rand(0, strlen($characters) - 1)]; + } + return $randomString; + } +} \ No newline at end of file diff --git a/VeriSignSync.php b/VeriSignSync.php new file mode 100644 index 0000000..f3c3fe4 --- /dev/null +++ b/VeriSignSync.php @@ -0,0 +1,836 @@ +prepare("SELECT * FROM tld_registrar WHERE registrar = :registrar"); + $stmt->bindValue(":registrar", $registrar); + $stmt->execute(); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $config = []; + + foreach ($rows as $row) + { + $config = json_decode($row["config"], true); + $registrar_id = $row["id"]; + } + + if (empty($config)) + { + exit("Database cannot be accessed right now.".PHP_EOL); + } + +} +catch(PDOException $e) +{ + exit("Database error: " . $e->getMessage().PHP_EOL); +} +catch(Exception $e) +{ + exit("General error: " . $e->getMessage().PHP_EOL); +} + +function connectEpp($config) +{ + try + { + $epp = new eppClient(); + $info = [ + "host" => $config["host"], + "port" => $config["port"], "timeout" => 30, "tls" => "1.3", "bind" => false, "bindip" => "1.2.3.4:0", "verify_peer" => false, "verify_peer_name" => false, + "verify_host" => false, "cafile" => "", "local_cert" => $config["ssl_cert"], "local_pk" => $config["ssl_key"], "passphrase" => "", "allow_self_signed" => true, ]; + $epp->connect($info); + $login = $epp->login(["clID" => $config["username"], "pw" => $config["password"], + "prefix" => "tembo", ]); + if (array_key_exists("error", $login)) + { + exit("Login Error: " . $login["error"]. PHP_EOL); + } + else + { + echo "Login Result: " . $login["code"] . ": " . $login["msg"][0] . PHP_EOL; + } + return $epp; + } + catch(EppException $e) + { + exit("Error : " . $e->getMessage().PHP_EOL); + } +} + +try { + // Fetch all domains + $stmt = $pdo->prepare('SELECT sld, tld FROM service_domain WHERE tld_registrar_id = :registrar'); + $stmt->bindValue(':registrar', $registrar_id); + $stmt->execute(); + $domains = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $epp = connectEpp($config); + + foreach ($domains as $domainRow) { + $domain = $domainRow['sld'] . $domainRow['tld']; + $params = ["domainname" => $domain]; + $domainInfo = $epp->domainInfo($params); + + if (array_key_exists("error", $domainInfo) || (isset($domainInfo['code']) && $domainInfo['code'] == 2303)) { + if (strpos($domainInfo["error"], "Domain does not exist") !== false || (isset($domainInfo['code']) && $domainInfo['code'] == 2303)) { + $stmt = $pdo->prepare('DELETE FROM service_domain WHERE sld = :sld AND tld = :tld'); + $stmt->bindValue(':sld', $domainRow['sld']); + $stmt->bindValue(':tld', $domainRow['tld']); + $stmt->execute(); + } + $stmt = $pdo->prepare('SELECT id FROM service_domain WHERE sld = :sld AND tld = :tld'); + $stmt->bindValue(':sld', $domainRow['sld']); + $stmt->bindValue(':tld', $domainRow['tld']); + $stmt->execute(); + $serviceDomain = $stmt->fetch(PDO::FETCH_ASSOC); + if ($serviceDomain) { + $serviceId = $serviceDomain['id']; + $stmt = $pdo->prepare('UPDATE client_order SET canceled_at = :canceled_at, status = :status, reason = :reason WHERE service_id = :service_id'); + $stmt->bindValue(':canceled_at', date('Y-m-d H:i:s')); + $stmt->bindValue(':status', 'cancelled'); + $stmt->bindValue(':reason', 'domain deleted'); + $stmt->bindValue(':service_id', $serviceId); + $stmt->execute(); + } + echo $domainInfo["error"] . " (" . $domain . ")" . PHP_EOL; + continue; + } + + $ns = $domainInfo['ns']; + + $ns1 = isset($ns[1]) ? $ns[1] : null; + $ns2 = isset($ns[2]) ? $ns[2] : null; + $ns3 = isset($ns[3]) ? $ns[3] : null; + $ns4 = isset($ns[4]) ? $ns[4] : null; + + $exDate = $domainInfo['exDate']; + $datetime = new DateTime($exDate); + $formattedExDate = $datetime->format('Y-m-d H:i:s'); + + $statuses = $domainInfo['status']; + + $clientStatuses = ['clientDeleteProhibited', 'clientTransferProhibited', 'clientUpdateProhibited']; + $serverStatuses = ['serverDeleteProhibited', 'serverTransferProhibited', 'serverUpdateProhibited']; + + // Check if all client statuses are present in the $statuses array + $clientProhibited = count(array_intersect($clientStatuses, $statuses)) === count($clientStatuses); + + // Check if all server statuses are present in the $statuses array + $serverProhibited = count(array_intersect($serverStatuses, $statuses)) === count($serverStatuses); + + if ($clientProhibited || $serverProhibited) { + $locked = 1; + } else { + $locked = 0; + } + + $sqlCheck = 'SELECT COUNT(*) FROM extension WHERE name = :name AND status = :status'; + $stmtCheck = $pdo->prepare($sqlCheck); + $stmtCheck->bindValue(':name', 'registrar'); + $stmtCheck->bindValue(':status', 'installed'); + $stmtCheck->execute(); + $count = $stmtCheck->fetchColumn(); + + // Prepare the UPDATE statement + $stmt = $pdo->prepare('UPDATE service_domain SET ns1 = :ns1, ns2 = :ns2, ns3 = :ns3, ns4 = :ns4, expires_at = :expires_at, locked = :locked, synced_at = :synced_at, transfer_code = :transfer_code WHERE sld = :sld AND tld = :tld'); + $stmt->bindValue(':ns1', $ns1); + $stmt->bindValue(':ns2', $ns2); + $stmt->bindValue(':ns3', $ns3); + $stmt->bindValue(':ns4', $ns4); + $stmt->bindValue(':expires_at', $formattedExDate); + $stmt->bindValue(':locked', $locked); + $stmt->bindValue(':synced_at', date('Y-m-d H:i:s')); + $stmt->bindValue(':transfer_code', $domainInfo["authInfo"]); + $stmt->bindValue(':sld', $domainRow['sld']); + $stmt->bindValue(':tld', $domainRow['tld']); + $stmt->execute(); + + $stmt = $pdo->prepare('SELECT id FROM service_domain WHERE sld = :sld AND tld = :tld'); + $stmt->bindValue(':sld', $domainRow['sld']); + $stmt->bindValue(':tld', $domainRow['tld']); + $stmt->execute(); + $serviceDomain = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($serviceDomain) { + $serviceId = $serviceDomain['id']; + $stmt = $pdo->prepare('UPDATE client_order SET expires_at = :expires_at WHERE service_id = :service_id'); + $stmt->bindValue(':expires_at', $formattedExDate); + $stmt->bindValue(':service_id', $serviceId); + $stmt->execute(); + + if ($count > 0) { + $stmt = $pdo->prepare('SELECT registrant_contact_id FROM domain_meta WHERE domain_id = :id'); + $stmt->bindValue(':id', $serviceId); + $stmt->execute(); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $registrant_contact_id = $result['registrant_contact_id']; + + $contact_params = ["contact" => $registrant_contact_id]; + $contactInfo = $epp->contactInfo($contact_params); + + if (array_key_exists('error', $contactInfo)) + { + echo 'ContactInfo Error: ' . $contactInfo['error'] . PHP_EOL; + } + else + { + try { + // Prepare the UPDATE statement + $stmt = $pdo->prepare(' + UPDATE service_domain + SET + contact_email = :contact_email, + contact_company = :contact_company, + contact_first_name = :contact_first_name, + contact_last_name = :contact_last_name, + contact_address1 = :contact_address1, + contact_address2 = :contact_address2, + contact_city = :contact_city, + contact_state = :contact_state, + contact_postcode = :contact_postcode, + contact_country = :contact_country, + contact_phone_cc = :contact_phone_cc, + contact_phone = :contact_phone + WHERE id = :id + '); + + // Split name into first and last names + $nameParts = explode(' ', $contactInfo['name']); + $contactFirstName = array_shift($nameParts); // First part of the name + $contactLastName = implode(' ', $nameParts); // Remaining parts as the last name + + // Split phone into country code and number + $phoneParts = explode('.', $contactInfo['voice']); + $contactPhoneCC = isset($phoneParts[0]) ? $phoneParts[0] : null; + $contactPhone = isset($phoneParts[1]) ? $phoneParts[1] : null; + + // Bind values, setting them to NULL if they are empty + $stmt->bindValue(':contact_email', !empty($contactInfo['email']) ? $contactInfo['email'] : null); + $stmt->bindValue(':contact_company', !empty($contactInfo['org']) ? $contactInfo['org'] : null); + $stmt->bindValue(':contact_first_name', !empty($contactFirstName) ? $contactFirstName : null); + $stmt->bindValue(':contact_last_name', !empty($contactLastName) ? $contactLastName : null); + $stmt->bindValue(':contact_address1', !empty($contactInfo['street1']) ? $contactInfo['street1'] : null); + $stmt->bindValue(':contact_address2', !empty($contactInfo['street2']) ? $contactInfo['street2'] : null); + $stmt->bindValue(':contact_city', !empty($contactInfo['city']) ? $contactInfo['city'] : null); + $stmt->bindValue(':contact_state', !empty($contactInfo['state']) ? $contactInfo['state'] : null); + $stmt->bindValue(':contact_postcode', !empty($contactInfo['postal']) ? $contactInfo['postal'] : null); + $stmt->bindValue(':contact_country', !empty($contactInfo['country']) ? $contactInfo['country'] : null); + $stmt->bindValue(':contact_phone_cc', !empty($contactPhoneCC) ? $contactPhoneCC : null); + $stmt->bindValue(':contact_phone', !empty($contactPhone) ? $contactPhone : null); + $stmt->bindValue(':id', $serviceId); + $stmt->execute(); + + echo "Update successful for contact: ".$contactInfo['id'].PHP_EOL; + + } catch (PDOException $e) { + exit("Database error: " . $e->getMessage().PHP_EOL); + } + } + } + } + + if ($count > 0) { + $selectStmt = $pdo->prepare('SELECT id FROM service_domain WHERE sld = :sld AND tld = :tld LIMIT 1'); + $selectStmt->bindValue(':sld', $domainRow['sld']); + $selectStmt->bindValue(':tld', $domainRow['tld']); + $selectStmt->execute(); + $domainId = $selectStmt->fetchColumn(); + + $sqlMeta = ' + INSERT INTO domain_meta (domain_id, registry_domain_id, registrant_contact_id, admin_contact_id, tech_contact_id, billing_contact_id, created_at, updated_at) + VALUES (:domain_id, :registry_domain_id, :registrant_contact_id, :admin_contact_id, :tech_contact_id, :billing_contact_id, NOW(), NOW()) + ON DUPLICATE KEY UPDATE + registry_domain_id = VALUES(registry_domain_id), + registrant_contact_id = VALUES(registrant_contact_id), + admin_contact_id = VALUES(admin_contact_id), + tech_contact_id = VALUES(tech_contact_id), + billing_contact_id = VALUES(billing_contact_id), + updated_at = NOW(); + '; + $stmtMeta = $pdo->prepare($sqlMeta); + $stmtMeta->bindValue(':domain_id', $domainId); + $stmtMeta->bindValue(':registry_domain_id', $domainInfo['roid']); + $stmtMeta->bindValue(':registrant_contact_id', $domainInfo['registrant']); + $admin_contact_id = null; + $tech_contact_id = null; + $billing_contact_id = null; + foreach ($domainInfo['contact'] as $contact) { + if ($contact['type'] === 'admin') { + $admin_contact_id = $contact['id']; + } elseif ($contact['type'] === 'tech') { + $tech_contact_id = $contact['id']; + } elseif ($contact['type'] === 'billing') { + $billing_contact_id = $contact['id']; + } + } + $stmtMeta->bindValue(':admin_contact_id', $admin_contact_id); + $stmtMeta->bindValue(':tech_contact_id', $tech_contact_id); + $stmtMeta->bindValue(':billing_contact_id', $billing_contact_id); + $stmtMeta->execute(); + + $status = $domainInfo['status'] ?? 'No status available'; + $sqlStatus = ' + INSERT INTO domain_status (domain_id, status, created_at) + VALUES (:domain_id, :status, NOW()) + ON DUPLICATE KEY UPDATE + status = VALUES(status), + created_at = NOW(); + '; + $stmtStatus = $pdo->prepare($sqlStatus); + + if (is_array($status)) { + foreach ($status as $singleStatus) { + $stmtStatus->bindValue(':domain_id', $domainId); + $stmtStatus->bindValue(':status', $singleStatus); + $stmtStatus->execute(); + } + } else { + $stmtStatus->bindValue(':domain_id', $domainId); + $stmtStatus->bindValue(':status', $status); + $stmtStatus->execute(); + } + } + echo "Update successful for domain: " . $domain . PHP_EOL; + } + + $logout = $epp->logout(); + echo "Logout Result: " . $logout["code"] . ": " . $logout["msg"][0] . PHP_EOL; +} catch (PDOException $e) { + exit("Database error: " . $e->getMessage().PHP_EOL); +} catch(EppException $e) { + exit("Error: " . $e->getMessage().PHP_EOL); +} + +// Mini EPP Client Class +class eppClient +{ + private $resource; + private $isLoggedIn; + private $prefix; + + public function __construct() + { + if (!extension_loaded('SimpleXML')) { + exit('PHP extension SimpleXML is not loaded.'.PHP_EOL); + } + } + + /** + * connect + */ + public function connect($params = array()) + { + $host = (string)$params['host']; + $port = (int)$params['port']; + $timeout = (int)$params['timeout']; + $tls = (string)$params['tls']; + $bind = (string)$params['bind']; + $bindip = (string)$params['bindip']; + if ($tls !== '1.3' && $tls !== '1.2' && $tls !== '1.1') { + exit('Invalid TLS version specified.'.PHP_EOL); + } + $opts = array( + 'ssl' => array( + 'verify_peer' => (bool)$params['verify_peer'], + 'verify_peer_name' => (bool)$params['verify_peer_name'], + 'verify_host' => (bool)$params['verify_host'], + 'cafile' => (string)$params['cafile'], + 'local_cert' => (string)$params['local_cert'], + 'local_pk' => (string)$params['local_pk'], + 'passphrase' => (string)$params['passphrase'], + 'allow_self_signed' => (bool)$params['allow_self_signed'], + 'min_tls_version' => $tls + ) + ); + if ($bind) { + $opts['socket'] = array('bindto' => $bindip); + } + $context = stream_context_create($opts); + $this->resource = stream_socket_client("tls://{$host}:{$port}", $errno, $errmsg, $timeout, STREAM_CLIENT_CONNECT, $context); + if (!$this->resource) { + exit("Cannot connect to server '{$host}': {$errmsg}".PHP_EOL); + } + + return $this->readResponse(); + } + + /** + * readResponse + */ + public function readResponse() + { + $hdr = stream_get_contents($this->resource, 4); + if ($hdr === false) { + exit('Connection appears to have closed.'.PHP_EOL); + } + if (strlen($hdr) < 4) { + exit('Failed to read header from the connection.'.PHP_EOL); + } + $unpacked = unpack('N', $hdr); + $xml = fread($this->resource, ($unpacked[1] - 4)); + $xml = preg_replace('/>\n<", $xml); + $this->_response_log($xml); + return $xml; + } + + /** + * writeRequest + */ + public function writeRequest($xml) + { + $this->_request_log($xml); + if (fwrite($this->resource, pack('N', (strlen($xml) + 4)) . $xml) === false) { + exit('Error writing to the connection.'.PHP_EOL); + } + $xml_string = $this->readResponse(); + libxml_use_internal_errors(true); + + $r = simplexml_load_string($xml_string, 'SimpleXMLElement', LIBXML_DTDLOAD | LIBXML_NOENT); + if ($r instanceof SimpleXMLElement) { + $r->registerXPathNamespace('e', 'urn:ietf:params:xml:ns:epp-1.0'); + $r->registerXPathNamespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $r->registerXPathNamespace('domain', 'urn:ietf:params:xml:ns:domain-1.0'); + $r->registerXPathNamespace('contact', 'urn:ietf:params:xml:ns:contact-1.0'); + $r->registerXPathNamespace('host', 'urn:ietf:params:xml:ns:host-1.0'); + $r->registerXPathNamespace('rgp', 'urn:ietf:params:xml:ns:rgp-1.0'); + } + if (isset($r->response) && $r->response->result->attributes()->code >= 2000) { + echo $r->response->result->msg.PHP_EOL; + } + return $r; + } + + /** + * disconnect + */ + public function disconnect() + { + if (!fclose($this->resource)) { + exit('Error closing the connection.'.PHP_EOL); + } + $this->resource = null; + } + + /** + * wrapper for functions + */ + public function __call($func, $args) + { + if (!function_exists($func)) { + exit("Call to undefined method Epp::$func().".PHP_EOL); + } + + if ($func === 'connect') { + try { + $result = call_user_func_array($func, $args); + } catch (\ErrorException $e) { + exit($e->getMessage().PHP_EOL); + } + + if (!is_resource($this->resource)) { + exit('An error occured while trying to connect to EPP server.'.PHP_EOL); + } + + $result = null; + } elseif (!is_resource($this->resource)) { + exit('Not connected to EPP server.'.PHP_EOL); + } else { + array_unshift($args, $this->resource); + try { + $result = call_user_func_array($func, $args); + } catch (\ErrorException $e) { + exit($e->getMessage().PHP_EOL); + } + } + + return $result; + } + + /** + * login + */ + public function login($params = array()) + { + $return = array(); + try { + $from = $to = array(); + $from[] = '/{{ clID }}/'; + $to[] = htmlspecialchars($params['clID']); + $from[] = '/{{ pwd }}/'; + $to[] = htmlspecialchars($params['pw']); + if (isset($params['newpw']) && !empty($params['newpw'])) { + $from[] = '/{{ newpw }}/'; + $to[] = PHP_EOL . ' ' . htmlspecialchars($params['newpw']) . ''; + } else { + $from[] = '/{{ newpw }}/'; + $to[] = ''; + } + $from[] = '/{{ clTRID }}/'; + $microtime = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($params['prefix'] . '-login-' . $microtime); + $xml = preg_replace($from, $to, ' + + + + {{ clID }} + {{ pwd }}{{ newpw }} + + 1.0 + en + + + urn:ietf:params:xml:ns:domain-1.0 + urn:ietf:params:xml:ns:contact-1.0 + urn:ietf:params:xml:ns:host-1.0 + http://www.verisign.com/epp/registry-1.0 + http://www.verisign.com/epp/lowbalance-poll-1.0 + http://www.verisign.com/epp/rgp-poll-1.0 + + urn:ietf:params:xml:ns:secDNS-1.1 + urn:ietf:params:xml:ns:epp:loginSec-1.0 + http://www.verisign.com/epp/whoisInf-1.0 + http://www.verisign.com/epp/idnLang-1.0 + urn:ietf:params:xml:ns:coa-1.0 + http://www.verisign-grs.com/epp/namestoreExt-1.1 + http://www.verisign.com/epp/sync-1.0 + http://www.verisign.com/epp/relatedDomain-1.0 + urn:ietf:params:xml:ns:verificationCode-1.0 + urn:ietf:params:xml:ns:rgp-1.0 + urn:ietf:params:xml:ns:changePoll-1.0 + + + + {{ clTRID }} + +'); + $r = $this->writeRequest($xml); + $code = (int)$r->response->result->attributes()->code; + if ($code == 1000) { + $this->isLoggedIn = true; + $this->prefix = $params['prefix']; + } + + $return = array( + 'code' => $code, + 'msg' => $r->response->result->msg + ); + } catch (\Exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + return $return; + } + + /** + * logout + */ + public function logout($params = array()) + { + if (!$this->isLoggedIn) { + return array( + 'code' => 2002, + 'msg' => 'Command use error' + ); + } + + $return = array(); + try { + $from = $to = array(); + $from[] = '/{{ clTRID }}/'; + $microtime = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->prefix . '-logout-' . $microtime); + $xml = preg_replace($from, $to, ' + + + + {{ clTRID }} + +'); + $r = $this->writeRequest($xml); + $code = (int)$r->response->result->attributes()->code; + if ($code == 1500) { + $this->isLoggedIn = false; + } + + $return = array( + 'code' => $code, + 'msg' => $r->response->result->msg + ); + } catch (\Exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + return $return; + } + + /** + * domainInfo + */ + public function domainInfo($params = array()) + { + if (!$this->isLoggedIn) { + return array( + 'code' => 2002, + 'msg' => 'Command use error' + ); + } + + $return = array(); + try { + $from = $to = array(); + $from[] = '/{{ domainname }}/'; + $to[] = htmlspecialchars($params['domainname']); + $from[] = '/{{ authInfo }}/'; + $authInfo = (isset($params['authInfoPw']) ? "\n\n" : ''); + $to[] = $authInfo; + $from[] = '/{{ clTRID }}/'; + $microtime = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->prefix . '-domain-info-' . $microtime); + $from[] = "/<\w+:\w+>\s*<\/\w+:\w+>\s+/ims"; + $to[] = ''; + $xml = preg_replace($from, $to, ' + + + + + {{ domainname }} + {{ authInfo }} + + + + + dotCOM + + + {{ clTRID }} + +'); + $r = $this->writeRequest($xml); + $code = (int)$r->response->result->attributes()->code; + $msg = (string)$r->response->result->msg; + $r = $r->response->resData->children('urn:ietf:params:xml:ns:domain-1.0')->infData; + $name = (string)$r->name; + $roid = (string)$r->roid; + $status = array(); + $i = 0; + foreach ($r->status as $e) { + $i++; + $status[$i] = (string)$e->attributes()->s; + } + $registrant = (string)$r->registrant; + $contact = array(); + $i = 0; + foreach ($r->contact as $e) { + $i++; + $contact[$i]['type'] = (string)$e->attributes()->type; + $contact[$i]['id'] = (string)$e; + } + $ns = array(); + $i = 0; + if (isset($r->ns->hostObj) && (is_array($r->ns->hostObj) || is_object($r->ns->hostObj))) { + foreach ($r->ns->hostObj as $hostObj) { + $i++; + $ns[$i] = (string)$hostObj; + } + } else { + $ns = []; + } + $host = array(); + $i = 0; + foreach ($r->host as $hostname) { + $i++; + $host[$i] = (string)$hostname; + } + $clID = (string)$r->clID; + $crID = (string)$r->crID; + $crDate = (string)$r->crDate; + $upID = (string)$r->upID; + $upDate = (string)$r->upDate; + $exDate = (string)$r->exDate; + $trDate = (string)$r->trDate; + $authInfo = (string)$r->authInfo->pw; + + $return = array( + 'code' => $code, + 'msg' => $msg, + 'name' => $name, + 'roid' => $roid, + 'status' => $status, + 'registrant' => $registrant, + 'contact' => $contact, + 'ns' => $ns, + 'host' => $host, + 'clID' => $clID, + 'crID' => $crID, + 'crDate' => $crDate, + 'upID' => $upID, + 'upDate' => $upDate, + 'exDate' => $exDate, + 'trDate' => $trDate, + 'authInfo' => $authInfo + ); + } catch (\Exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + return $return; + } + + /** + * contactInfo + */ + public function contactInfo($params = array()) + { + if (!$this->isLoggedIn) { + return array( + 'code' => 2002, + 'msg' => 'Command use error' + ); + } + + $return = array(); + try { + $from = $to = array(); + $from[] = '/{{ id }}/'; + $to[] = htmlspecialchars($params['contact']); + $from[] = '/{{ authInfo }}/'; + $authInfo = (isset($params['authInfoPw']) ? "\n\n" : ''); + $to[] = $authInfo; + $from[] = '/{{ clTRID }}/'; + $microtime = str_replace('.', '', round(microtime(1), 3)); + $to[] = htmlspecialchars($this->prefix . '-contact-info-' . $microtime); + $from[] = "/<\w+:\w+>\s*<\/\w+:\w+>\s+/ims"; + $to[] = ''; + $xml = preg_replace($from, $to, ' + + + + + {{ id }} + {{ authInfo }} + + + + + dotCOM + + + {{ clTRID }} + +'); + $r = $this->writeRequest($xml); + $code = (int)$r->response->result->attributes()->code; + $msg = (string)$r->response->result->msg; + $r = $r->response->resData->children('urn:ietf:params:xml:ns:contact-1.0')->infData[0]; + + foreach ($r->postalInfo as $e) { + $name = (string)$e->name; + $org = (string)$e->org; + $street1 = $street2 = $street3 = ''; + for ($i = 0; $i <= 2; $i++) { + ${'street' . ($i + 1)} = (string)$e->addr->street[$i]; + } + $city = (string)$e->addr->city; + $state = (string)$e->addr->sp; + $postal = (string)$e->addr->pc; + $country = (string)$e->addr->cc; + } + $id = (string)$r->id; + $status = array(); + $i = 0; + foreach ($r->status as $e) { + $i++; + $status[$i] = (string)$e->attributes()->s; + } + $roid = (string)$r->roid; + $voice = (string)$r->voice; + $fax = (string)$r->fax; + $email = (string)$r->email; + $clID = (string)$r->clID; + $crID = (string)$r->crID; + $crDate = (string)$r->crDate; + $upID = (string)$r->upID; + $upDate = (string)$r->upDate; + $authInfo = (string)$r->authInfo->pw; + + $return = array( + 'id' => $id, + 'roid' => $roid, + 'code' => $code, + 'status' => $status, + 'msg' => $msg, + 'name' => $name, + 'org' => $org, + 'street1' => $street1, + 'street2' => $street2, + 'street3' => $street3, + 'city' => $city, + 'state' => $state, + 'postal' => $postal, + 'country' => $country, + 'voice' => $voice, + 'fax' => $fax, + 'email' => $email, + 'clID' => $clID, + 'crID' => $crID, + 'crDate' => $crDate, + 'upID' => $upID, + 'upDate' => $upDate, + 'authInfo' => $authInfo + ); + } catch (\Exception $e) { + $return = array( + 'error' => $e->getMessage() + ); + } + + return $return; + } + + public function _response_log($content) + { + $handle = fopen(dirname(__FILE__) . '/data/log/verisign_response.log', 'a'); + ob_start(); + echo "\n==================================\n"; + ob_end_clean(); + fwrite($handle, $content); + fclose($handle); + } + + public function _request_log($content) + { + $handle = fopen(dirname(__FILE__) . '/data/log/verisign_request.log', 'a'); + ob_start(); + echo "\n==================================\n"; + ob_end_clean(); + fwrite($handle, $content); + fclose($handle); + } +} \ No newline at end of file diff --git a/install_verisign_module.sh b/install_verisign_module.sh new file mode 100644 index 0000000..6c6efb3 --- /dev/null +++ b/install_verisign_module.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Ask for FOSSBilling directory +echo "Enter the path to the FOSSBilling directory (default is /var/www):" +read -r fossbilling_path +fossbilling_path=${fossbilling_path:-/var/www} + +# Clone the repository to /tmp +git clone https://github.com/getnamingo/fossbilling-epp-verisign /tmp/fossbilling-epp-verisign + +# Move the VeriSign.php file +mv /tmp/fossbilling-epp-verisign/VeriSign.php "$fossbilling_path/library/Registrar/Adapter/VeriSign.php" + +# Move the VeriSignSync.php file +mv /tmp/fossbilling-epp-verisign/VeriSignSync.php "$fossbilling_path/VeriSignSync.php" + +# Add the cron job +(crontab -l 2>/dev/null; echo "0 0,12 * * * php $fossbilling_path/VeriSignSync.php") | crontab - + +# Clean up +rm -rf /tmp/fossbilling-epp-verisign + +# Final instructions +echo "Installation complete." +echo "" +echo "1. Activate the Domain Registrar Module:" +echo "Within FOSSBilling, go to System -> Domain Registration -> New Domain Registrar and activate the new domain registrar." +echo "" +echo "2. Registrar Configuration:" +echo "Next, head to the 'Registrars' tab. Here, you'll need to enter your specific configuration details, including the path to your SSL certificate and key." +echo "" +echo "3. Adding a New TLD:" +echo "Finally, add a new Top Level Domain (TLD) using your module from the 'New Top Level Domain' tab. Make sure to configure all necessary details, such as pricing, within this tab." \ No newline at end of file