Создание дополнительных модулей регистраторов доменов

Материал из ISPWiki

Перейти к: навигация, поиск

Чтобы создать своего регистратора, достаточно написать скрипт с именем следующего формата dr[nameregistrar] и поместить в папку /usr/local/ispmgr/sbin. Набор символов, следующий после dr, будет внутренним названием регистратора, и элементы интерфейса будут использовать именно это название.

Содержание

Список возможностей скрипта

Скрипт должен поддерживать следующие команды:

check <domain name> <registrar_id> - проверяет домен на занятость. Если домен свободен, то завершаться с кодом 0, иначе - с кодом 1.

register <domain_id> - регистрация домена

renew <domain_id> - продление домена

transfer <domain_id> - трансфер домена

update ns <domain_id> - обновление NS-серверов домена

set status <domain_id[,domain_id[...]]> - определение статуса домена и срока регистрации

fix <domain_id[,domain_id[...]]> - синхронизация данных регистратора и биллинга. Выполняется раз в месяц.

validate - проверка данных контактов домена. В stdin получает xml.

Статусы домена

1 - Не оплачен;
2 - Делегирован (Активен);
3 - Зарегистрирован (Неделегирован);
4 - Удален;
5 - Обрабатывается (На регистрации);
6 - Обрабатывается (На продлении);
7 - Обрабатывается (Трансфер).

Команда validate

Проверяет данные контакта при регистрации домена. На stdin получает xml следующего формата:

<?xml version="1.0" encoding="UTF-8"?>
<doc>
  <node name="NAME_FIELD1">VALUE1</node>
  <node name="NAME_FIELD1">VALUE1</node>
</doc>

Скрипт должен завершится с кодом 0. В случае есть поле не верное должно вернуть в stdout имя поля (например NAME_FIELD1).

Команда fix

Для того что-бы данные биллинга были корректны, необходимо синхронизировать их с данными регистратора. Каждый домен проверяется раз в месяц.
Данные регистратора приоритетней данных биллинга.
Стоит отметить, что наиболее значимые для биллинга поля это статус домена, дата окончания регистрации.

База данных

Для написания модуля нам понадобятся данные из таблиц domain, tld, domaincontact, registrar. Структуру domain, domaincontact вы можете посмотреть в разделе Структура базы данных.

Таблица tld - Домены верхнего уровня

  • id - Код зоны
  • name - Наименование зоны

Таблица registrar - Домены верхнего уровня

  • id - Код регистратора
  • name - Наименование регистратора
  • url - URL-адрес регистратора
  • username - Логин авторизации
  • password - Пароль авторизации

Это не полное описание таблиц.

Пример скрипта на PHP

Рассмотрим пример скрипта, написанного на PHP для регистратора Webnames. Создайте файл /usr/local/ispmgr/sbin/drphpwebnames с содержанием, приведенным ниже. После этого регистратор станет доступным в интерфейсе биллинга. Далее все настройки производятся как для обычных регистраторов.

