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; } }