You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PdnsApiAuthenticator.py 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import json
  2. import logging
  3. import time
  4. from certbot import errors
  5. from certbot_dns.pdnsapi import PdnsApi
  6. logger = logging.getLogger(__name__)
  7. class PdnsApiAuthenticator:
  8. api = None
  9. zones = None
  10. axfr_time = None
  11. def find_best_matching_zone(self, domain):
  12. if domain is None or domain == "":
  13. return None
  14. for zone in self.zones:
  15. if zone['name'] == domain + ".":
  16. return zone
  17. return self.find_best_matching_zone(domain[domain.index(".") + 1:]) if "." in domain else None
  18. def find_soa(self, zone):
  19. for rrset in zone["rrsets"]:
  20. if rrset["type"] == "SOA":
  21. return rrset, rrset["records"][0]
  22. return None
  23. def flush_zone(self, zone_name):
  24. res = self.api.flush_zone_cache(zone_name)
  25. if res is None or "result" not in res or res["result"] != "Flushed cache.":
  26. raise errors.PluginError("Bad return from PDNS API when flushing cache: %s" % res)
  27. def notify_zone(self, zone_name):
  28. res = self.api.notify_zone(zone_name)
  29. if res is None or "result" not in res or res["result"] != "Notification queued":
  30. raise errors.PluginError("Bad return from PDNS API when notifying: %s" % res)
  31. def update_soa(self, zone_name):
  32. zone = self.api.get_zone(zone_name)
  33. if zone is None or "error" in zone:
  34. raise errors.PluginError("Bad return from PDNS API when getting zone %s: %s" % (zone_name, zone))
  35. rrset, soa = self.find_soa(zone)
  36. split = soa["content"].split(" ")
  37. split[2] = str(int(split[2]) + 1)
  38. soa["content"] = ' '.join(split)
  39. res = self.api.replace_record(zone_name, zone_name, rrset["type"], rrset["ttl"], soa["content"],
  40. soa["disabled"], False)
  41. if res is not None:
  42. raise errors.PluginError("Bad return from PDNS API when updating SOA: %s" % res)
  43. def prepare(self, conf_path):
  44. self.api = PdnsApi()
  45. with open(conf_path) as f:
  46. config = json.load(f)
  47. self.api.set_api_key(config["api-key"])
  48. self.api.set_base_url(config["base-url"])
  49. self.axfr_time = config["axfr-time"]
  50. self.zones = self.api.list_zones()
  51. # print(self.zones)
  52. # raw_input('Press <ENTER> to continue')
  53. if self.zones is None or "error" in self.zones:
  54. raise errors.PluginError("Could not list zones %s" % self.zones)
  55. def perform_single(self, achall, response, validation):
  56. domain = achall.domain
  57. token = validation.encode()
  58. zone = self.find_best_matching_zone(domain)
  59. if zone is None:
  60. raise errors.PluginError("Could not find zone for %s" % domain)
  61. logger.debug("Found zone %s for domain %s" % (zone["name"], domain))
  62. res = self.api.replace_record(zone["name"], "_acme-challenge." + domain + ".", "TXT", 1, "\"" + token + "\"", False, False)
  63. if res is not None:
  64. raise errors.PluginError("Bad return from PDNS API when adding record: %s" % res)
  65. self.update_soa(zone["name"])
  66. self.flush_zone(zone["name"])
  67. self.notify_zone(zone["name"])
  68. # raw_input('Press <ENTER> to continue')
  69. logger.info("Waiting %i seconds..." % self.axfr_time)
  70. time.sleep(self.axfr_time)
  71. return response
  72. def cleanup(self, achall):
  73. domain = achall.domain
  74. zone = self.find_best_matching_zone(domain)
  75. if zone is None:
  76. return
  77. res = self.api.delete_record(zone["name"], "_acme-challenge." + domain + ".", "TXT", 1, None, False, False)
  78. if res is not None:
  79. raise errors.PluginError("Bad return from PDNS API when deleting record: %s" % res)
  80. self.update_soa(zone["name"])
  81. self.flush_zone(zone["name"])
  82. self.notify_zone(zone["name"])