Dyndns api support for VestaCP [PHP] + client script [Python]

Section with additional software for Vesta
Posts: 1
Joined: Tue Sep 06, 2016 8:17 pm

Dyndns api support for VestaCP [PHP] + client script [Python]

Postby sq7lqw » Tue Sep 06, 2016 11:19 pm

As this is my first post in this forum Id like to say Hi to everyone, and I also liked to say thanks to the authors of VestaCP panel.

I'd like to share a small simple but very usefull script which helped me change VestaCP into a dyndns server, where a client server can query a certain link from our VestaCP server and change chosen DNS records of a domain, whole records for a chosen domain, check its IP.

In order to achive this some backend script (1 file - php only) got to be placet in your VestaCP main www url.
For sake of this case, we will use example.com as a server address, %usr% and %pass% as user login and password.

Current behavior/limitations:
- user can list only domains/subdomain which he owns and are set up undr DNS section in VestaCP panel,
- updating mail subdomain will also change ip address under TXT record (two DNS entries at the same go)
(ip address will be extracted and replaced with new ip, no other modification will be done with TXT DNS entry)
- updating main domain entry will update all domain A records and TXT record
- once domain/subdomain is updated TTL time of the main domain will be reduced down to 300s [5m]
- changes are done only when new ip address is detected (will be different than in DNS entry)

I recomend two case scenario of usage,
Detect IP address change on a dyndns client server and update when needed

Update your IP address every few minutes, only new ip address if detected will couse changes.

example use of the script in linux bash:

Case were we have no valid SSL certificates installed on server

Code: Select all

wget --no-check-certificate https://example.com:8083/api/dyndns/?do=ip

Case where domain certificate is valid and installed on server

Code: Select all

wget https://example.com:8083/api/dyndns/?do=ip

Full commands list is as follow:
Client IP address check

Code: Select all


Will simply give us an IP address from we are connecting

Code: Select all

All main domains listing

Code: Select all


will answer us with a list of domains which are set under DNS section in VestaCP panel by our %usr%
An example:

Code: Select all


All main domains & subdomains

Code: Select all


Will give us full list of domains, and all A records which are set under DNS in VestaCP panel by our %usr%
An example:

Code: Select all


Update a domain or subdomain with IP from which we are connecting

Code: Select all


When main domain is used it will update all A records available under that domain, when subdomain is used it will update only that subdomain. It will be an IP address from which we are connecting

Update a domain or subdomain with selected IP

Code: Select all


When main domain is used it will update all A records available under that domain, when subdomain is used it will update only that subdomain. with an IP address specified under &ip=...

Possible all answers received from script

Code: Select all

OK - record updated and changes saved in name server
NOK - record already match with DNS entry, nothing to update
AUTH - incorrect user/password
DOMAIN_REQUIRED - there is no &domain=... specified
NOT_EXISTS - specified subdomain/domain was not found under choosen username
DO_UNKNOWN - unrecognized command specified under &do=...
DO_REQUIRED - there is no &do=...  specified

How/where to install
- create a directory named as dyndns in /usr/local/vesta/web/api
- create a file index.php in /usr/local/vesta/web/api/dyndns/index.php
- copy below code and save into this file:

Code: Select all

define('VESTA_CMD', '/usr/bin/sudo /usr/local/vesta/bin/');

function clear_spaces($txt)
   while(strpos($txt,'  ')!==false) $txt=str_replace('  ',' ',$txt);   
   return $txt;

function list_domains($v_user)
   GLOBAL $domains;
   if (count($domains)>=1) return $domains;
   exec(VESTA_CMD ."v-list-dns-domains ".$v_user,  $output, $auth_code);
   if (count($output)==2) return array();   
   foreach($output as $dom)
      $dom=explode(' ',clear_spaces($dom));   
   return $domains;

function domain_details($v_user,$domain)
   global $domain_all;
   if (isset($domain_all[$domain])) return $domain_all[$domain];
    exec(VESTA_CMD."v-list-dns-records ".$v_user." ".$domain." 'json'", $output, $return_var);
   $data = json_decode(implode('', $output), true);
   if (count($data)<1) return array();   
   foreach($data as $id=>$sub) if ($sub['TYPE']=='A') $domain_all[$domain][$domain.'/'.$id]=$sub['RECORD'].".".$domain.'/'.$sub['VALUE'];
   return $domain_all[$domain];

