We are happy to announce that Vesta is back under active development as of 25 February 2024. We are working on Vesta 2.0 and expect to release it by the end of 2024. Read more about it: https://vestacp.com/docs/vesta-2-development
Dyndns api support for VestaCP [PHP] + client script [Python]
Dyndns api support for VestaCP [PHP] + client script [Python]
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,
First:
Detect IP address change on a dyndns client server and update when needed
Second:
Update your IP address every few minutes, only new ip address if detected will couse changes.
example use of the script in linux bash:
Full commands list is as follow:
Will simply give us an IP address from we are connecting
will answer us with a list of domains which are set under DNS section in VestaCP panel by our %usr%
An example:
Will give us full list of domains, and all A records which are set under DNS in VestaCP panel by our %usr%
An example:
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
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=...
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.
Sorry for any potential English mistakes.
I'm native Polish.
Have fun.
Cheers
Darek
EDIT:
Added DynDNS python script for linux client
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,
First:
Detect IP address change on a dyndns client server and update when needed
Second:
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 serverShow
Code: Select all
wget --no-check-certificate https://example.com:8083/api/dyndns/?do=ip
Case where domain certificate is valid and installed on serverShow
Code: Select all
wget https://example.com:8083/api/dyndns/?do=ip
Client IP address checkShow
Code: Select all
https://example.com:8083/api/dyndns/?do=ip
Code: Select all
1.1.1.1
All main domains listingShow
Code: Select all
https://example.com:8083/api/dyndns/?user=%usr%&pass=%pass%&do=list_domains
An example:
Code: Select all
example.com/1.1.1.1
example.org/1.1.1.1
other.com/1.2.3.4
All main domains & subdomainsShow
Code: Select all
https://example.com:8083/api/dyndns/?user=%usr%&pass=%pass%&do=list_subdomains
An example:
Code: Select all
example.com/1.1.1.1
@.example.com/1.1.1.1
mail.example.com/1.1.1.1
http://www.example.com/1.1.1.1
pop.example.com/1.1.1.1
ftp.example.com/1.1.1.1
example.org/1.1.1.1
@.example.org/1.1.1.1
mail.example.org/1.1.1.1
http://www.example.org/1.1.1.1
pop.example.org/1.1.1.1
ftp.example.org/1.1.1.1
other.com/1.2.3.4
@.other.com/1.2.3.4
mail.other.com/1.2.3.4
http://www.other.com/1.2.3.4
pop.other.com/1.2.3.4
ftp.other.com/1.2.3.4
Update a domain or subdomain with IP from which we are connectingShow
Code: Select all
https://example.com:8083/api/dyndns/?user=%usr%&pass=%pass%&do=update&domain=ftp.example.org
Update a domain or subdomain with selected IPShow
Code: Select all
https://example.com:8083/api/dyndns/?user=%usr%&pass=%pass%&do=update&domain=ftp.example.org&ip=3.3.3.3
Possible all answers received from scriptShow
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 installShow
- 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:
- 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
<?php
define('VESTA_CMD', '/usr/bin/sudo /usr/local/vesta/bin/');
$domains=array();
$domain_all=array();
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();
$domains=array();
unset($output[0]);
unset($output[1]);
foreach($output as $dom)
{
$dom=explode(' ',clear_spaces($dom));
$domains[$dom[0]]=$dom[0].'/'.$dom[1];
}
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);
$domain_all[$domain]=array();
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')
{
$result=array();
$domains=list_domains($v_user);
if ($domain=='all')
{
if (count($domains)<1) return false;
foreach($domains as $dummy=>$dom)
{
$dom=explode('/',$dom);
$result=array_merge($result,array($dom[0]=>$dom[0].'/'.$dom[1]),domain_details($v_user,$dom[0]));
}
} else
{
if (!isset($domains[$domain])) return false;
$dom=$dom=explode('/',$domains[$domain]);
$result=array_merge(array($dom[0]=>$dom[0].'/'.$dom[1]),domain_details($v_user,$domain));
}
return $result;
}
function dnsid($v_user,$domain,$dns)
{
$list=list_domain($v_user,$domain);
if (count($list)<1) return 0;
foreach ($list as $id=>$t)
{
list(,$id)=explode('/',$id);
$id=(int)$id;
list($t,)=explode('/',$t);
$t=trim(str_replace('.'.$domain,'',$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;
unset($output[0]);
unset($output[1]);
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;
}
else
{
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';
$id=dnstxtid($v_user,$domain);
if ($id==0) return false;
$old=getidvalue($v_user,$domain,$id);
if ($old=='') return false;
$old=$old['VALUE'];
$old_ip=extract_txt('([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})',$old);
if ($old_ip=='') return false;
if ($old_ip==$ip) return false;
$old=str_replace($old_ip,$ip,$old);
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;
$named_restart=false;
$change=false;
$list=list_domain($v_user,$domain);
if (!isset($list[$domain])) die('NOT_EXISTS');
list(,$d_ip)=explode('/',$list[$domain]);
unset($list[$domain]);
foreach($list as $id=>$ip)
{
list(,$id)=explode('/',$id);
list(,$ip)=explode('/',$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);
$change=true;
}
}
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);
$change=true;
}
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';
$list=list_domain($v_user);
$domain_part=explode('.',$domain);
$sub=array();
foreach($domain_part as $id=>$part)
{
$sub[]=$domain_part[$id];
unset($domain_part[$id]);
$domain=implode('.',$domain_part);
if (isset($list[$domain]))
{
$sub=@implode('.',$sub);
break;
}
}
$id=dnsid($v_user,$domain,$sub);
if ($id==0) die('NOT_EXISTS');
$old=$list[$domain.'/'.$id];
list(,$old)=explode('/',$old);
$old=trim($old);
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"];
$v_user=escapeshellarg($_GET['user']);
$v_password=escapeshellarg($_GET['pass']);
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';
exit;
}
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']);
$list=list_domain($v_user);
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);
die('NOT_EXISTS');
}
if (count($list)<1) die('DO_UNKNOWN');
foreach ($list as $dom) echo $dom."\n";
exit;
?>
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.7Show
Python script wrapper arround wget thanks to which our ip will be updated on VestaCP nameserver.
Functionality:
- 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:
example usage with /etc/rc.local:
Functionality:
- 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
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
#!/usr/bin/python
# 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 - [email protected]
import os
import time
import commands
import re
import sys
#set to '' if no log is required
log_file='/var/log/dyndns.log'
#please set VestaCP admin port, default: 8083
default_port=8083
#How often in seconds script will query an ip and update our domain, default: 300
update=300
#when set to true, every update interval "xxx.xxx.xxx.xxx - no change detected." will be saved in log
log_ip_no_change_detected=False
def run_cmd(command):
return commands.getoutput(command)
def write_file(f, txt):
if os.path.isfile(f):
f = open(f, 'a')
else:
f = open(f, 'w')
f.write(txt)
f.close()
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')
f.write('1')
f.close()
if len(sys.argv)<5:
log_file=''
error='usage: $ python '+sys.argv[0]+' %server% %user% %password% %domain% [%refresh_time% - default 300s]'
else:
error=''
if len(sys.argv)==6:
if int(sys.argv[5])>0:
update=int(sys.argv[5])
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=='':
domain_update='"https://'+sys.argv[1]+':'+str(default_port)+'/api/dyndns/?user='+sys.argv[2]+'&pass='+sys.argv[3]+'&do=update&domain='+sys.argv[4]+'"'
ip_query='"https://'+sys.argv[1]+':'+str(default_port)+'/api/dyndns/?do=ip"'
log('Update interval is set to '+str(update)+'s')
last_ip='0.0.0.0'
current_ip='0.0.0.0'
last_update=0
last=0
err_count=0
connected=False
while error=='':
now=int(time.time());
if last!=now:
last=now
update_lock_file()
if ((now-last_update)>update):
last_update=now
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:
connected=True
log('Connected to Host "'+sys.argv[1]+'" at port '+str(default_port))
if response!=current_ip:
if current_ip=='0.0.0.0':
log('Ip detected: '+response)
else:
log('Ip change detected, New: '+response+', Old: '+current_ip)
current_ip=response
if current_ip!=last_ip:
last_ip=current_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]
else:
if log_ip_no_change_detected==True:
log(current_ip+' - no change detected.')
else:
if connected==True:
connected=False
err_count+=1
log('['+str(err_count)+']Host "'+sys.argv[1]+'" unreachable at port '+str(default_port)+'... Next retry in '+str(update)+'s')
time.sleep(0.5)
log(error)
log('Client terminated.')
EXTRA - changing VestaCP into a public available DNS serverShow
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:
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 {
8.8.8.8; //google main
8.8.4.4; //google secondary
209.244.0.3; //Level3 main
209.244.0.4; //Level3 secondary
64.6.64.6; //Verisign main
64.6.65.6; //Verisign secondary
84.200.69.80; //dns watch main
84.200.70.40; //dns watch secondary
8.26.56.26; // comodo secure dns main
8.20.247.20; // comodo secure dns secondary
208.67.222.222; //open dns home main
208.67.220.220; //open dns home secondary
156.154.70.1; //DNS advantage main
156.154.71.1; //DNS advantage secondary
199.85.126.10; // Norton connect safe main
199.85.127.10; // Norton connect safe secondary
81.218.119.11; // Green Team main
209.88.198.133; // Green Team secondary
195.46.39.39; // Safe dns main
195.46.39.40; // Safe dns secondary
162.211.64.20; //OpenNIC main
199.195.249.174; // OpenNIC secondary
208.76.50.50; // SmartViper main
208.76.51.51; //SmartViper secondary
216.146.35.35; //dyn main
216.146.36.36; //dyn secondary
37.235.1.174; //FreeDNS main
37.235.1.177; //FreeDNS secondary
198.101.242.72; //Alternate DNS main
23.253.163.53; //Alternate DNS secondary
77.88.8.8; //Yandex main
77.88.8.1; //Yandex secondary
91.239.100.100; // censufridns main
89.233.43.71; //censufridns secondary
74.82.42.42; // Huricane electric
109.69.8.51; // 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.
Cheers
Darek
EDIT:
Added DynDNS python script for linux client
-
- Posts: 14
- Joined: Mon Jan 25, 2016 2:25 pm
Re: Dyndns api support for VestaCP [PHP] + client script [Python]
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!
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!
Re: Dyndns api support for VestaCP [PHP] + client script [Python]
Topic it "sticky" now.
Thanks a lot for this solution.
Thanks a lot for this solution.