Beispiel #1
0
def https_disable():
    certs = Certificates()
    if not certs.set_apache_config(ssl=False):
        return error("failed disabling ssl for apache")

    cfg["config"]["https_port"] = None
    cfg.save()

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

        cfg["config"]["proxy_port"] = proxy_port
        cfg.save()

    return success("HTTPS disabled " + add_msg)
Beispiel #2
0
def backup():
    devs = partitions.backup_devices
    data = {
        "devices": devs,
        "backups": backup_restore.find_backups([dev["path"] for dev in devs]),
        "last_backup": cfg["config"]["last_backup"],
    }

    return success(data=data)
Beispiel #3
0
def https_enable():

    board.set("tls", {
        "what": "enable",
        "state": "pending",
    })
    job_queue.put("EnableHTTPS")

    return success("HTTPS enabling pending")
Beispiel #4
0
def test_http():
    nc = Nextcloud()
    res, req = nc.check_reachability()
    data = yaml.load(res.text)
    data["data"]["ipv4"] = [req["ipv4"], req["domain"]]
    data["data"]["ipv6"] = [
        "[" + req["ipv6"] + "]" if req["ipv6"] else "", req["domain"]
    ]
    return success(data["msg"][0], data=data["data"])
Beispiel #5
0
def https():
    dct = {
        "domain": cfg.get("config", {}).get("domain"),
        "email": cfg.get("config", {}).get("email"),
        "https": cfg["config"]["https_port"] is not None,
        "dns_mode": cfg["config"]["dns_mode"]
    }

    return success(data=dct)
Beispiel #6
0
def certs_clear():

    if cfg["config"]["https_port"]:
        return error("cannot clear certificates while HTTPS is active")

    certs = Certificates()
    certs.clear_certs()

    return success("Cleared all certificates")
Beispiel #7
0
def system_settings():
    if request.method == "GET":
        return success(
            data={
                "log_lvl": cfg["config"]["log_lvl"],
                "expert_mode": cfg["config"]["expert_mode"]
            })

    elif request.method == "POST":
        pass
Beispiel #8
0
def umount_storage(name):
    if ".." in name or "/" in name:
        return error("invalid name")

    mount_target = f"/media/{name}"

    if mount_target not in partitions.mounted:
        return error("not mounted")

    if partitions.umount_partition(mount_target):
        return success("Unmounting successful")
    else:
        return error("Failed unmount, check logs...")
Beispiel #9
0
def ssh_set():
    auth_p_dir = Path("/home/nextuser/.ssh")
    if not auth_p_dir.exists():
        os.makedirs(auth_p_dir.as_posix())

    auth_p = auth_p_dir / "authorized_keys"

    if request.method == "GET":
        ip = local_ip()
        if not auth_p.exists():
            return success(data={"pubkey": "", "local_ip": ip})
        with auth_p.open() as fd:
            return success(data={"pubkey": fd.read(), "local_ip": ip})

    elif request.method == "POST":
        pubkey = request.form.get("pubkey")

        with auth_p.open("w") as fd:
            fd.write(pubkey.strip() + "\n")

        log.info(f"setting ssh pub key: {pubkey}")
        return success()
Beispiel #10
0
def getcerts():
    dct = {
        "cert": None,
        "domain": cfg["config"]["domain"],
    }

    if dct.get("domain"):
        certs = Certificates()
        my_cert = certs.get_cert(dct.get("domain"))
        if my_cert:
            dct["cert"] = my_cert

    return success(data=dct)
Beispiel #11
0
def restore_start():
    src_path = request.form.get("src_path")

    if not backup_restore.check_backup(src_path):
        msg = "Invalid backup, cannot restore"
        log.error(msg)
        return error(msg)

    log.info(f"Initiating restore from: {src_path}")

    job_kwargs = {"tar_path": src_path, "mode": "restore"}
    job_queue.put(("BackupRestore", job_kwargs))

    return success("restore started")