function list_domain($v_user,$domain='all')
   if ($domain=='all') 
      if (count($domains)<1) return false;
      foreach($domains as $dummy=>$dom)
   } else
      if (!isset($domains[$domain])) return false;
   return $result;   
function dnsid($v_user,$domain,$dns)
   if (count($list)<1) return 0;
   foreach ($list as $id=>$t)
      if ($dns==$t) return $id;
   return 0;   

function dnstxtid($v_user,$domain)
   exec(VESTA_CMD ."v-list-dns-records ".$v_user." ".$domain,  $output, $auth_code);
   if (count($output)==1) return 0;
   foreach($output as $sub)
      $sub=explode(' ',clear_spaces($sub));   
      if ($sub[2]=='TXT') return $sub[0];
   return 0;

function getidvalue($v_user,$domain,$id)
    exec (VESTA_CMD."v-list-dns-records ".$v_user." ".$domain." 'json'", $output, $return_var);
   $data = json_decode(implode('', $output), true);
   if ($id=='all')
      if (count($data)<1) return ''; else return $data;
      if (!isset($data[$id])) return ''; else return $data[$id];

function extract_txt($pattern,$t)
   if (!preg_match_all('/'.$pattern.'/',$t, $out, PREG_PATTERN_ORDER)) return '';
   return $out[1][0];

function update_txt($v_user,$domain,$ip,$named_restart=true)
   if ($named_restart) $reset=''; else $reset='no';
   if ($id==0)  return false;
   if ($old=='') return false;
   if ($old_ip=='') return false;
   if ($old_ip==$ip) return false;
   exec(VESTA_CMD ."v-change-dns-record '".$v_user."' '".$domain."' '".$id."' '".$old."' '".$reset."'",  $output, $auth_code);
   return true;

function update_domain($v_user,$domain,$v_ip_addr)
   global $named_restart;
   if (!isset($list[$domain])) die('NOT_EXISTS');
   foreach($list as $id=>$ip)
      if ($v_ip_addr!=$ip)
         //updating all A entries
         exec(VESTA_CMD ."v-change-dns-record '".$v_user."' '".$domain."' '".$id."' '".$v_ip_addr."' 'no'",  $output, $auth_code);
   if (update_txt($v_user,$domain,$v_ip_addr,false)) $change=true;
   if ($d_ip!=$v_ip_addr)
      //updating main domain entry
      exec(VESTA_CMD ."v-change-dns-domain-ip '".$v_user."' '".$domain."' '".$v_ip_addr."' 'no'",  $output, $auth_code);
   if ($change)
      //if there were any changes made by now, we will queue a name server restart for refreshing and TTL reducing down  to 5 minutes - 300s
      exec(VESTA_CMD ."v-change-dns-domain-ttl '".$v_user."' '".$domain."' '300' 'no'",  $output, $auth_code);
      die('OK'); //let the client know that we have updated and saved the changes on our name server
      else die('NOK'); //let the client know that all records are up to date and there are no changes needed

function update_subdomain($v_user,$domain,$val,$named_restart=false)
   if ($named_restart) $reset=''; else $reset='no';
   foreach($domain_part as $id=>$part)
      if (isset($list[$domain]))
   if ($id==0) die('NOT_EXISTS');
   if ($old!=$val)
      exec(VESTA_CMD ."v-change-dns-record '".$v_user."' '".$domain."' '".$id."' '".$val."' '".$reset."'",  $output, $auth_code);
      exec(VESTA_CMD ."v-change-dns-domain-ttl '".$v_user."' '".$domain."' '300' 'no'",  $output, $auth_code);
      if ($sum=='mail') update_txt($v_user,$domain,$val,$named_restart);
      if ($named_restart) die ('OK');   
   } else
      if ($named_restart) die('NOK');

$v_ip_addr = $_SERVER["REMOTE_ADDR"];
if (!isset($_GET['do'])) die('DO_REQUIRED');
if ($_GET['do']=='ip') die($_SERVER["REMOTE_ADDR"]);

exec(VESTA_CMD ."v-check-user-password ".$v_user." ".$v_password." '".$v_ip_addr."'",  $output, $auth_code);

