mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge pull request #944 from YunoHost/enh-check-whois
[enh] Check domain expiration date
This commit is contained in:
commit
0371c84a1c
4 changed files with 117 additions and 15 deletions
|
@ -1,13 +1,21 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from moulinette.utils.filesystem import read_file
|
||||
from datetime import datetime, timedelta
|
||||
from publicsuffix import PublicSuffixList
|
||||
|
||||
from moulinette.utils.process import check_output
|
||||
|
||||
from yunohost.utils.network import dig
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain
|
||||
|
||||
# We put here domains we know has dyndns provider, but that are not yet
|
||||
# registered in the public suffix list
|
||||
PENDING_SUFFIX_LIST = ['ynh.fr', 'netlib.re']
|
||||
|
||||
|
||||
class DNSRecordsDiagnoser(Diagnoser):
|
||||
|
||||
|
@ -17,12 +25,6 @@ class DNSRecordsDiagnoser(Diagnoser):
|
|||
|
||||
def run(self):
|
||||
|
||||
resolvers = read_file("/etc/resolv.dnsmasq.conf").split("\n")
|
||||
ipv4_resolvers = [r.split(" ")[1] for r in resolvers if r.startswith("nameserver") and ":" not in r]
|
||||
# FIXME some day ... handle ipv4-only and ipv6-only servers. For now we assume we have at least ipv4
|
||||
assert ipv4_resolvers != [], "Uhoh, need at least one IPv4 DNS resolver ..."
|
||||
|
||||
self.resolver = ipv4_resolvers[0]
|
||||
main_domain = _get_maindomain()
|
||||
|
||||
all_domains = domain_list()["domains"]
|
||||
|
@ -32,9 +34,13 @@ class DNSRecordsDiagnoser(Diagnoser):
|
|||
for report in self.check_domain(domain, domain == main_domain, is_subdomain=is_subdomain):
|
||||
yield report
|
||||
|
||||
# FIXME : somewhere, should implement a check for reverse DNS ...
|
||||
|
||||
# FIXME / TODO : somewhere, could also implement a check for domain expiring soon
|
||||
# Check if a domain buy by the user will expire soon
|
||||
psl = PublicSuffixList()
|
||||
domains_from_registrar = [psl.get_public_suffix(domain) for domain in all_domains]
|
||||
domains_from_registrar = [domain for domain in domains_from_registrar if "." in domain]
|
||||
domains_from_registrar = set(domains_from_registrar) - set(PENDING_SUFFIX_LIST)
|
||||
for report in self.check_expiration_date(domains_from_registrar):
|
||||
yield report
|
||||
|
||||
def check_domain(self, domain, is_main_domain, is_subdomain):
|
||||
|
||||
|
@ -67,7 +73,6 @@ class DNSRecordsDiagnoser(Diagnoser):
|
|||
results[id_] = "WRONG"
|
||||
discrepancies.append(("diagnosis_dns_discrepancy", r))
|
||||
|
||||
|
||||
def its_important():
|
||||
# Every mail DNS records are important for main domain
|
||||
# For other domain, we only report it as a warning for now...
|
||||
|
@ -136,6 +141,92 @@ class DNSRecordsDiagnoser(Diagnoser):
|
|||
else:
|
||||
return r["current"] == r["value"]
|
||||
|
||||
def check_expiration_date(self, domains):
|
||||
"""
|
||||
Alert if expiration date of a domain is soon
|
||||
"""
|
||||
|
||||
details = {
|
||||
"not_found": [],
|
||||
"error": [],
|
||||
"warning": [],
|
||||
"success": []
|
||||
}
|
||||
|
||||
for domain in domains:
|
||||
expire_date = self.get_domain_expiration(domain)
|
||||
|
||||
if isinstance(expire_date, str):
|
||||
status_ns, _ = dig(domain, "NS", resolvers="force_external")
|
||||
status_a, _ = dig(domain, "A", resolvers="force_external")
|
||||
if "ok" not in [status_ns, status_a]:
|
||||
details["not_found"].append((
|
||||
"diagnosis_domain_%s_details" % (expire_date),
|
||||
{"domain": domain}))
|
||||
else:
|
||||
self.logger_debug("Dyndns domain: %s" % (domain))
|
||||
continue
|
||||
|
||||
expire_in = expire_date - datetime.now()
|
||||
|
||||
alert_type = "success"
|
||||
if expire_in <= timedelta(15):
|
||||
alert_type = "error"
|
||||
elif expire_in <= timedelta(45):
|
||||
alert_type = "warning"
|
||||
|
||||
args = {
|
||||
"domain": domain,
|
||||
"days": expire_in.days - 1,
|
||||
"expire_date": str(expire_date)
|
||||
}
|
||||
details[alert_type].append(("diagnosis_domain_expires_in", args))
|
||||
|
||||
for alert_type in ["success", "error", "warning", "not_found"]:
|
||||
if details[alert_type]:
|
||||
if alert_type == "not_found":
|
||||
meta = {"test": "domain_not_found"}
|
||||
else:
|
||||
meta = {"test": "domain_expiration"}
|
||||
# Allow to ignore specifically a single domain
|
||||
if len(details[alert_type]) == 1:
|
||||
meta["domain"] = details[alert_type][0][1]["domain"]
|
||||
yield dict(meta=meta,
|
||||
data={},
|
||||
status=alert_type.upper() if alert_type != "not_found" else "WARNING",
|
||||
summary="diagnosis_domain_expiration_" + alert_type,
|
||||
details=details[alert_type])
|
||||
|
||||
def get_domain_expiration(self, domain):
|
||||
"""
|
||||
Return the expiration datetime of a domain or None
|
||||
"""
|
||||
command = "whois -H %s || echo failed" % (domain)
|
||||
out = check_output(command).strip().split("\n")
|
||||
|
||||
# Reduce output to determine if whois answer is equivalent to NOT FOUND
|
||||
filtered_out = [line for line in out
|
||||
if re.search(r'^[a-zA-Z0-9 ]{4,25}:', line, re.IGNORECASE) and
|
||||
not re.match(r'>>> Last update of whois', line, re.IGNORECASE) and
|
||||
not re.match(r'^NOTICE:', line, re.IGNORECASE) and
|
||||
not re.match(r'^%%', line, re.IGNORECASE) and
|
||||
not re.match(r'"https?:"', line, re.IGNORECASE)]
|
||||
|
||||
# If there is less than 7 lines, it's NOT FOUND response
|
||||
if len(filtered_out) <= 6:
|
||||
return "not_found"
|
||||
|
||||
for line in out:
|
||||
match = re.search(r'Expir.+(\d{4}-\d{2}-\d{2})', line, re.IGNORECASE)
|
||||
if match is not None:
|
||||
return datetime.strptime(match.group(1), '%Y-%m-%d')
|
||||
|
||||
match = re.search(r'Expir.+(\d{2}-\w{3}-\d{4})', line, re.IGNORECASE)
|
||||
if match is not None:
|
||||
return datetime.strptime(match.group(1), '%d-%b-%Y')
|
||||
|
||||
return "expiration_not_found"
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return DNSRecordsDiagnoser(args, env, loggers).diagnose()
|
||||
|
|
2
debian/control
vendored
2
debian/control
vendored
|
@ -29,7 +29,7 @@ Depends: ${python:Depends}, ${misc:Depends}
|
|||
, redis-server
|
||||
, metronome
|
||||
, git, curl, wget, cron, unzip, jq
|
||||
, lsb-release, haveged, fake-hwclock, equivs, lsof
|
||||
, lsb-release, haveged, fake-hwclock, equivs, lsof, whois, python-publicsuffix
|
||||
Recommends: yunohost-admin
|
||||
, ntp, inetutils-ping | iputils-ping
|
||||
, bash-completion, rsyslog
|
||||
|
|
|
@ -172,6 +172,13 @@
|
|||
"diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.<br>Type: <code>{type}</code><br>Name: <code>{name}</code><br>Value: <code>{value}</code>",
|
||||
"diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:<br>Type: <code>{type}</code><br>Name: <code>{name}</code><br>Current value: <code>{current}</code><br>Excepted value: <code>{value}</code>",
|
||||
"diagnosis_dns_point_to_doc": "Please check the documentation at <a href='https://yunohost.org/dns_config'>https://yunohost.org/dns_config</a> if you need help about configuring DNS records.",
|
||||
"diagnosis_domain_expiration_not_found": "Unable to check the expiration date for some domains",
|
||||
"diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired!",
|
||||
"diagnosis_domain_expiration_not_found_details": "The WHOIS information for domain {domain} doesn't seem to contain the information about the expiration date?",
|
||||
"diagnosis_domain_expiration_success": "Your domains are registered and not going to expire anytime soon.",
|
||||
"diagnosis_domain_expiration_warning": "Some domains will expire soon!",
|
||||
"diagnosis_domain_expiration_error": "Some domains will expire VERY SOON!",
|
||||
"diagnosis_domain_expires_in": "{domain} expires in {days} days.",
|
||||
"diagnosis_services_running": "Service {service} is running!",
|
||||
"diagnosis_services_conf_broken": "Configuration is broken for service {service}!",
|
||||
"diagnosis_services_bad_status": "Service {service} is {status} :(",
|
||||
|
|
|
@ -119,6 +119,10 @@ def find_expected_string_keys():
|
|||
for level in ["danger", "thirdparty", "warning"]:
|
||||
yield "confirm_app_install_%s" % level
|
||||
|
||||
for errortype in ["not_found", "error", "warning", "success", "not_found_details"]:
|
||||
yield "diagnosis_domain_expiration_%s" % errortype
|
||||
yield "diagnosis_domain_not_found_details"
|
||||
|
||||
for errortype in ["bad_status_code", "connection_error", "timeout"]:
|
||||
yield "diagnosis_http_%s" % errortype
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue