Яндекс.Метрика

    Ни о чём

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

    В сентябре прошлого 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-хостинге Яндекса.

    скачать исходный код и пример конфига