if ($auth_code != 0 ) {
        echo 'AUTH';

if ($_GET['do']==false) die('AUTH');
// used to be OK (as authorized ok, but decided to make it as athorizing error - dont want to create any backdors)
// when OK message in here,       it is possible to make bruteforce attach if login is compromised   

list_domain($v_user,'all'); // just loading all into variable that is kept in ram
if ($_GET['do']=='list_domains') $list=list_domains($v_user); // using variable loaded before
if ($_GET['do']=='list_subdomains') $list=list_domain($v_user,'all'); // using variable loaded before
if ($_GET['do']=='update')
   if (!isset($_GET['domain'])) die('DOMAIN_REQUIRED');
   $_GET['domain']=str_replace(' ','',$_GET['domain']);
   if (isset($_GET['ip'])) $v_ip_addr = str_replace("'",'',escapeshellarg($_GET['ip']));
   if (isset($list[$_GET['domain']])) update_domain($v_user,$_GET['domain'],$v_ip_addr);
   else update_subdomain($v_user,$_GET['domain'],$v_ip_addr,true);
if (count($list)<1) die('DO_UNKNOWN');
foreach ($list as $dom) echo $dom."\n";



This script was created only for my private needs, I'm not responsible for any incorrect script usage, please be aware about changes that will take place when you use this script.

DynDNS client script written in python 2.7
Python script wrapper arround wget thanks to which our ip will be updated on VestaCP nameserver.

- multiple instances available - one for each domain (please do not try to update main domain, and subdomains separate as they will be overwritten)
- lock file usage - cannot have more than one client / domain active on same server
- selectable update interval
- for minimum VestaCP server usage IP addres is updated only when change is detected
- logging into /var/log/dyndns.log

Currently only linux support, wget installed is a must.

example usage:

Code: Select all

python dns_client.py vestaserver.com admin password subdomain.domainonvestaserver.com

example usage with /etc/rc.local:

Code: Select all

su - root -c "python /root/dns_client.py vestaserver.com admin password subdomain.domainonvestaserver.com" >/dev/null 2>&1 &

Code: Select all


# DynDNS client for VestaCP dyndns "plugin" at https://forum.vestacp.com/viewtopic.php?f=19&t=12599
# Please use freely,

# usage: python file.py %server% %user% %password% %domain% [%refresh_time% - default 300s]
# example: python dns_client.py vestaserver.com admin password subdomain.domainonvestaserver.com 1000
# where:
# vestaserver.com - our VestaCP server address
# admin - user
# password - password
# subdomain.domainonvestaserver.com - (sub)domain which we want to update, must be already delegated to ns1.vestaserver.com
# 1000 - interval time (in seconds) how often our IP change will be checked and updated if needed, optional parameter - default: 300s [5 minutes]

# by Dariusz - admin@box24.info

import os
import time
import commands
import re
import sys

#set to '' if no log is required

#please set VestaCP admin port, default: 8083

#How often in seconds script will query an ip and update our domain, default: 300

#when set to true, every update interval "xxx.xxx.xxx.xxx  - no change detected." will be saved in log

def run_cmd(command):
   return commands.getoutput(command)

def write_file(f, txt):
   if os.path.isfile(f):
      f = open(f, 'a')
      f = open(f, 'w')

def log(txt):
   txt='['+sys.argv[4]+'] '+txt
   if log_file!='':
      s='['+time.strftime("%d %b %Y %H:%M:%S", time.gmtime())+']'
      txt=s+' '+txt
      write_file(log_file, txt+"\n")
   print txt

def update_lock_file():
   f = open('/run/lock/'+sys.argv[4], 'w')

if len(sys.argv)<5:
   error='usage: $ python '+sys.argv[0]+' %server% %user% %password% %domain% [%refresh_time% - default 300s]'

if len(sys.argv)==6:
   if int(sys.argv[5])>0:

if os.path.isfile('/run/lock/'+sys.argv[4]):
   if (time.time()-os.path.getctime('/run/lock/'+sys.argv[4]))<6:
      error='Domain "'+sys.argv[4]+'" is already being updated!!!'
#internally used variables - do not modifie
if error=='':
   log('Update interval is set to '+str(update)+'s')


while error=='':

   if last!=now:

   if ((now-last_update)>update):
      response = run_cmd('wget -qO- -T 1 -t 1 --no-check-certificate '+ip_query)
      if re.search(ur'([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})', response ):
         if connected==False:
            log('Connected to Host "'+sys.argv[1]+'" at port '+str(default_port))
         if response!=current_ip:
            if current_ip=='':
               log('Ip detected: '+response)
               log('Ip change detected, New: '+response+', Old: '+current_ip)
            if current_ip!=last_ip:
               response = run_cmd('wget -qO- -T 5 -t 1 --no-check-certificate '+domain_update)
               if response=='NOK':
                  log(current_ip+' - ip is already assign with this domain - nothing to update.')
               if response=='OK':
                  log(current_ip+' - updated.')
               if response=='AUTH':
                  error='Authorization error - please verify your user / password.'
               if response=='NOT_EXISTS':
                  error='Domain was not found on server under following username '+sys.argv[2]
            if log_ip_no_change_detected==True:
               log(current_ip+' - no change detected.')
         if connected==True:
         log('['+str(err_count)+']Host "'+sys.argv[1]+'" unreachable at port '+str(default_port)+'... Next retry in '+str(update)+'s')
log('Client terminated.')

EXTRA - changing VestaCP into a public available DNS server
This will allow changes to act much faster than usual DNS propagation delays.
Simply point your server as a primary nameserver where ever you need to have changes to happend in instant:
(usefull with IOT devices - where i use this setting - it also allows you to use your own type of domains, but only when your VestaCP is se as a primary DNS server on a client computer/device)

Paste following content into /etc/bind/named.conf.options and restart service/server:

Code: Select all

options {
   directory "/var/cache/bind";

   // If there is a firewall between you and nameservers you want
   // to talk to, you may need to fix the firewall to allow multiple
   // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

   // If your ISP provided one or more IP addresses for stable
   // nameservers, you probably want to use them as forwarders. 
   // Uncomment the following block, and insert the addresses replacing
   // the all-0's placeholder.

    forwarders {; //google main; //google secondary; //Level3 main; //Level3 secondary; //Verisign main; //Verisign secondary; //dns watch main; //dns watch secondary; // comodo secure dns main; // comodo secure dns secondary; //open dns home main; //open dns home secondary; //DNS advantage main; //DNS advantage secondary; // Norton connect safe main; // Norton connect safe secondary; // Green Team main; // Green Team secondary; // Safe dns main; // Safe dns secondary; //OpenNIC main; // OpenNIC secondary; // SmartViper main; //SmartViper secondary; //dyn main; //dyn secondary; //FreeDNS main; //FreeDNS secondary; //Alternate DNS main; //Alternate DNS secondary; //Yandex main; //Yandex secondary; // censufridns main; //censufridns secondary; // Huricane electric; // PuntCat

   // If BIND logs error messages about the root key being expired,
   // you will need to update your keys.  See https://www.isc.org/bind-keys
   dnssec-validation no;
   recursion yes;
    allow-recursion { any; };
   //allow-query { any; };
   auth-nxdomain no;    # conform to RFC1035
   //listen-v6 { any; };

Sorry for any potential English mistakes.
I'm native Polish.

Have fun.


Added DynDNS python script for linux client

Posts: 12
Joined: Mon Jan 25, 2016 2:25 pm

Re: Dyndns api support for VestaCP [PHP] + client script [Python]

Postby ThA-LaN-LaW » Fri Sep 16, 2016 11:01 am

Thanks Darek! Nice work!

i have to improvement suggestion for rainy sundays:
- fail2ban extension (block to many failure logins)
- Log File on Server (who updated, when, what)

Best Regards!

VestaCP Team
Posts: 7479
Joined: Fri Dec 26, 2014 2:23 pm
Location: Moscow

Re: Dyndns api support for VestaCP [PHP] + client script [Python]

Postby skurudo » Wed Sep 28, 2016 2:09 pm

Topic it "sticky" now.
Thanks a lot for this solution.
-> DigitalOcean competition - please, support us
-> fix for phpmyadmin - nice and sweet now

Return to “3rd Party Software”

Who is online

Users browsing this forum: No registered users and 1 guest