Beispiel #12
0
def get_logs():

    log_dir = Path("/srv/logdump")

    journal_logs = {
        "nextbox-daemon": "journald.nextbox-daemon.log",
        "nextbox-compose": "journald.nextbox-compose.log"
    }
    cmd_dump_journalctl = "journalctl --no-pager -n 1000 -xeu {daemon} > {log_dir}/{filename}"

    var_logfiles = [
        "/var/log/dpkg.log",
        "/var/log/unattended-upgrades/unattended-upgrades.log",
        "/var/log/unattended-upgrades/unattended-upgrades-dpkg.log",
        "/var/log/unattended-upgrades/unattended-upgrades-shutdown.log",
        "/var/log/kern.log", "/var/log/letsencrypt/letsencrypt.log",
        LOG_FILENAME
    ]

    logfiles = []

    # cleanup logdump dir
    shutil.rmtree(log_dir.as_posix())
    os.makedirs(log_dir.as_posix())

    for unit, fn in journal_logs.items():
        cmd = cmd_dump_journalctl.format(daemon=unit,
                                         filename=fn,
                                         log_dir=log_dir)
        CommandRunner(cmd, block=True, shell=True)
        logfiles.append(log_dir / fn)

    for path in var_logfiles:
        if Path(path).exists():
            shutil.copy(path, log_dir.as_posix())
            logfiles.append(log_dir / Path(path).name)

    hash_file = Path(log_dir) / "sha256.txt"
    CommandRunner(f"sha256sum {log_dir.as_posix()}/* > {hash_file.as_posix()}",
                  shell=True,
                  block=True)
    logfiles.append(hash_file)

    zip_path = "/srv/nextbox-logs.zip"
    with ZipFile(zip_path, "w") as fd:
        for path in logfiles:
            fd.write(path, path.name)

    with open(zip_path, "rb") as fd:
        return success(data={"zip": b64encode(fd.read())})
Beispiel #13
0
def register_proxy():

    # assemble data
    for key in request.form:
        if key == "nk_token":
            token = request.form.get(key)
        elif key == "proxy_domain":
            proxy_domain = request.form.get(key)
            subdomain = proxy_domain.split(".")[0]

    scheme = "https" if cfg["config"]["https_port"] else "http"

    proxy_tunnel = ProxyTunnel()
    try:
        proxy_port = proxy_tunnel.setup(token, subdomain, scheme)
    except ProxySetupError as e:
        cfg["config"]["proxy_active"] = False
        cfg.save()
        log.error(str(e))
        return error(str(e))
    except Exception as e:
        cfg["config"]["proxy_active"] = False
        cfg.save()
        msg = "unexpected error during proxy setup"
        log.error(msg, exc_info=e)
        return error(msg)

    # configure nextcloud
    nc = Nextcloud()
    try:
        nc.set_config("overwriteprotocol", "https")
        nc.set_config("overwritecondaddr", "^172\\.18\\.238\\.1$")
        nc.set_config("trusted_proxies", ["172.18.238.1"])
    except NextcloudError as e:
        cfg["config"]["proxy_active"] = False
        cfg.save()
        msg = "could not configure nextcloud for proxy usage"
        log.error(msg, exc_info=e)
        return error(msg)

    cfg["config"]["proxy_domain"] = proxy_domain
    cfg["config"]["proxy_active"] = True
    cfg["config"]["proxy_port"] = proxy_port
    cfg.save()

    # ensure trusted domains are set
    job_queue.put("TrustedDomains")

    return success("Proxy successfully registered")
Beispiel #14
0
def get_status():
    # add pkginfo if not yet inside status-board
    if not board.contains_key("pkginfo"):
        board.set("pkginfo", {"version": nextbox_version()})
        keys = board.get_keys()

    # update ips, not more often than 1min
    if board.is_older_than("ips", 60):
        dns = DNSManager()
        board.set("ips", {"ipv4": dns.get_ipv4(), "ipv6": dns.get_ipv6()})

    # return all board data
    keys = board.get_keys()
    status_data = {key: board.get(key) for key in keys}

    return success(data=status_data)
Beispiel #15
0
def test_proxy():
    domain = cfg["config"]["proxy_domain"]
    what = "https"
    url = f"{what}://{domain}"
    if not domain:
        return error("no proxy-domain set")

    out = {"result": None, "domain": domain}
    try:
        res = requests.get(url, timeout=2)
        out["result"] = True
        out["nextcloud"] = "Nextcloud" in res.text
    except (requests.exceptions.ConnectionError, requests.exceptions.SSLError):
        out["result"] = False
        out["nextcloud"] = False

    return success("tested proxy for reachability", data=out)
