Esempio n. 1
0
    def register_job(self, job):
        """register job by `job.name` and construct their instances"""
        if job.name in self.jobs:
            log.warning(f"overwriting job (during register) with name: {job.name}")

        self.jobs[job.name] = job()
        log.info(f"registered job: {job.name}")
Esempio n. 2
0
    def start(self):
        if self.started:
            log.warning(f"trying to start already started: {self.cmd}")
            return False

        self.cmd = self.cmd if not self.shell else " ".join(self.cmd)
        self.proc = subprocess.Popen(self.cmd, shell=self.shell,
            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

        self.started = time.time()
        log.debug(f"started {self.cmd} ({'non-' if not self.block else ''}blocking)")

        # return if non-blocking, else wait + pass-on output
        if not self.block:
            return True

        log.debug("blocking call, waiting now...")

        while self.proc.returncode is None:
            try:
                self.proc.wait(timeout=10)
            except subprocess.TimeoutExpired:
                log.debug("blocking-cmd timeout (10secs)")

        self._handle_stdout_stream(poll_block_ms=1000)
        log.debug("blocking call finished")
        return True
Esempio n. 3
0
    def _run(self, cfg, board, kwargs):
        self.interval = 900

        # only set trusted domains, if nextcloud is installed
        if not self.nc.is_installed:
            log.debug("cannot set trusted_domains - uninitialized nextcloud")
            self.interval = 15
            return False

        try:
            trusted_domains = self.nc.get_config("trusted_domains")
        except NextcloudError:
            log.warning("cannot get trusted_domains from nextcloud")
            self.interval = 15
            return False        

        default_entry = trusted_domains[0]

        entries = [default_entry] + self.static_entries[:]
        if cfg["config"].get("domain"):
            entries.append(cfg["config"]["domain"])

        if cfg["config"].get("proxy_active") and cfg["config"].get("proxy_domain"):
            entries.append(cfg["config"]["proxy_domain"])

        if any(entry not in trusted_domains for entry in entries):
            try:
                self.nc.set_config("trusted_domains", entries)
            except NextcloudError:
                log.warning("failed to write all trusted_domains")
                self.interval = 15
Esempio n. 4
0
    def delete_cert(self, domain):
        certs = self.get_local_certs()
        for cert in certs:
            if domain in cert["domains"]:
                cmd = self.delete_cmd.format(config_dir=self.config_dir,
                                             domain=domain)
                cr = CommandRunner(cmd, block=True)
                return cr.returncode == 0

        log.warning(f"{domain} not in certbot certificates for deletion found")
        return False
    def rsync_dir(self, key, src_dir, tar_dir, block_main=False):
        """
        rsync is used to copy files for export & import.
        * a --dry-run is parsed 1st to determine the number of files (paths) to be created on the target side 
        * -av is used to get one line of output for each "created" file (path), which determines the percentage
        """
        cmd = self.rsync_stats_cmd.format(src=src_dir, tar=tar_dir)
        cr = CommandRunner(cmd, block=True)
        stats = {"key": key}
        parsing = {
            "Number of files:": "all",
            "Number of created files:": "create",
            "Number of deleted files:": "delete",
            "Number of regular files transferred:": "transfer",
            "Total file size:": "size"
        }
        for line in cr.output:
            for needle, tag in parsing.items():
                if needle in line:
                    try:
                        num_tok = line.split(":")[1].strip().split(
                            " ")[0].strip()
                        stats[tag] = int(num_tok.replace(",", ""))
                    except ValueError:
                        log.warning(f"failed parsing: {tag} - line: {line}")
                    break

            # all what we need is parsed
            if len(stats) >= 6:
                break
        self.rsync_stats = stats

        def parse(line, data_dct):
            data_dct.setdefault("line", 0)
            data_dct.setdefault("ratio", 0)
            data_dct["num_files"] = stats
            data_dct["line"] += 1

            cnt = stats.get("create", 0)
            if cnt == 0:
                data_dct["ratio"] = 100
            else:
                data_dct["ratio"] = int(
                    round(min(1, max(0, data_dct["line"] / cnt)), 2) * 100)

        cmd = self.rsync_base_cmd.format(src=src_dir, tar=tar_dir)
        self.rsync_proc = CommandRunner(cmd, cb_parse=parse, block=block_main)
        return False
Esempio n. 6
0
    def retries(cls, count, *vargs, **kwargs):
        # cannot retry, if non-blocking
        if not kwargs.get("block"):
            log.warning("trying CommandRunner-retries w/o block: no retries are done!")
            return cls(*vargs, **kwargs)

        # if 'block' is set, retry until returncode == 0
        for idx in range(count):
            res = cls(*vargs, **kwargs)
            if res.returncode == 0:
                return res
            log.debug(f"retry #{idx+1}, returncode: {res.returncode}")
            res.log_output()

        # failed retries
        log.error("retried hard for {count} times, failed anyways")
        return res
Esempio n. 7
0
    def _run(self, cfg, board, kwargs):

        token = cfg["config"]["desec_token"]
        dns_mode = cfg["config"]["dns_mode"]
        domain = cfg["config"]["domain"]

        if dns_mode not in ["desec_done", "off"]:
            if not services.is_active("ddclient"):
                services.restart("ddclient")
                services.enable("ddclient")
            return

        if dns_mode == "off":
            if services.is_active("ddclient"):
                services.stop("ddclient")
                services.disable("ddclient")
            return

        if dns_mode == "desec_done":
            if services.is_active("ddclient"):
                services.stop("ddclient")
                services.disable("ddclient")

            dns = DNSManager()
            ipv4 = dns.get_ipv4()
            ipv6 = dns.get_ipv6()

            headers = {"Authorization": f"Token {token}"}
            
            params = {"hostname": domain}
            if ipv4:
                params["myipv4"] = ipv4
            if ipv6:
                params["myipv6"] = ipv6

            res = requests.get("https://update.dedyn.io", params=params, headers=headers)

            if res.ok:
                log.info(f"updated deSEC IPv4 ({ipv4}) and IPv6 ({ipv6}) address for '{domain}'")
            else:
                log.warning(f"failed updating IPs ({ipv4} & {ipv6}) for domain: '{domain}'")
                log.debug(f"result: {res.text} status_code: {res.status_code} url: {res.url}")
Esempio n. 8
0
def test_resolve4():
    ip_type = request.path.split("/")[-1]
    domain = cfg["config"]["domain"]
    resolve_ip = None
    ext_ip = None

    if not domain:
        return error("no domain is set")

    dns = DNSManager()
    dns.clear_dns_caches()

    if ip_type == "ipv4":
        resolve_ip = dns.resolve_ipv4(domain)
        ext_ip = dns.get_ipv4()
    else:
        resolve_ip = dns.resolve_ipv6(domain)
        ext_ip = dns.get_ipv6()

    log.info(
        f"resolving '{domain}' to IP: {resolve_ip}, external IP: {ext_ip}")
    data = {"ip": ext_ip, "resolve_ip": resolve_ip, "domain": domain}

    # if not both "resolve" and "getip" are successful, we have failed
    if resolve_ip is None or ext_ip is None:
        log.error(f"failed resolving and/or getting external {ip_type}")
        return error("Resolve test: Not OK", data=data)

    # resolving to wrong ip
    if resolve_ip != ext_ip:
        log.warning(f"Resolved {ip_type} does not match external {ip_type}")
        log.warning("This might indicate a bad DynDNS configuration")
        return error("Resolve test: Not OK", data=data)

    # all good!
    return success("Resolve test: OK", data=data)
Esempio n. 9
0
def handle_config():
    if request.method == "GET":
        data = dict(cfg["config"])
        try:
            data["conf"] = Path(DDCLIENT_CONFIG_PATH).read_text("utf-8")
        except FileNotFoundError:
            data["conf"] = ""
        return success(data=data)

    # save dyndns related values to configuration
    elif request.method == "POST":
        run_jobs = []
        for key in request.form:
            val = request.form.get(key)

            # special config-value 'conf' represents ddclient-config-contents
            if key == "conf":
                old_conf = Path(DDCLIENT_CONFIG_PATH).read_text("utf-8")
                if old_conf != val:
                    log.info("writing ddclient config and restarting service")
                    Path(DDCLIENT_CONFIG_PATH).write_text(val, "utf-8")

                elif len(val.strip()) == 0:
                    log.info("writing empty ddclient config")
                    Path(DDCLIENT_CONFIG_PATH).write_text(val, "utf-8")
                    services.stop("ddclient")
                    services.disable("ddclient")

                if len(val.strip()) > 0:
                    services.enable("ddclient")
                    services.restart("ddclient")

                run_jobs.append("DynDNSUpdate")

            elif key in AVAIL_CONFIGS and val is not None:
                # only allow valid DYNDNS_MODES
                if key == "dns_mode" and val not in DYNDNS_MODES:
                    log.warning(
                        f"key: 'dns_mode' has invalid value: {val} - skipping")
                    continue

                # start DynDNS update on "desec_done"
                elif key == "dns_mode" and val == "desec_done":
                    run_jobs.append("DynDNSUpdate")

                # start TrustedDomains update on new domain
                elif "domain" in key:
                    run_jobs.append("TrustedDomains")

                # deactivate proxy on request
                elif key == "proxy_active" and val.lower() == "false":
                    proxy_tunnel = ProxyTunnel()
                    proxy_tunnel.stop()

                # skip if 'val' is empty
                elif val is None:
                    log.debug(f"skipping key: '{key}' -> no value provided")
                    continue

                # convert to bool, ugly?
                if val.lower() in ["true", "false"]:
                    val = val.lower() == "true"

                # put key-value into cfg and save (yes, saving each value)
                cfg["config"][key] = val
                log.debug(f"saving key: '{key}' with value: '{val}'")
                cfg.save()

        # run jobs collected during configuration update
        if len(run_jobs) > 0:
            for job in run_jobs:
                job_queue.put(job)

        return success("DynDNS configuration saved")
Esempio n. 10
0
 def _run(self, cfg=None, board=None, kwargs=None):
     
     # factory-reset is executed by systemd
     log.warning("Starting factory-reset operation")
     shield.set_led_state("factory-reset")
     services.start("nextbox-factory-reset")
Esempio n. 11
0
    def _run(self, cfg, board, kwargs):
        board.set("tls", {
            "what": "enable",
            "state": "running",
        }) 

        certs = Certificates()
        domain = cfg.get("config", {}).get("domain")
        email = cfg.get("config", {}).get("email")
        
        if not domain or not email:
            log.error(f"cannot enable tls, domain: '{domain}' email: '{email}'")
            board.set("tls", {
                "what": "domain-or-email",
                "state": "fail",
            })
            return False

        my_cert = certs.get_cert(domain)
        if not my_cert:
            log.warning(f"could not get local certificate for: {domain}, acquiring...")
            if not certs.acquire_cert(domain, email):
                msg = f"Failed to acquire {domain} with {email}"
                log.error(msg)
                board.set("tls", {
                    "what": "acquire",
                    "state": "fail",
                })
                return False
            log.info(f"acquired certificate for: {domain} with {email}")
            my_cert = certs.get_cert(domain)

        certs.write_apache_ssl_conf(
            my_cert["domains"][0], 
            my_cert["fullchain_path"], 
            my_cert["privkey_path"]
        )

        if not certs.set_apache_config(ssl=True):
            board.set("tls", {
                "what": "apache-config",
                "state": "fail",
            })
            log.error("failed enabling ssl configuration for apache")
            return False

        log.info(f"activated https for apache using: {domain}")
        
        cfg["config"]["https_port"] = 443
        cfg["config"]["email"] = email    
        cfg.save()

        board.set("tls", {
            "what": "enable",
            "state": "success",
        })

        # we need to "re-wire" the proxy to port 443 on activated TLS
        add_msg = ""
        if cfg["config"]["proxy_active"]:
            subdomain = cfg["config"]["proxy_domain"].split(".")[0]
            scheme = "https"
            token = cfg["config"]["nk_token"]
            proxy_tunnel = ProxyTunnel()
            try:
                proxy_port = proxy_tunnel.setup(token, subdomain, scheme)
            except ProxySetupError as e:
                log.error("register at proxy-server error", exc_info=e)
            except Exception as e:
                log.error("unexpected error during proxy setup", exc_info=e)
                
            cfg["config"]["proxy_port"] = proxy_port
            cfg.save()