Pull to refresh

DNS-хостинг Яндекса vs Динамический IP

Reading time6 min
Views27K
В сентябре прошлого 2010 года компания Яндекс открыла для публичного использования DNS-хостинг в рамках Почты для доменов. Радости пользователей не было предела, топик был встречен массой положительных комментариев, а Яндекс был объявлен корпорацией добра.

К сожалению администрирование DNS-записей было предусмотрено только через web-интерфейс. API для администрирования предусмотрено не было, до сих пор не появилось, и возможно еще долго не появится. Этот факт опечалил многих владельцев доменов с динамическим IP не меньше, чем перевод отличного бесплатного сервиса free.editdns.net на платную основу (для custom доменов), в связи с покупкой последнего компанией DynDNS.

Убедившись, что чуда не случилось, я взял в руки Python напильник с целью исправить эту несправедливость…

Первым делом я отправился к всезнающему Google в поисках хоть какой нибудь информации об API сервисов Яндекса. Первым мне попалось подробное описание API Почты для доменов. Увы, из 32 имеющихся функций в нем не оказалось ничего, связанного с администрированием DNS-хостинга, и я продолжил поиски. Добавив к запросу волшебные слова python, а затем и c sharp, я наткнулся на статью Алексея Немиро с подробным описанием работы браузера при авторизации на сервисах Яндекса и примерами кода на VB и C#.

Прочитав статью и убедившись, что все же придется имитировать браузер, я вооружился FireBug'ом и HTTP Analyzer'ом. Потратив немного времени на изучение тонкостей авторизации и работы с DNS-хостингом, я выяснил, что авторизация на сервисах Яндекса работает достаточно просто. Процедура авторизации начинается с куки yandexuid, получаемой при входе на любой сервис Яндекса:

Copy Source | Copy HTML
  1. def initialize(self):
  2.     connection = httplib.HTTPConnection('www.yandex.ru')
  3.     connection.request('GET', '/')
  4.  
  5.     response = connection.getresponse()
  6.     cookies = response.getheader('set-cookie', None)
  7.     response.close()
  8.  
  9.     match = re.search('(?<=yandexuid=)[^;]*', cookies)
  10.     self._yandexuid = match.group( 0)
  11.     print 'yandexuid =', self._yandexuid


Получив куку yandexuid браузер передает POST-запросом логин, пароль и таймстамп в формате UNIX. Если с формированием запроса проблем не возникло, то с формулой таймстампа я просидел долго:

Copy Source | Copy HTML
  1. def login(self):
  2.     content = 'login={0}&passwd={1}&timestamp={2}'
  3.     content = content.format(self._login, self._passwd, self.timestamp())
  4.  
  5.     connection = httplib.HTTPConnection('passport.yandex.ru')
  6.     connection.request('POST', '/passport?mode=auth', content, {'Cookie': self.getcookies()})
  7.  
  8.     response = connection.getresponse()
  9.     content = response.read()
  10.     response.close()
  11.  
  12.     match = re.search('idkey\"\s.*', content)
  13.     match = re.search('(\d\w*)', match.group( 0))
  14.     self._idkey = match.group( 0)
  15.     print 'idkey =', self._idkey


Получив волшебный idkey, в ответ на запрос «Установить постоянную авторизацию на этом компьютере» потребовалось сформировать запрос, имитирующий нажатие кнопки «Нет»:

Copy Source | Copy HTML
  1. def authenticate(self):
  2.     content = 'filled=yes&timestamp={0}&idkey={1}&no=%D0%9D%D0%B5%D1%82'
  3.     content = content.format(self.timestamp(), self._idkey)
  4.  
  5.     connection = httplib.HTTPConnection('passport.yandex.ru')
  6.     connection.request('POST', '/passport?mode=auth', content, {'Cookie': self.getcookies()})
  7.  
  8.     response = connection.getresponse()
  9.     cookies = response.getheader('set-cookie', None)
  10.  
  11.     ... парсинг кук регекспами ...
  12.  
  13.     response.close()


Теперь, имея под рукой все необходимые куки, для работы с Почтой для доменов достаточно имитировать нажатие кнопки «Сохранить» в редакторе DNS-записей через внутренний AJAX API:

Copy Source | Copy HTML
  1. def updatedomain(self, ns_record_id):
  2.     content = 'domain={0}&ns_record_id={1}&ns_rec_type=A&ns_subdomain=%40&ns_weight=&ns_port=&ns_content={2}&ns_priority=1'
  3.     content = content.format(self._domain, ns_record_id, self._externalip)
  4.  
  5.     connection = httplib.HTTPSConnection('pdd.yandex.ru')
  6.     connection.request('POST', '/ajax/ns_simple_record_edit.ajax.xml', content,\
  7.                       {'Accept': 'application/json, text/javascript, */*',\
  8.                        'Cookie': self.getcookies()})
  9.     response = connection.getresponse()
  10.     response.close()


Как и в предыдущем случае, на этом этапе заголовок Accept оказался обязательным. Впрочем это не самое важное. Для получения ns_record_id пришлось разобрать HTML-код страницы со списком DNS-записей:

Copy Source | Copy HTML
  1. def domainlist(self):
  2.     connection = httplib.HTTPSConnection('pdd.yandex.ru')
  3.     connection.request('GET', '/domain_ns/{0}/'.format(self._domain), None,\
  4.                       {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\
  5.                        'Cookie': self.getcookies(),\
  6.                        'Referer': 'https://pdd.yandex.ru'})
  7.  
  8.     response = connection.getresponse()
  9.     content = response.read()
  10.  
  11.     block = re.findall('item:\s\'[\d]+\'(.+)value="[\w\.]+"', content)
  12.  
  13.     for item in block:
  14.         match = re.search('(?<=item:\s\')[\d]*', item)
  15.         ns_record_id = match.group( 0)
  16.  
  17.         match = re.search('ns_subdomain(.+?)value=\"(.+?)\"', item)
  18.         match = re.search('(?<=value=)\".+?\"', match.group( 0))
  19.         ns_subdomain = match.group( 0)
  20.  
  21.         match = re.search('ns_rec_type(.+?)value=\"(.+?)\"', item)
  22.         match = re.search('(?<=value=)\".+?\"', match.group( 0))
  23.         ns_rec_type = match.group( 0)
  24.  
  25.         match = re.search('ns_content(.+?)value=\"(.+?)\"', item)
  26.         match = re.search('(?<=value=)\".+?\"', match.group( 0))
  27.         ns_content = match.group( 0)
  28.  
  29.         record = 'ns_record_id = {0} | ns_subdomain = {1} | ns_rec_type = {2} | ns_content = {3}'
  30.         print record.format(ns_record_id, ns_subdomain, ns_rec_type , ns_content)
  31.  
  32.     response.close()


Так как это была моя первая программа на Python, я ограничился httplib и ручным формированием кук. Добавив в этот коктейль параметр командной строки, парсинг конфига и получение внешнего IP, я получил простой скрипт для обновления DNS-записи на DNS-хостинге Яндекса.

скачать исходный код и пример конфига.
Tags:
Hubs:
+38
Comments35

Articles