package helpers;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.typesafe.config.ConfigFactory;
import models.DnsEntry;
import models.Domain;
import models.ResourceRecord;
import play.Logger;
import play.libs.F.Function;
import play.libs.F.Promise;
import play.libs.ws.WS;
import play.libs.ws.WSResponse;
public class DnsUpdateHelper {
private final static String AUTODNS_HOST = ConfigFactory.load().getString("autodns.host");
private final static String AUTODNS_USERNAME = ConfigFactory.load().getString("autodns.user");
private final static String AUTODNS_PASSWORD = ConfigFactory.load().getString("autodns.pass");
private final static String AUTODNS_CONTEXT = ConfigFactory.load().getString("autodns.context");
private final static String AUTODNS_NS_1 = ConfigFactory.load().getString("autodns.ns1");
private final static String AUTODNS_NS_2 = ConfigFactory.load().getString("autodns.ns2");
private final static String AUTODNS_NS_3 = ConfigFactory.load().getString("autodns.ns3");
private final static String AUTODNS_NS_4 = ConfigFactory.load().getString("autodns.ns4");
private Domain domain;
private final static int SUBDOMAIN_TTL = parseInteger("autodns.subdomain.ttl", 60);
private final static int DOMAIN_TTL = parseInteger("autodns.domain.ttl", 3600);
private final static int NS_TTL = parseInteger("autodns.nameserver.ttl", 86400);
public DnsUpdateHelper(Domain domain) {
this.domain = domain;
}
public void update() {
String message = getHeader() + buildUpdateList() + getFooter();
Logger.debug("@"+System.currentTimeMillis()+" sending Update for "+domain.name+":\n" + message + "\n");
performUpdate(message);
}
// TODO move string to template
private String getHeader() {
return "" + "" + ""
+ AUTODNS_USERNAME
+ ""
+ ""
+ AUTODNS_PASSWORD
+ ""
+ ""
+ AUTODNS_CONTEXT
+ ""
+ ""
+ ""
+ "0202
"
+ ""
+ ""
+ AUTODNS_NS_1
+ ""
+ ""
+ domain.name
+ ""
+ "complete"
+ ""
+ ""
+ domain.ip
+ ""
+ ""+DOMAIN_TTL+""
+ ""
+ "1"
+ ""
+ "86400"
+ "39940"
+ "14400"
+ "604800"
+ ""
+ domain.soaEmail
+ ""
+ ""
+ ""
+ ""
+ AUTODNS_NS_1
+ ""
+ ""+NS_TTL+""
+ ""
+ ""
+ ""
+ AUTODNS_NS_2
+ ""
+ ""+NS_TTL+""
+ ""
+ ""
+ ""
+ AUTODNS_NS_3
+ ""
+ ""+NS_TTL+""
+ ""
+ ""
+ ""
+ AUTODNS_NS_4
+ ""
+ ""+NS_TTL+""
+ ""
+ ""
+ "";
}
// TODO move string to template
private String getFooter() {
return ""
+ "default"
+ ""
+ ""
+ "";
}
/**
* We need to update all Entries all Time. API is an all or nothing approach.
*/
private void updateEntries() {
for (DnsEntry entry : domain.findNeedsToChanged()) {
entry.actualIp = entry.updatedIp;
entry.actualIp6 = entry.updatedIp6;
if(entry.needsUpdate6()) {
entry.updated6 = new Date();
} else {
entry.updated = new Date();
}
Logger.info("@"+System.currentTimeMillis()+" did update for " + entry);
entry.save();
}
domain.forceUpdate = false;
domain.save();
}
private void performUpdate(String content) {
Promise xmlPromise = WS.url(AUTODNS_HOST)
.setHeader("Content-Type", "text/xml; charset=utf-8")
.post(content).map(new Function() {
public Document apply(WSResponse response) {
Logger.debug("response: "+response.getBody());
Document doc = response.asXml();
if (getUpdateStatus(doc)) {
Logger.info("@"+System.currentTimeMillis()+" updating "+domain.name+" success!");
updateEntries();
} else {
Logger.error("@"+System.currentTimeMillis()+" updating "+domain.name+" error:\n", response.getBody());
}
return doc;
}
});
}
private static String getCName(String fullName, String domainName){
return fullName.replace("."+domainName, "").trim();
}
private Map getUpdates(Domain domain) {
Map updates = new HashMap<>();
for(DnsEntry dnsEntry : domain.findNeedsToChanged()) {
ResourceRecord rr = ResourceRecord.getOrCreateFromDNSEntry(dnsEntry);
updates.put(rr.id, dnsEntry);
}
return updates;
}
private String buildUpdateList() {
StringBuilder sb = new StringBuilder();
Map updates = getUpdates(domain);
for(ResourceRecord rr : domain.getResourceRecords()) {
if(updates.containsKey(rr.id)) {
DnsEntry entry = updates.get(rr.id);
if(!entry.toDelete) {
if(entry.updatedIp6 != null) {
sb.append("")
.append("")
.append(entry.getSubdomainPart())
.append("")
.append(""+SUBDOMAIN_TTL+"")
.append("AAAA")
.append("")
.append(entry.updatedIp6)
.append("")
.append("");
rr.value = entry.updatedIp6;
}
if(entry.updatedIp != null) {
sb.append("")
.append("")
.append(getCName(entry.name+"."+entry.subDomain.name, entry.domain.name))
.append("")
.append(""+SUBDOMAIN_TTL+"")
.append("A")
.append("")
.append(entry.updatedIp)
.append("")
.append("");
rr.value = entry.updatedIp;
}
rr.save();
} else {
if(rr != null) {
rr.delete();
Logger.info("@"+System.currentTimeMillis()+" deleted "+rr);
}
entry.delete();
Logger.info("@"+System.currentTimeMillis()+" deleted "+entry);
}
} else if(rr.value != null) {
sb.append("")
.append("")
.append(rr.name)
.append("")
.append(""+SUBDOMAIN_TTL+"")
.append(""+rr.type+"")
.append("")
.append(rr.value)
.append("")
.append("");
}
}
return sb.toString();
}
private boolean getUpdateStatus(Document doc) {
doc.getDocumentElement().normalize();
NodeList statusConfigList = doc.getElementsByTagName("status");
Node status = statusConfigList.getLength() > 0 ? statusConfigList.item(0) : null;
if(status == null) {
return false;
}
NodeList layerConfigList = status.getChildNodes();
for(int i = 0; i < layerConfigList.getLength(); i++) {
Node n = layerConfigList.item(i);
if( n.getTextContent().toLowerCase().equals("success") ) {
return true;
}
}
return false;
}
private static int parseInteger(String key, int fallback){
if(key != null && ConfigFactory.load().hasPath(key)){
try {
return Integer.parseInt(ConfigFactory.load().getString(key));
} catch(NumberFormatException ex){
Logger.warn("cannot parse "+ex.getLocalizedMessage());
}
}
return fallback;
}
}