Beispiel #16
0
def mount_storage(device, name=None):
    devs = partitions.block_devices
    mounted = partitions.mounted

    found = False
    for dev in devs.values():
        for part in dev["parts"].values():
            if part["name"] == device:
                found = True
                if part["mounted"]:
                    return error("device already mounted")
                if part["special"]:
                    return error("cannot mount special device")

    if not found:
        return error("Could not find device!")

    if name is None:
        for idx in range(1, 11):
            _name = f"extra-{idx}"
            mount_target = f"/media/{_name}"
            if mount_target not in mounted:
                name = _name
                break

        if name is None:
            return error("cannot determine mount target, too many mounts?")

    if ".." in device or "/" in device:
        return error("invalid device")
    if ".." in name or "/" in name:
        return error("invalid name")

    mount_target = f"/media/{name}"
    mount_device = f"/dev/{device}"

    if not os.path.exists(mount_target):
        os.makedirs(mount_target)

    if partitions.mount_partition(mount_device, mount_target):
        return success("Mounting successful")
    else:
        return error("Failed mounting, check logs...")
Beispiel #17
0
def dyndns_register():
    data = {}
    for key in request.form:
        if key in ["domain", "email"]:
            data[key] = request.form.get(key)
    data["password"] = None

    headers = {"Content-Type": "application/json"}

    req = urllib.request.Request(DYNDNS_DESEC_REGISTER,
                                 method="POST",
                                 data=json.dumps(data).encode("utf-8"),
                                 headers=headers)

    try:
        res = urllib.request.urlopen(req).read().decode("utf-8")
    except urllib.error.HTTPError as e:
        desc = e.read()
        return error(f"Could not complete registration", data=json.loads(desc))
    return success(data=json.loads(res))
Beispiel #18
0
def test_ddclient():
    cr = CommandRunner([DDCLIENT_BIN, "-verbose", "-foreground", "-force"],
                       block=True)
    cr.log_output()

    for line in cr.output:
        if "SUCCESS:" in line:
            return success("DDClient test: OK")
        if "Request was throttled" in line:
            pat = "available in ([0-9]*) seconds"
            try:
                waitfor = int(re.search(pat, line).groups()[0]) + 5
            except:
                waitfor = 10
            return error("DDClient test: Not OK",
                         data={
                             "reason": "throttled",
                             "waitfor": waitfor
                         })

    return error("DDClient test: Not OK", data={"reason": "unknown"})
Beispiel #19
0
def backup_start():
    tar_path = request.form.get("tar_path")
    found = False
    for dev in partitions.backup_devices:
        if tar_path.startswith(dev["path"]):
            found = dev
            break

    if not found:
        msg = "Invalid backup location provided"
        log.error(msg)
        return error(msg)

    log.info(
        f"Initiating backup onto: {dev['friendly_name']} @ {dev['path']} with target: {tar_path}"
    )

    job_kwargs = {"tar_path": tar_path, "mode": "backup"}
    job_queue.put(("BackupRestore", job_kwargs))

    return success("backup started")
Beispiel #20
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)
Beispiel #21
0
def backup_status_clear():
    board.delete_key("backup_restore")
    return success()
Beispiel #22
0
def storage():
    return success(data=partitions.block_devices)
Beispiel #23
0
def show_log(num_lines=50):
    ret = tail(LOG_FILENAME, num_lines)
    return error(f"could not read log: {LOG_FILENAME}") if ret is None \
        else success(data=ret[:-1])
Beispiel #24
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")
Beispiel #25
0
def service_operation(name, operation):
    if not services.check(name, operation):
        return error("not allowed")

    dct = services.exec(name, operation)
    return success(data=dct)
Beispiel #26
0
def poweroff():
    log.info("POWER-OFF - by /poweroff request")
    cr = CommandRunner("poweroff")
    if cr.returncode != 0:
        return error("failed executing: 'poweroff'")
    return success(data={})
Beispiel #27
0
def reboot():
    log.info("REBOOTING NOW - by /reboot request")
    cr = CommandRunner("reboot")
    if cr.returncode != 0:
        return error("failed executing: 'reboot'")
    return success(data={})
Beispiel #28
0
def backup_status():
    return success(data=board.get("backup_restore"))