#!/usr/bin/php
<?php
/*
Описываем класс Регистратора.
*/
class WebNames {
      private $url;
      private $login;
      private $password;

//Конструктор класса. Сохраняем url, логин, пароль для доступа к шлюзу.
       public function __construct($login, $password, $url) {
		$this->url = $url; 
		$this->login = $login;
		$this->password = $password;
	}

//Функция для отправки запросов к серверу регистратора. Возвращает строку ответа.
	public function invoke($request) {
		$arr = array("username" => $this->login, "password" => $this->password, "interface_revision" => "1","interface_lang" => "ru") + $request;
		$data = "";
		foreach ($arr as $key => $val) {
			$data .= "&".$key."=".$val;
		}
		$data = iconv("UTF-8" ,"cp1251", $data);
		Debug("Response: \n" .$data);
		$curl_client = curl_init($this->url);
		curl_setopt($curl_client, CURLOPT_SSL_VERIFYPEER, 1);
		curl_setopt($curl_client, CURLOPT_POST, 1);
		curl_setopt($curl_client, CURLOPT_RETURNTRANSFER, "1");
		curl_setopt($curl_client, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
		curl_setopt($curl_client, CURLOPT_POSTFIELDS, $data);
		$output = curl_exec($curl_client);
		Debug("Request: \n".$output);
		return $output;
      }

//Функция проверки статуса домена. Если домен свободен, возвращает true, иначе false
	public function Check($DomainName) {
		$data = array("thisPage" => "pispCheckDomain","domain_name" => $DomainName);
		$res = $this->invoke($data);
		if (substr_count($res, "is Available") != 0)
			return true;
		return false;
	}
//Функция регистрации домена, принимает массив с данными домена. 
	public function Register($domain) {
		$data = array("thisPage" => "pispRegistration", 
				"domain_name" => $domain["dname"],  
				"period" =>  GetPeriod($domain["pid"])) + 
				$this->GetContact($domain) +
				$this->GetNS($domain);
		return $this->invoke($data);
	}
 //Функция трансфера домена, принимает массив с данными домена. 
	public function Transfer($domain) {
		$data = array("thisPage" => "pispInitiateTransfer",
			"domain_name" => $domain["dname"],
			"period" => GetPeriod($domain["pid"]), 
			"authinfo" => $domain["auth_code"]) + 
			$this->GetContact($domain) +
			$this->GetNS($domain);

		return $this->invoke($data);
	}
 //Функция продления домена, принимает массив с данными домена. 
	public function Renew($domain) {
		$data = array("thisPage"=>"pispRenewDomain",
			"domain_name" => $domain["dname"],
			"period" => GetPeriod($domain["pid"]));
		return $this->invoke($data);
	}
//Функция обновления NS-серверов домена, принимает массив с данными домена. 
	public function UpdateNS($domain) {
		$data = array("thisPage" => "pispRedelegation",
			"domain_name" => $domain["dname"]) + $this->GetNS($domain);
		return $this->invoke($data);
	}
 //Функция определения статуса домена, используется для определения срока регистрации и статуса домена
	public function SetStatus($domain) {
		$data = array("thisPage" => "pispDomainInfo",
			"domain_name" => $domain["dname"]);
		return $this->invoke($data);
	}
 //Функция возвращает массив с контактными данными домена. Используется во время регистрации и трансфера домена.
	function GetContact($domain) {
		$message = array();

		if ($domain["bill"] == "") {
			$query = mysql_query("select * from domaincontact where id =  ".$domain["owner"]);
			$owner = mysql_fetch_array($query);
			if ($owner["ctype"] == "company")
				$message += array(
					"org" => $owner["company"], 
					"org_r" => $owner["company_ru"], 
					"kpp" => $owner["kpp"], 
					"address_r" => $owner["la_state"]." ".$owner["la_postcode"]." ".$owner["la_city"]." ".$owner["la_address"]
				);
			else {
				$birthdate = substr($owner["birthdate"], 8, 2).".".substr($owner["birthdate"], 5, 2).".".substr($owner["birthdate"], 0, 4);

			$message += array(
					"person" => $owner["firstname"]." ".$owner["middlename"]." ".$owner["lastname"],
					"private_person" => $owner["private"],
					"person_r" => $owner["lastname_ru"]." ".$owner["firstname_ru"]." ".$owner["middlename_ru"],
					"birth_date" => $birthdate,
					"passport" => $owner["passport_series"]. " выдан ".$owner["passport_org"]." ".substr($owner["passport_date"], 8, 2).".".substr($owner["passport_date"], 5, 2).".".substr($owner["passport_date"], 0, 4),
					"residence" => $owner["la_postcode"]." ".$owner["la_state"]." ".$owner["la_city"]." ".$owner["la_address"]
				);
			}

			$fax = ($owner["fax"] <> ""?"%2B".substr($owner["fax"],1):"");
			$message += array(
				"code" => $owner["inn"],
				"phone" => "%2B" . substr($owner["phone"],1),
				"fax" => $fax,
				"e_mail" => $owner["email"], 
				"country" => $this->GetCountry($owner["la_country"]),
				"p_addr" => $owner["pa_postcode"].", ".$owner["pa_state"].", ".$owner["pa_city"].", ".$owner["pa_address"].", ".$owner["pa_addressee"]
				);
		} else {
			$ctype = array("owner", "admin", "tech", "bill");
			for ($i = 0; $i < 4; $i++) {
				$query = mysql_query("select * from domaincontact where id =  ".$domain[$ctype[$i]]);
				$contact = mysql_fetch_array($query);
				$message += array(
					$ctype[$i]{0} . "_company" => $contact["company"],
					$ctype[$i]{0} . "_first_name" => $contact["firstname"],
					$ctype[$i]{0} . "_last_name" => $contact["lastname"],
					$ctype[$i]{0} . "_email" => $contact["email"],
					$ctype[$i]{0} . "_phone" => $this->GetPhone($contact["phone"]),
					$ctype[$i]{0} . "_fax" => $this->GetPhone($contact["fax"]),
					$ctype[$i]{0} . "_addr" => $contact["la_address"],
					$ctype[$i]{0} . "_city" => $contact["la_city"],
					$ctype[$i]{0} . "_state" => $contact["la_state"],
					$ctype[$i]{0} . "_postcode" => $contact["la_postcode"],
					$ctype[$i]{0} . "_country_code" => $this->GetCountry($contact["la_country"])
				);
			}
		}

		return $message ;
	}
//Функция возвращает данные с NS-серверами домена. Используется во время регистрации, трансфера, смены NS-серверов.
	function GetNS($domain) {
		$message = array();
		for ($i = 0; $i < 4; $i++) {
			$value = $domain["ns".$i];

			if ($value <> "") {
				if (substr_count($value, "/") <> 0) {
					$host = strtok($value, "/");
					$ip = strtok("");
					$message += array("ns".$i => $value,
							  "ns".$i."ip" => $ip);
				} else
					$message += array("ns".$i => $value);
			}
		}

		return $message;
	}
//Получение ISO кода страны по ID в базе данных
	function GetCountry($id) {
		$query = mysql_query("select * from country where id =  ".$id);
		$contact = mysql_fetch_array($query);
		return $contact["iso2"];
	}
 //Возвращает отформатированный телефон, нужен для регистрации gTLD доменов, используется в функции GetContact.
	function GetPhone($phone) {
		$pos = strpos($phone, " ");
		$res = "%2B" . substr($phone, 1, $pos-1).".";
		$pos2 = strpos($phone, " ", $pos+1);
		$res .= substr($phone, $pos+1, $pos2-$pos-1).substr($phone, $pos2+1);
		return $res;
	}
 //Функция принимает строку ответа от регистратора $res, имя операции, id домена - эти данные необходимы, чтобы вернуть в биллинг информацию, в случае определения ошибки. Парсит строку и возвращает массив с параметрами ключ => значение.
	public function GetResultParam($DomainID, $operation, $res) {
		$result = array();
		$pos = strpos($res, ":");
		$stat = substr($res, 0, $pos);
		$res = substr($res, $pos+1);
		
		if ($stat <> "Success") {
			echo "\nerror\n";
			if ($stat == "Error") {
				SendResult($DomainID, $operation, $res);
				exit(1);
			}
			SendResult($DomainID, $operation, "The registrar has returned an error");
			exit(1);
		}

		$arr = explode(";", $res);
		foreach ($arr as $str) {
			if ($str == "")
				break;
			$str = trim($str);
			list($key, $value) = explode(" ", $str);
			$result += array($key => $value);
		}
		SendResult($DomainID, $operation, "OK");
		return $result;
	}
} // закрытие класса WebNames

//Устанавливаем свой обработчик ошибок для записи их в лог файл вместо передачи в стандартный поток вывода: 
set_error_handler("tmErrorHandler");

// Открываем файл лога
$log_file = fopen("/usr/local/ispmgr/var/drphpwebnames.log", "a");
fwrite($log_file, "=======".date("M j H:i:s") . "[] " ."=======\n");

// Чтение файла конфигурации биллинга для определения параметров подключения к базе данных

$arr = file ("/usr/local/ispmgr/etc/billmgr.conf"); 

$conf = array();
 //Парсим конфиг и записываем в массив
foreach ($arr as $str) {
	list($key, $value) = explode(" ", $str);
	if ($key <> "path" && $value != "") {
		$conf += array(chop($key) => chop($value));
	}
} 
//опеределяем переменные для подключения к базе данных
$dbhost = ($conf["DBHost"] == "") ? "localhost": $conf["DBHost"];// Имя хоста БД
$dbusername = ($conf["DBUser"] == ""? "root": $conf["DbUser"]); // Пользователь БД
$dbpass = $conf["DBPassword"]; 					// Пароль к базе
$dbname = ($conf["DBName"] == ""? "billmgr": $conf["DBName"]);  // Имя базы

// Подключение к базе данных
$dbconnect = @mysql_connect ($dbhost, $dbusername, $dbpass) or exit("Не могу подключиться к серверу базы данных!"); 
mysql_set_charset("utf8", $dbconnect);
mysql_select_db($dbname) or exit("Не могу подключиться к базе данных $dbname!");

//Выводим в лог принятые параметры
foreach($argv as $line_num => $line) {
	Debug($line_num.":".$line);
}

//Выводим информацию по скрипту, если параметры переданны неверно.
if ($argc<2) {
	usage();
	exit(1);
}
//Оперделяем переменные.
$domain_id = "";
$registrar_id = "";
$DomainName = "";
$domain = array();
$registrar = array();
$url="";
$username="XXXXXXXX";
$password="XXXXXXXX";

//Анализируем входящие параметры и присваиваем значения соответсвующим переменным. Если параметров не хватает,  выводим usage()
//первый параметр - всегда название операции
if (($argv[1] == "register" || $argv[1] == "renew" || $argv[1] == "transfer") && ($argv[2] != "")) {
	$domain_id = $argv[2]; 
}
elseif (($argv[1] == "set" || $argv[1] == "update") && ($argv[3] != "")) {
	$domain_id = $argv[3];
}
elseif ($argv[1] == "check" && $argv[2] != "" && $argv[3] != "") {
	$DomainName = $argv[2];
	$registrar_id = $argv[3];
}
else {
	usage();
	exit();
}

//Если id домена не пустой, то получаем информацию из базы данных. Также заполняется регистратор, id регистратора берется по полю registrar в таблице domain
if ($domain_id != "") {
	global $domain, $DomainName;

	$query = mysql_query("SELECT * FROM domain where pid = ".$domain_id);
	$domain = mysql_fetch_array($query);
	$query = mysql_query("SELECT * FROM tld where id = ".$domain["tld"]);
	$tld = mysql_fetch_array($query); // получаем домен верхнего уровня из таблицы tld. 

	$DomainName = $domain["name"] . "." . $tld["name"]; //Опеределяем полное имя домена
	$domain += array("dname" => $DomainName);

	Debug("domainname = $DomainName");
	$registrar_id = $domain["registrar"];
}
//Если id регистратора не пустой, берем информацию из базы, иначе выходим с ошибкой.
if ($registrar_id != "") {
	global $url, $username, $password, $registrar;
	$query = mysql_query("SELECT * FROM registrar where id = '".$registrar_id."'");
	$registrar = mysql_fetch_array($query) or die('Invalid query: ' . mysql_error());
	if (!$registrar) {
		Debug("Registrar not found");
		exit(1);
	}

	$url = ($registrar["url"] != ""? $registrar["url"]:$url);
	$username = $registrar["username"];
	$password = $registrar["password"];

}
else {
	Debug("Registrar empty");
	exit(1);
}
//Создаем объект класса WebNames. Входящие параметры - то, что получили из базы
$regmodule = new WebNames($username, $password, $url);

//Определяем операцию
switch($argv[1]) {
	case "check":
//проверяем на занятость
		$res = $regmodule->Check($DomainName);
		if ($res == false)
			exit(1); //Обязательно завершаемся с кодом 1

		break;
	
	case "register":
//Регистрируем домен
		$res = $regmodule->Register($domain);
//Результат анализируем
		$regmodule->GetResultParam($domain_id, $argv[1], $res);
		break;

	case "transfer":
//Трансфер домена
		$res = $regmodule->Transfer($domain);
		$regmodule->GetResultParam($domain_id, $argv[1], $res);
		break;

	case "renew":
//Продление домена
		$res = $regmodule->Transfer($domain);
		$regmodule->GetResultParam($domain_id, $argv[1], $res);
		break;

	case "set":
//Важно! В примере расматривается случай, когда передается только один id домена, в общем случае их может быть несколько, id перечисленны через запятую без пробела

//Получаем статус домена
		$res = $regmodule->SetStatus($domain);
//Получаем массив параметров ответа регистратора
		$resparam = $regmodule->GetResultParam($domain_id, $argv[1], $res);
//Преобразуем статус webnames в статус биллинга
		$status = $resparam["Status"];
		if ($status{0} == 'Y')
			$status = "2";
		else if ($status{0} == 'N' || $status{0} == 'B')
			$status = "3";
		else if ($status{0} == 'T')
			$status = "6";
//Возвращаем биллингу полученные данные. Вызывается функция domain.set
		exec("/usr/local/ispmgr/sbin/mgrctl -m billmgr -o xml  domain.set item=" . $domain_id . " status=" . $status . " expire=".$resparam["ExpirationDate"]);

		break;

	case "update":
//Обновление NS-серверов
		$res = $regmodule->UpdateNS($domain);
		$regmodule->GetResultParam($domain_id, $argv[1], $res);
		break;
}

//Функция check запускается не LongTask. Для других функций говорим биллингу, что завершаемся успешно
if ($argv[1] != "check")
	exec("/usr/local/ispmgr/sbin/mgrctl -m billmgr -o xml longtask.finish elid=". getenv("MGR_LT_PID") ." status=ok");

exit(0);

//Функция вывода в лог принятой строки
function Debug($data) {
	global $log_file;
	fwrite($log_file, date("M j H:i:s") . " " . $data."\n");
} 

//Обработчик ошибок
function tmErrorHandler($errno, $errstr, $errfile, $errline) {
	global $log_file;
	fwrite($log_file, date("M j H:i:s") . " " . "Error [" . $errno . "] ErrMsg: " . $errstr . ". In file: " . $errfile . ". In line: " . $errline . "\n");
   return true;
}

//Функция возвращат биллингу информацию о том, как прошла операция по домену. Используестя в GetResultParam. 
//domain.result - весит банер и отправляет письмо в случае ошибки
function SendResult($DomainID, $operation, $res) {
	if ($res == "OK") {
		exec("/usr/local/ispmgr/sbin/mgrctl -m billmgr -o xml  domain.result item=".$DomainID." operation=".$operation." state=ok");
	}
	else {
//стоит отметить: если есть возможность распарстиь строку ответа регистратора и определить, в каком поле ошибся пользователь, то их можно передать через параметр  errorparams. Например, 'errorparams=passport,email,phone'. contactid - id контакта, в которм обнаружены ошибки

		exec("/usr/local/ispmgr/sbin/mgrctl -m billmgr -o xml  domain.result item=".$DomainID." operation=".$operation." state=error message=\"".$res."\" errorparams=  contactid=");
	}
}
//Определяем длинну периода. Возможно, они заданы в месяцах, днях. 
function GetPeriod($domain_id) {
	$query = mysql_query("select p.plength as plength, p.ptype as type from item i left join priceperiod p on i.period = p.id where i.id =  ".$domain_id);
	$entry = mysql_fetch_array($query);
	$period = $entry["plength"];
	switch ($entry["type"]) {
	case 1: // month
		$period = $period / 12;
		break;
	case 2: // day
		$period = $period / 365;
		break;
	case 3: // year
		break;
	default:
		throw Exception('invalid period');
	}

	return $period;
}


//Функция вывода информации по использованию скрипта: 
function usage() {
	print(
	    "Commands:                                  \n" .
	    "  check <domain name> <registrar_id>       \n" .
	    "  register <domain_id>                     \n" .
	    "  renew <domain_id>                        \n" .
	    "  transfer <domain_id>                     \n" . 
	    "  set status <domain_id[,domain_id[...]]>  \n" .
	    "  update ns <domain_id>                    \n"
	    );
}

?>
Была ли эта информация полезной? Да | Нет