Esempio n. 1
0
    def mount(self, passwd=None):
        """
        Mount partition.

        :param str passwd: If disk is encrypted, use this passphrase to unlock
        """
        if self.mountpoint and os.path.ismount(self.mountpoint):
            raise errors.InvalidConfigError("Virtual disk already mounted")
        signals.emit("filesystems", "pre_mount", self)
        if not os.path.isdir(os.path.join("/media", self.id)):
            os.makedirs(os.path.join("/media", self.id))
        mount_point = self.mountpoint or os.path.join("/media", self.id)
        luks_point = os.path.join("/dev/mapper", self.id)
        # Find a free loopback device and mount
        loop = losetup.find_unused_loop_device()
        loop.mount(str(self.path), offset=1048576)
        if self.crypt and passwd:
            # If it's an encrypted virtual disk, decrypt first then mount
            s = crypto.luks_open(loop.device, self.id, passwd)
            if s != 0:
                loop.unmount()
                excmsg = "Failed to decrypt {0} with errno {1}"
                raise errors.OperationFailedError(
                    excmsg.format(self.id, str(s)))
            s = libc.mount(ctypes.c_char_p(b(luks_point)),
                           ctypes.c_char_p(b(mount_point)),
                           ctypes.c_char_p(b(self.fstype)), 0,
                           ctypes.c_char_p(b""))
            if s == -1:
                crypto.luks_close(self.id)
                loop.unmount()
                excmsg = "Failed to mount {0}: {1}"
                raise errors.OperationFailedError(
                    excmsg.format(self.id, os.strerror(ctypes.get_errno())))
        elif self.crypt and not passwd:
            excstr = "Must provide password to decrypt encrypted container"
            raise errors.InvalidConfigError(excstr)
        else:
            s = libc.mount(ctypes.c_char_p(b(loop.device)),
                           ctypes.c_char_p(b(mount_point)),
                           ctypes.c_char_p(b(self.fstype)), 0,
                           ctypes.c_char_p(b""))
            if s == -1:
                loop.unmount()
                excstr = "Failed to mount {0}: {1}"
                raise errors.OperationFailedError(
                    excmsg.format(self.id, os.strerror(ctypes.get_errno())))
        signals.emit("filesystems", "post_mount", self)
        self.mountpoint = mount_point
Esempio n. 2
0
File: backup.py Progetto: ns408/core
def create(id, data=True, nthread=NotificationThread()):
    """
    Convenience function to create a backup.

    :param str id: ID of associated app (or website) to backup
    :param bool data: Backup app data also?
    :returns: Backup info
    :rtype: Backup
    """
    controller = None
    if id == "arkOS":
        controller = arkOSBackupCfg("arkOS", "setting",
                                    version=arkos_version)
        return controller.backup()
    app = applications.get(id)
    if app and app.type != "website" and hasattr(app, "_backup"):
        controller = app._backup(app.id, app.icon, version=app.version)
    else:
        sites = websites.get()
        for x in sites:
            if x.id == id:
                controller = x.backup
                break
    if not controller:
        raise errors.InvalidConfigError("No backup controller found")
    return controller.backup(data=data, nthread=nthread)
Esempio n. 3
0
 def execute(self, cmd, commit=False, strf=True):
     self.manager.connect()
     conns.MariaDB.query('USE {0}'.format(self.id))
     cur = conns.MariaDB.cursor()
     parse, s = [], ""
     for l in cmd.split('\n'):
         if not l.split() or re.match('--', l):
             continue
         elif not re.search('[^-;]+;', l):
             s = s + l
         elif re.search('^\s*USE\s*', l, re.IGNORECASE):
             raise errors.InvalidConfigError(
                 'Cannot switch databases during execution')
         else:
             s = s + l
             cur.execute(s)
             for x in cur.fetchall():
                 parse.append(x)
             s = ""
     if commit:
         conns.MariaDB.commit()
     if strf:
         status = ""
         for line in parse:
             line = [str(x) for x in line]
             status += ', '.join(line) + '\n'
         return status
     else:
         return parse
Esempio n. 4
0
File: backup.py Progetto: ns408/core
def restore(backup, data=True, nthread=NotificationThread()):
    """
    Convenience function to restore a backup.

    :param Backup backup: Backup to restore
    :param bool data: Restore included data files as well?
    :returns: Backup info
    :rtype: Backup
    """
    controller = None
    if backup["type"] == "site":
        sites = websites.get()
        for x in sites:
            if x.id == backup["pid"]:
                controller = x.backup
                break
        else:
            app = applications.get(backup["site_type"])
            controller = app._backup(backup["pid"], backup["icon"], True)
    else:
        app = applications.get(backup["pid"])
        controller = app._backup()
    if not controller:
        raise errors.InvalidConfigError("No backup controller found")
    b = controller.restore(backup, data, nthread)
    return b
Esempio n. 5
0
 def add_share(self):
     """Add a share."""
     config = configparser.ConfigParser()
     config.read(["/etc/samba/smb.conf"])
     if config.has_section(self.id):
         raise errors.InvalidConfigError(
             "Share already present with this name"
         )
     if not os.path.exists(self.path):
         os.makedirs(self.path)
     config.set("global", "security", "user")
     config.set("global", "map to guest", "bad user")
     config.add_section(self.id)
     config.set(self.id, "comment", self.comment)
     config.set(self.id, "path", self.path)
     config.set(self.id, "browseable", "yes")
     config.set(self.id, "public", "yes" if not self.valid_users else "no")
     config.set(
         self.id, "guest ok", "yes" if not self.valid_users else "no"
     )
     config.set(self.id, "read only", "yes" if self.readonly else "no")
     config.set(self.id, "writable", "no" if self.readonly else "yes")
     if self.valid_users:
         config.set(self.id, "valid users", " ".join(self.valid_users))
     with open("/etc/samba/smb.conf", "w") as f:
         config.write(f)
     svc = services.get("smbd")
     if svc:
         svc.restart()
Esempio n. 6
0
 def remove(self):
     """Delete domain."""
     if self.name in [x.domain for x in users.get()]:
         emsg = "A user is still using this domain"
         raise errors.InvalidConfigError(emsg)
     signals.emit("domains", "pre_remove", self)
     conns.LDAP.delete_s(self.ldap_id)
     signals.emit("domains", "post_remove", self)
Esempio n. 7
0
    def mount(self, passwd=None):
        """
        Mount partition.

        :param str passwd: If disk is encrypted, use this passphrase to unlock
        """
        if self.mountpoint and os.path.ismount(self.mountpoint):
            raise errors.InvalidConfigError("Disk partition already mounted")
        elif self.fstype == "Unknown":
            emsg = "Cannot mount a partition of unknown type"
            raise errors.InvalidConfigError(emsg)
        signals.emit("filesystems", "pre_mount", self)
        mount_point = self.mountpoint or os.path.join("/media", self.id)
        luks_point = os.path.join("/dev/mapper", self.id)
        if not os.path.isdir(mount_point):
            os.makedirs(mount_point)
        if self.crypt and passwd:
            # Decrypt the disk first if it's an encrypted disk
            s = crypto.luks_open(self.path, self.id, passwd)
            if s != 0:
                excmsg = "Failed to decrypt {0} with errno {1}"
                excmsg = excmsg.format(self.id, str(s))
                raise errors.OperationFailedError(excmsg)
            s = libc.mount(ctypes.c_char_p(b(luks_point)),
                           ctypes.c_char_p(b(mount_point)),
                           ctypes.c_char_p(b(self.fstype)), 0,
                           ctypes.c_char_p(b""))
            if s == -1:
                crypto.luks_close(self.id)
                excmsg = "Failed to mount {0}: {1}"
                raise errors.OperationFailedError(
                    excmsg.format(self.id, os.strerror(ctypes.get_errno())))
        elif self.crypt and not passwd:
            emsg = "Must provide password to decrypt encrypted disk"
            raise errors.InvalidConfigError(emsg)
        else:
            s = libc.mount(ctypes.c_char_p(b(self.path)),
                           ctypes.c_char_p(b(mount_point)),
                           ctypes.c_char_p(b(self.fstype)), 0,
                           ctypes.c_char_p(b""))
            if s == -1:
                excmsg = "Failed to mount {0}: {1}"
                raise errors.OperationFailedError(
                    excmsg.format(self.id, os.strerror(ctypes.get_errno())))
        signals.emit("filesystems", "post_mount", self)
        self.mountpoint = mount_point
Esempio n. 8
0
    def _update(self, nthread):
        nthread.title = "Updating website"
        if self.version == self.app.version.rsplit("-", 1)[0]:
            raise errors.InvalidConfigError(
                "Website is already at the latest version")
        elif self.version in [None, "None"]:
            raise errors.InvalidConfigError(
                "Updates not supported for this website type")

        # Classify the source package type
        if not self.app.download_url:
            ending = ""
        elif self.app.download_url.endswith(".tar.gz"):
            ending = ".tar.gz"
        elif self.app.download_url.endswith(".tgz"):
            ending = ".tgz"
        elif self.app.download_url.endswith(".tar.bz2"):
            ending = ".tar.bz2"
        elif self.app.download_url.endswith(".zip"):
            ending = ".zip"
        elif self.app.download_url.endswith(".git"):
            ending = ".git"
        else:
            raise errors.InvalidConfigError(
                "Invalid source archive format in {0}".format(self.app.id))

        # Download and extract the source package
        msg = "Downloading website source..."
        nthread.update(Notification("info", "Webs", msg))
        if self.app.download_url and ending == ".git":
            pkg_path = self.download_url
        elif self.app.download_url:
            pkg_path = os.path.join("/tmp", self.id + ending)
            download(self.app.download_url, file=pkg_path, crit=True)

        # Call the site type's update hook
        msg = "Updating website..."
        nthread.update(Notification("info", "Webs", msg))
        self.update_site(pkg_path, self.app.version)

        # Update stored version and remove temp source archive
        msg = "{0} updated successfully".format(self.id)
        nthread.complete(Notification("success", "Webs", msg))
        self.version = self.app.version.rsplit("-", 1)[0]
        if pkg_path:
            os.unlink(pkg_path)
Esempio n. 9
0
 def validate(self, id='', user='', passwd=''):
     if id and re.search('\.|-|`|\\\\|\/|^test$|[ ]', id):
         raise errors.InvalidConfigError(
             'Database name must not contain spaces, dots, dashes or other '
             'special characters')
     elif id and len(id) > 16:
         raise errors.InvalidConfigError(
             'Database name must be shorter than 16 characters')
     if user and re.search('\.|-|`|\\\\|\/|^test$|[ ]', user):
         raise errors.InvalidConfigError(
             'Database username must not contain spaces, dots, dashes or '
             'other special characters')
     elif user and len(user) > 16:
         raise errors.InvalidConfigError(
             'Database username must be shorter than 16 characters')
     if passwd and len(passwd) < 8:
         raise errors.InvalidConfigError(
             'Database password must be longer than 8 characters')
     if id:
         for x in self.get_dbs():
             if x.id == id:
                 raise errors.InvalidConfigError(
                     'You already have a database named {0} - please '
                     'remove that one or choose a new name!'.format(id))
     if user:
         for x in self.get_users():
             if x.id == user:
                 raise errors.InvalidConfigError(
                     'You already have a database user named {0} - please '
                     'remove that one or choose a new name!'.format(user))
     return True
Esempio n. 10
0
    def uninstall(self, force=False, nthread=NotificationThread()):
        """
        Uninstall the arkOS application from the system.

        :param bool force: Uninstall the app even if others depend on it?
        :param NotificationThread nthread: notification thread to use
        """
        signals.emit("apps", "pre_remove", self)
        msg = "Uninstalling application..."
        nthread.update(Notification("info", "Apps", msg))
        exclude = ["openssl", "openssh", "nginx", "python2", "git",
                   "nodejs", "npm"]

        # Make sure this app can be successfully removed, and if so also remove
        # any system-level packages that *only* this app requires
        for x in get(installed=True):
            for item in x.dependencies:
                if item["type"] == "app" and item["package"] == self.id \
                        and not force:
                    exc_str = "{0} depends on this application"
                    raise errors.InvalidConfigError(exc_str.format(x.name))
                elif item["type"] == "system":
                    exclude.append(item["package"])

        # Stop any running services associated with this app
        for item in self.dependencies:
            if item["type"] == "system" and not item["package"] in exclude:
                if item.get("daemon"):
                    try:
                        services.get(item["daemon"]).stop()
                        services.get(item["daemon"]).disable()
                    except:
                        pass
                pacman.remove([item["package"]],
                              purge=config.get("apps", "purge"))

        # Remove the app's directory and cleanup the app object
        shutil.rmtree(os.path.join(config.get("apps", "app_dir"), self.id))
        self.loadable = False
        self.installed = False

        # Regenerate the firewall and re-block the abandoned ports
        regen_fw = False
        for x in self.services:
            if x["ports"]:
                regen_fw = True
        if regen_fw:
            tracked_services.deregister(self.id)
        ports = []
        for s in self.services:
            if s.get("default_policy", 0) and s["ports"]:
                ports.append(s["ports"])
        if ports and config.get("general", "enable_upnp"):
            tracked_services.close_all_upnp(ports)
        smsg = "{0} uninstalled successfully".format(self.name)
        nthread.complete(Notification("success", "Apps", smsg))
        signals.emit("apps", "post_remove", self)
Esempio n. 11
0
 def add_db(self):
     if re.search('\.|-|`|\\\\|\/|[ ]', self.id):
         raise errors.InvalidConfigError(
             'Name must not contain spaces, dots, dashes or other '
             'special characters')
     self.manager.chkpath()
     status = shell("sqlite3 {0} \"ATTACH '{1}' AS {2};\"".format(
         self.path, self.path, self.id))
     if status["code"] >= 1:
         raise errors.OperationFailedError(status["stderr"])
Esempio n. 12
0
 def add(self):
     """Add the domain to LDAP."""
     try:
         ldif = conns.LDAP.search_s(self.ldap_id, ldap.SCOPE_SUBTREE,
                                    "(objectClass=*)", None)
         emsg = "This domain is already present here"
         raise errors.InvalidConfigError(emsg)
     except ldap.NO_SUCH_OBJECT:
         pass
     ldif = {"virtualdomain": [b(self.name)],
             "objectClass": [b"mailDomain", b"top"]}
     signals.emit("domains", "pre_add", self)
     conns.LDAP.add_s(self.ldap_id, ldap.modlist.addModlist(ldif))
     signals.emit("domains", "post_add", self)
Esempio n. 13
0
File: groups.py Progetto: ns408/core
    def update(self):
        """Update a group object in LDAP. Change params on the object first."""
        try:
            ldif = conns.LDAP.search_s(self.ldap_id, ldap.SCOPE_SUBTREE,
                                       "(objectClass=*)", None)
        except ldap.NO_SUCH_OBJECT:
            raise errors.InvalidConfigError("This group does not exist")

        ldif = ldap.modlist.modifyModlist(
            ldif[0][1], {"memberUid": [b(u) for u in self.users]},
            ignore_oldexistent=1)
        signals.emit("groups", "pre_update", self)
        conns.LDAP.modify_s(self.ldap_id, ldif)
        signals.emit("groups", "post_update", self)
Esempio n. 14
0
    def create(self,
               mount=False,
               will_crypt=False,
               nthread=NotificationThread()):
        """
        Create virtual disk image.

        :param bool mount: Mount after creation?
        :param bool will_crypt: Will this disk be encrypted later?
        :param NotificationThread nthread: notification thread to use
        """
        nthread.title = "Creating virtual disk"

        vdisk_dir = config.get("filesystems", "vdisk_dir")
        if not os.path.exists(vdisk_dir):
            os.mkdir(vdisk_dir)
        self.path = str(os.path.join(vdisk_dir, self.id + ".img"))
        if os.path.exists(self.path):
            raise errors.InvalidConfigError("This virtual disk already exists")

        # Create an empty file matching disk size
        signals.emit("filesystems", "pre_add", self)
        msg = "Creating virtual disk..."
        nthread.update(Notification("info", "Filesystems", msg))
        with open(self.path, "wb") as f:
            written = 0
            with open("/dev/zero", "rb") as zero:
                while self.size > written:
                    written += 1024
                    f.write(zero.read(1024))

        if not will_crypt:
            # Get a free loopback device and mount
            loop = losetup.find_unused_loop_device()
            loop.mount(str(self.path), offset=1048576)
            # Make a filesystem
            msg = "Writing filesystem..."
            nthread.update(Notification("info", "Filesystems", msg))
            s = shell("mkfs.ext4 {0}".format(loop.device))
            if s["code"] != 0:
                excmsg = "Failed to format loop device: {0}"
                raise errors.OperationFailedError(excmsg.format(s["stderr"]))
            loop.unmount()
            msg = "Virtual disk created successfully"
            nthread.complete(Notification("success", "Filesystems", msg))

        signals.emit("filesystems", "post_add", self)
        if mount:
            self.mount()
Esempio n. 15
0
 def post(self):
     data = request.get_json()["user"]
     try:
         u = users.User(name=data["name"],
                        first_name=data["first_name"],
                        last_name=data["last_name"],
                        domain=data["domain"],
                        admin=data["admin"],
                        sudo=data["sudo"])
         u.add(data["passwd"])
     except KeyError as e:
         raise errors.InvalidConfigError(str(e) if e else "Value not found")
     except errors.InvalidConfigError as e:
         return jsonify(errors={"msg": str(e)}), 422
     return jsonify(user=u.serialized)
Esempio n. 16
0
 def add_share(self):
     """Add a share."""
     with open("/etc/exports", "r") as f:
         data = f.readlines()
     if any([shlex.split(x)[0] == self.path for x in data]):
         raise errors.InvalidConfigError(
             "Share already present with this path"
         )
     if not os.path.exists(self.path):
         os.makedirs(self.path)
     sstr = '"' + self.path + '" '
     sstr += '*(' + ("ro" if self.readonly else "rw") + ',sync)'
     with open("/etc/exports", "w") as f:
         f.writelines(data)
         f.write(sstr + '\n')
     shell("exportfs -arv")
Esempio n. 17
0
 def enable(self):
     """Enable mounting of this partition on boot."""
     if self.crypt:
         raise errors.InvalidConfigError(
             "Cannot enable encrypted virutal disks")
     f = FstabEntry()
     f.src = self.path
     f.dst = os.path.join("/media", self.id)
     f.uuid = get_partition_uuid_by_name(self.path)
     f.fs_type = "ext4"
     f.options = "defaults"
     f.dump_p = 0
     f.fsck_p = 0
     save_fstab_entry(f)
     if not os.path.exists(f.dst):
         os.makedirs(f.dst)
     self.enabled = True
Esempio n. 18
0
 def put(self, id):
     data = request.get_json()["user"]
     u = users.get(id)
     if not u:
         abort(404)
     try:
         u.first_name = data["first_name"]
         u.last_name = data["last_name"]
         u.domain = data["domain"]
         u.admin = data["admin"]
         u.sudo = data["sudo"]
         u.mail = [str(x) for x in data["mail_addresses"]]
         u.update(data.get("passwd"))
     except KeyError as e:
         raise errors.InvalidConfigError(str(e))
     except errors.InvalidConfigError as e:
         return jsonify(errors={"msg": str(e)}), 422
     return jsonify(user=u.serialized)
Esempio n. 19
0
 def __init__(self,
              level,
              comp,
              message,
              cls="notify",
              id=None,
              title=None):
     level = level.upper()
     if level not in self.LEVELS:
         raise errors.InvalidConfigError("Unrecognized log level specified")
     id = id or random_string(16)
     self.level = self.LEVELS[level]
     self.comp = comp
     self.message = message
     self.cls = cls
     self.id = id
     self.title = title
     self.message_id = id
     self.complete = True
Esempio n. 20
0
    def add(self, passwd):
        """
        Add the user to LDAP.

        :param str passwd: user password to set
        """
        try:
            ldif = conns.LDAP.search_s(self.ldap_id, ldap.SCOPE_BASE,
                                       "(objectClass=*)", None)
            msg = "A user named {0} already exists".format(self.name)
            raise errors.InvalidConfigError(msg)
        except ldap.NO_SUCH_OBJECT:
            pass

        # Create LDAP user with proper metadata
        ldif = {
            "objectClass": [b"mailAccount", b"inetOrgPerson", b"posixAccount"],
            "givenName": [b(self.first_name)],
            "sn": [b(self.last_name)] if self.last_name else [b"NONE"],
            "displayName": [b(self.full_name)],
            "cn": [b(self.full_name)],
            "uid": [b(self.name)],
            "mail": [b(self.name + "@" + self.domain)],
            "maildrop": [b(self.name)],
            "userPassword": [b(ldap_sha512_crypt.encrypt(passwd))],
            "gidNumber": [b"100"],
            "uidNumber": [b(str(self.uid))],
            "homeDirectory": [b("/home/" + self.name)],
            "loginShell": [b"/usr/bin/bash"]
        }
        ldif = ldap.modlist.addModlist(ldif)
        signals.emit("users", "pre_add", self)
        logger.debug("Roles", "Adding user: {0}".format(self.ldap_id))
        conns.LDAP.add_s(self.ldap_id, ldif)
        modes = ["admin" if self.admin else "", "sudo" if self.sudo else ""]
        msg = "Setting user modes: {0}".format(", ".join(modes))
        logger.debug("Roles", msg)

        self.update_adminsudo()
        self.update_samba(passwd)

        signals.emit("users", "post_add", {"user": self, "passwd": passwd})
Esempio n. 21
0
    def update(self, newpasswd=""):
        """
        Update a user's object in LDAP. Change params on the object first.

        To change password, do so via the ``newpasswd`` param here.

        :param str newpasswd: new password to set
        """
        try:
            ldif = conns.LDAP.search_s(self.ldap_id, ldap.SCOPE_SUBTREE,
                                       "(objectClass=*)", None)
        except ldap.NO_SUCH_OBJECT:
            raise errors.InvalidConfigError("Users",
                                            "This user does not exist")

        self.mail = list(set(self.mail))

        for i, x in enumerate(self.mail):
            if not x.endswith(self.domain):
                self.mail[i] = x.split("@")[0] + "@" + self.domain

        ldif = ldif[0][1]
        attrs = {
            "givenName": [b(self.first_name)],
            "sn": [b(self.last_name)] if self.last_name else [b"NONE"],
            "displayName": [b(self.full_name)],
            "cn": [b(self.full_name)],
            "mail": [b(x) for x in self.mail]
        }
        if newpasswd:
            attrs["userPassword"] = [b(ldap_sha512_crypt.encrypt(newpasswd))]
        signals.emit("users", "pre_update", self)
        nldif = ldap.modlist.modifyModlist(ldif, attrs, ignore_oldexistent=1)
        conns.LDAP.modify_s(self.ldap_id, nldif)

        self.update_adminsudo()
        self.update_samba(newpasswd)

        signals.emit("users", "post_update", {
            "user": self,
            "passwd": newpasswd
        })
Esempio n. 22
0
File: groups.py Progetto: ns408/core
 def add(self):
     """Add the group to LDAP."""
     try:
         ldif = conns.LDAP.search_s(self.ldap_id, ldap.SCOPE_SUBTREE,
                                    "(objectClass=*)", None)
         emsg = "A group with this name already exists"
         raise errors.InvalidConfigError(emsg)
     except ldap.NO_SUCH_OBJECT:
         pass
     ldif = {
         "objectClass": [b"posixGroup", b"top"],
         "cn": [b(self.name)],
         "gidNumber": [b(str(self.gid))]
     }
     if self.users:
         ldif["memberUid"] = [b(u) for u in self.users]
     ldif = ldap.modlist.addModlist(ldif)
     signals.emit("groups", "pre_add", self)
     conns.LDAP.add_s(self.ldap_id, ldif)
     signals.emit("groups", "post_add", self)
Esempio n. 23
0
 def add_share(self):
     """Add a share."""
     config = configparser.ConfigParser()
     config.read(["/etc/afp.conf"])
     if config.has_section(self.id):
         raise errors.InvalidConfigError(
             "Share already present with this name")
     if not os.path.exists(self.path):
         os.makedirs(self.path)
     config.add_section(self.id)
     config.set(self.id, "path", self.path)
     config.set(self.id, "read only", "yes" if self.readonly else "no")
     if self.valid_users:
         config.set(self.id, "valid users", " ".join(self.valid_users))
     config.set("Global", "ldap_server", "localhost")
     config.set("Global", "ldap_auth_method", "none")
     config.set("Global", "ldap_userbase",
                "ou=users,dc=arkos-servers,dc=org")
     with open("/etc/afp.conf", "w") as f:
         config.write(f)
     svc = services.get("netatalk")
     if svc and svc.state:
         svc.restart()
Esempio n. 24
0
    def _ssl_enable(self):
        # Get server-preferred ciphers
        if config.get("certificates", "ciphers"):
            ciphers = config.get("certificates", "ciphers")
        else:
            config.set("certificates", "ciphers", ciphers)
            config.save()

        block = nginx.loadf(
            os.path.join("/etc/nginx/sites-available/", self.id))

        # If the site is on port 80, setup an HTTP redirect to new port 443
        server = block.server
        listens = server.filter("Key", "listen")
        for listen in listens:
            httport = "80"
            sslport = "443"
            if listen.value.startswith("[::]"):
                # IPv6
                httport = "[::]:80"
                sslport = "[::]:443"
            if listen.value == httport:
                listen.value = (sslport + " ssl http2")
                block.add(
                    nginx.Server(
                        nginx.Key("listen", httport),
                        nginx.Key("server_name", self.domain),
                        nginx.Location(
                            "/",
                            nginx.Key("return",
                                      "301 https://$host$request_uri")),
                        nginx.Location("/.well-known/acme-challenge/",
                                       nginx.Key("root", self.path))))
                for x in block.servers:
                    if " ssl" in x.filter("Key", "listen")[0].value:
                        server = x
                        break
            else:
                listen.value = listen.value.split(" ssl")[0] + " ssl http2"

        # Clean up any pre-existing SSL directives that no longer apply
        to_remove = [x for x in server.keys if x.name.startswith("ssl_")]
        server.remove(*to_remove)

        # Add the necessary SSL directives to the serverblock and save
        server.add(
            nginx.Key("ssl_certificate", self.cert.cert_path),
            nginx.Key("ssl_certificate_key", self.cert.key_path),
            nginx.Key("ssl_protocols", "TLSv1 TLSv1.1 TLSv1.2"),
            nginx.Key("ssl_ciphers", ciphers),
            nginx.Key("ssl_session_timeout", "5m"),
            nginx.Key("ssl_prefer_server_ciphers", "on"),
            nginx.Key("ssl_dhparam", "/etc/arkos/ssl/dh_params.pem"),
            nginx.Key("ssl_session_cache", "shared:SSL:50m"),
        )
        nginx.dumpf(block, os.path.join("/etc/nginx/sites-available/",
                                        self.id))

        # Set the certificate name in the metadata file
        if not os.path.exists(os.path.join(self.path, ".arkos")):
            raise errors.InvalidConfigError("Could not find metadata file")
        meta = configparser.SafeConfigParser()
        meta.read(os.path.join(self.path, ".arkos"))
        meta.set("website", "ssl", self.cert.id)
        with open(os.path.join(self.path, ".arkos"), "w") as f:
            meta.write(f)

        # Call the website type's SSL enable hook
        self.enable_ssl(self.cert.cert_path, self.cert.key_path)
Esempio n. 25
0
    def _install(self, extra_vars, enable, nthread):
        nthread.title = "Installing website"

        msg = Notification("info", "Webs", "Preparing to install...")
        nthread.update(msg)

        # Make sure the chosen port is indeed open
        if not tracked_services.is_open_port(self.port, self.domain):
            cname = "({0})".format(self.app.id)
            raise errors.InvalidConfigError(cname, nthread)\
                from tracked_services.PortConflictError(self.port, self.domain)

        # Set some metadata values
        specialmsg, dbpasswd = "", ""
        site_dir = config.get("websites", "site_dir")
        path = (self.path or os.path.join(site_dir, self.id))
        self.path = path
        self.php = extra_vars.get("php") or self.php \
            or self.app.uses_php or False
        self.version = self.app.version.rsplit("-", 1)[0] \
            if self.app.website_updates else None

        # Classify the source package type
        if not self.app.download_url:
            ending = ""
        elif self.app.download_url.endswith(".tar.gz"):
            ending = ".tar.gz"
        elif self.app.download_url.endswith(".tgz"):
            ending = ".tgz"
        elif self.app.download_url.endswith(".tar.bz2"):
            ending = ".tar.bz2"
        elif self.app.download_url.endswith(".zip"):
            ending = ".zip"
        elif self.app.download_url.endswith(".git"):
            ending = ".git"
        else:
            raise errors.InvalidConfigError(
                "Invalid source archive format in {0}".format(self.app.id))

        msg = "Running pre-installation..."
        uid, gid = users.get_system("http").uid, groups.get_system("http").gid
        nthread.update(Notification("info", "Webs", msg))

        # Call website type's pre-install hook
        self.pre_install(extra_vars)

        # If needs DB and user didn't select an engine, choose one for them
        if len(self.app.database_engines) > 1 \
                and extra_vars.get("dbengine", None):
            self.app.selected_dbengine = extra_vars.get("dbengine")
        if not getattr(self.app, "selected_dbengine", None)\
                and self.app.database_engines:
            self.app.selected_dbengine = self.app.database_engines[0]

        # Create DB and/or DB user as necessary
        if getattr(self.app, "selected_dbengine", None):
            msg = "Creating database..."
            nthread.update(Notification("info", "Webs", msg))
            mgr = databases.get_managers(self.app.selected_dbengine)
            if not mgr:
                estr = "No manager found for {0}"
                raise errors.InvalidConfigError(
                    estr.format(self.app.selected_dbengine))
            # Make sure DB daemon is running if it has one
            if not mgr.state:
                svc = services.get(mgr.meta.database_service)
                svc.restart()
            self.db = mgr.add_db(self.id)
            if hasattr(self.db, "path"):
                os.chmod(self.db.path, 0o660)
                os.chown(self.db.path, -1, gid)
            # If multiuser DB type, create user
            if mgr.meta.database_multiuser:
                dbpasswd = random_string(16)
                db_user = mgr.add_user(self.id, dbpasswd)
                db_user.chperm("grant", self.db)

        # Make sure the target directory exists, but is empty
        pkg_path = os.path.join("/tmp", self.id + ending)
        if os.path.isdir(self.path):
            shutil.rmtree(self.path)
        os.makedirs(self.path)

        # Download and extract the source repo / package
        msg = "Downloading website source..."
        nthread.update(Notification("info", "Webs", msg))
        if self.app.download_url and ending == ".git":
            g = git.Repo.clone_from(self.app.download_url, self.path)
            if hasattr(self.app, "download_at_tag"):
                g = git.Git(self.path)
                g.checkout(self.app.download_git_tag)
        elif self.app.download_url:
            download(self.app.download_url, file=pkg_path, crit=True)

            # Format extraction command according to type
            msg = "Extracting source..."
            nthread.update(Notification("info", "Webs", msg))
            if ending in [".tar.gz", ".tgz", ".tar.bz2"]:
                arch = tarfile.open(pkg_path, "r:gz")
                r = (x for x in arch.getnames() if re.match("^[^/]*$", x))
                toplvl = next(r, None)
                if not toplvl:
                    raise errors.OperationFailedError(
                        "Malformed source archive")
                arch.extractall(site_dir)
                os.rename(os.path.join(site_dir, toplvl), self.path)
            else:
                arch = zipfile.ZipFile(pkg_path)
                r = (x for x in arch.namelist() if re.match("^[^/]*/$", x))
                toplvl = next(r, None)
                if not toplvl:
                    raise errors.OperationFailedError(
                        "Malformed source archive")
                arch.extractall(site_dir)
                os.rename(os.path.join(site_dir, toplvl.rstrip("/")),
                          self.path)
            os.remove(pkg_path)

        # Set proper starting permissions on source directory
        os.chmod(self.path, 0o755)
        os.chown(self.path, uid, gid)
        for r, d, f in os.walk(self.path):
            for x in d:
                os.chmod(os.path.join(r, x), 0o755)
                os.chown(os.path.join(r, x), uid, gid)
            for x in f:
                os.chmod(os.path.join(r, x), 0o644)
                os.chown(os.path.join(r, x), uid, gid)

        # If there is a custom path for the data directory, set it up
        if getattr(self.app, "website_datapaths", None) \
                and extra_vars.get("datadir"):
            self.data_path = extra_vars["datadir"]
            if not os.path.exists(self.data_path):
                os.makedirs(self.data_path)
            os.chmod(self.data_path, 0o755)
            os.chown(self.data_path, uid, gid)
        elif hasattr(self, "website_default_data_subdir"):
            self.data_path = os.path.join(self.path,
                                          self.website_default_data_subdir)
        else:
            self.data_path = self.path

        # Create the nginx serverblock
        addtoblock = self.addtoblock or []
        if extra_vars.get("addtoblock"):
            addtoblock += nginx.loads(extra_vars.get("addtoblock"), False)
        default_index = "index." + ("php" if self.php else "html")
        if hasattr(self.app, "website_root"):
            webroot = os.path.join(self.path, self.app.website_root)
        else:
            webroot = self.path
        block = nginx.Conf()
        server = nginx.Server(
            nginx.Key("listen", str(self.port)),
            nginx.Key("listen", "[::]:" + str(self.port)),
            nginx.Key("server_name", self.domain), nginx.Key("root", webroot),
            nginx.Key(
                "index",
                getattr(self.app, "website_index", None) or default_index),
            nginx.Location("/.well-known/acme-challenge/",
                           nginx.Key("root", self.path)))
        if addtoblock:
            server.add(*[x for x in addtoblock])
        block.add(server)
        nginx.dumpf(block, os.path.join("/etc/nginx/sites-available", self.id))
        challenge_dir = os.path.join(self.path, ".well-known/acme-challenge/")
        if not os.path.exists(challenge_dir):
            os.makedirs(challenge_dir)

        # Create arkOS metadata file
        meta = configparser.SafeConfigParser()
        meta.add_section("website")
        meta.set("website", "id", self.id)
        meta.set("website", "app", self.app.id)
        meta.set("website", "ssl",
                 self.cert.id if getattr(self, "cert", None) else "None")
        meta.set("website", "version", self.version or "None")
        if getattr(self.app, "website_datapaths", None) \
                and self.data_path:
            meta.set("website", "data_path", self.data_path)
        meta.set("website", "dbengine", "")
        meta.set("website", "dbengine",
                 getattr(self.app, "selected_dbengine", ""))
        with open(os.path.join(self.path, ".arkos"), "w") as f:
            meta.write(f)

        # Call site type's post-installation hook
        msg = "Running post-installation. This may take a few minutes..."
        nthread.update(Notification("info", "Webs", msg))
        specialmsg = self.post_install(extra_vars, dbpasswd)

        # Cleanup and reload daemons
        msg = "Finishing..."
        nthread.update(Notification("info", "Webs", msg))
        self.installed = True
        storage.websites[self.id] = self
        if self.port == 80:
            cleanup_acme_dummy(self.domain)
        signals.emit("websites", "site_installed", self)
        if enable:
            self.nginx_enable()
        if enable and self.php:
            php.open_basedir("add", "/srv/http/")
            php_reload()

        msg = "{0} site installed successfully".format(self.app.name)
        nthread.complete(Notification("success", "Webs", msg))
        if specialmsg:
            return specialmsg
Esempio n. 26
0
def _request_acme_certificate(domain, webroot, nthread):
    nthread.title = "Requesting ACME certificate"
    signals.emit("certificates", "pre_add", id)
    domains = [domain]

    uid = users.get_system("http").uid
    gid = groups.get_system("ssl-cert").gid

    if webroot:
        webroot = os.path.join(webroot, ".well-known", "acme-challenge")

    acme_dir = config.get("certificates", "acme_dir")
    cert_dir = os.path.join(acme_dir, "certs", domain)
    cert_path = os.path.join(cert_dir, "cert.pem")
    key_path = os.path.join(cert_dir, "privkey.pem")

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

    if not webroot:
        sites = websites.get()
        for x in sites:
            if x.port in [80, 443] and x.domain == domain:
                webroot = x.add_acme_challenge()
                break
        else:
            webroot = websites.create_acme_dummy(domain)

    smsg = "Requesting certificate from Let's Encrypt CA..."
    nthread.update(Notification("info", "Certificates", smsg))
    agree_to_tos = None
    has_written_files = False
    while True:
        try:
            leclient.issue_certificate(
                domains,
                acme_dir,
                acme_server=config.get("certificates", "acme_server"),
                certificate_file=cert_path,
                private_key_file=key_path,
                agree_to_tos_url=agree_to_tos)
            break
        except leclient.NeedToAgreeToTOS as e:
            agree_to_tos = e.url
            continue
        except leclient.NeedToTakeAction as e:
            if not has_written_files:
                if not os.path.exists(webroot):
                    os.makedirs(webroot)
                os.chown(webroot, uid, gid)
                for x in e.actions:
                    fn = os.path.join(webroot, x.file_name)
                    with open(fn, 'w') as f:
                        f.write(x.contents)
                    os.chown(fn, uid, gid)
                has_written_files = True
                continue
            else:
                raise errors.InvalidConfigError(
                    "Requesting a certificate failed - it doesn't appear your "
                    "requested domain's DNS is pointing to your server, or "
                    "there was a port problem. Please check these things and "
                    "try again.")
        except leclient.WaitABit as e:
            while e.until_when > datetime.datetime.now():
                until = e.until_when - datetime.datetime.now()
                until_secs = int(round(until.total_seconds())) + 1
                if until_secs > 300:
                    raise errors.InvalidConfigError(
                        "Requesting a certificate failed - LE rate limiting "
                        "detected, for a period of more than five minutes. "
                        "Please try again later."
                    )
                nthread.update(
                    Notification(
                        "warning", "Certificates", "LE rate limiting detected."
                        " Will reattempt in {0} seconds".format(until_secs))
                    )
                time.sleep(until_secs)
            continue
        except leclient.InvalidDomainName:
            raise errors.InvalidConfigError(
                "Requesting a certificate failed - invalid domain name"
            )
        except leclient.RateLimited:
            raise errors.InvalidConfigError(
                "Requesting a certificate failed - LE is refusing to issue "
                "more certificates to you for this domain. Please choose "
                "another domain or try again another time."
            )

    os.chown(cert_path, -1, gid)
    os.chown(key_path, -1, gid)
    os.chmod(cert_path, 0o750)
    os.chmod(key_path, 0o750)

    with open(cert_path, "rb") as f:
        cert = x509.load_pem_x509_certificate(f.read(), default_backend())
    with open(key_path, "rb") as f:
        key = serialization.load_pem_private_key(
            f.read(), password=None, backend=default_backend()
        )
    sha1 = binascii.hexlify(cert.fingerprint(hashes.SHA1())).decode()
    md5 = binascii.hexlify(cert.fingerprint(hashes.MD5())).decode()
    sha1 = ":".join([sha1[i:i+2].upper() for i in range(0, len(sha1), 2)])
    md5 = ":".join([md5[i:i+2].upper() for i in range(0, len(md5), 2)])
    if isinstance(key.public_key(), rsa.RSAPublicKey):
        ktype = "RSA"
    elif isinstance(key.public_key(), dsa.DSAPublicKey):
        ktype = "DSA"
    elif isinstance(key.public_key(), ec.EllipticCurvePublicKey):
        ktype = "EC"
    else:
        ktype = "Unknown"
    ksize = key.key_size
    c = Certificate(domain, domain, cert_path, key_path, ktype, ksize,
                    [], cert.not_valid_after, sha1, md5, is_acme=True)
    storage.certificates[c.id] = c

    with open("/etc/cron.d/arkos-acme-renew", "a") as f:
        f.write("0 4 * * * systemctl reload nginx\n")
        fln = ("30 3 * * * free_tls_certificate {0} {1} {2} {3} {4} "
               ">> /var/log/acme-renew.log\n")
        f.write(fln.format(
            " ".join(domains), key_path, cert_path,
            webroot.split("/.well-known/acme-challenge")[0], acme_dir
        ))

    signals.emit("certificates", "post_add", c)
    msg = "Certificate issued successfully"
    nthread.complete(Notification("success", "Certificates", msg))
    return c
Esempio n. 27
0
 def update_samba(self, passwd=""):
     """Update Samba values in LDAP."""
     try:
         domain = conns.LDAP.search_s(self.rootdn, ldap.SCOPE_SUBTREE,
                                      "objectClass=sambaDomain", None)
     except ldap.NO_SUCH_OBJECT:
         domain = None
     if not domain:
         hostname = sysconfig.get_hostname().upper()
         sambaSID = "S-1-5-21-0-0-0"
         dldif = {
             "objectClass": [b"sambaDomain"],
             "sambaDomainName": [b(hostname)],
             "sambaSID": [b(sambaSID)],
             "sambaAlgorithmicRidBase": [b"1000"],
             "sambaNextUserRid": [b"1000"],
             "sambaMinPwdLength": [b"5"],
             "sambaPwdHistoryLength": [b"0"],
             "sambaLogonToChgPwd": [b"0"],
             "sambaMaxPwdAge": [b"-1"],
             "sambaMinPwdAge": [b"0"],
             "sambaLockoutDuration": [b"30"],
             "sambaLockoutObservationWindow": [b"30"],
             "sambaLockoutThreshold": [b"0"],
             "sambaForceLogoff": [b"-1"],
             "sambaRefuseMachinePwdChange": [b"0"],
             "sambaNextRid": [b(str(get_next_uid()))]
         }
         dldif = ldap.modlist.addModlist(dldif)
         try:
             conns.LDAP.add_s(
                 "sambaDomainName={0},{1}".format(hostname, self.rootdn),
                 dldif)
         except ldap.ALREADY_EXISTS:
             pass
     else:
         sambaSID = domain[0][1]["sambaSID"][0].decode()
         attrs = {"sambaNextRid": [b(str(get_next_uid()))]}
         dldif = ldap.modlist.modifyModlist(domain[0][1],
                                            attrs,
                                            ignore_oldexistent=1)
         conns.LDAP.modify_s(domain[0][0], dldif)
     if passwd:
         try:
             uldif = conns.LDAP.search_s(self.ldap_id, ldap.SCOPE_SUBTREE,
                                         "(objectClass=*)", None)
         except ldap.NO_SUCH_OBJECT:
             raise errors.InvalidConfigError("Users",
                                             "This user does not exist")
         uldif = uldif[0][1]
         attrs = {
             "objectClass": [
                 b"mailAccount", b"inetOrgPerson", b"posixAccount",
                 b"sambaSamAccount"
             ],
             "sambaSID": [b("{0}-{1}".format(sambaSID, self.uid))],
             "sambaAcctFlags": [b"[UX         ]"],
             "sambaNTPassword": [b(nthash.encrypt(passwd).upper())]
         }
         nldif = ldap.modlist.modifyModlist(uldif,
                                            attrs,
                                            ignore_oldexistent=1)
         conns.LDAP.modify_s(self.ldap_id, nldif)
Esempio n. 28
0
    def _install(self, extra_vars, enable, nthread):
        # Set metadata values
        site_dir = config.get("websites", "site_dir")
        path = (self.path or os.path.join(site_dir, self.id))
        self.path = path

        if os.path.isdir(self.path):
            shutil.rmtree(self.path)
        os.makedirs(self.path)

        # If extra data is passed in, set up the serverblock accordingly
        uwsgi_block = [
            nginx.Location(
                extra_vars.get("lregex", "/"),
                nginx.Key("{0}_pass".format(extra_vars.get("type")),
                          extra_vars.get("pass", "")),
                nginx.Key("include",
                          "{0}_params".format(extra_vars.get("type"))))
        ]
        default_block = [
            nginx.Location(extra_vars.get("lregex", "/"),
                           nginx.Key("proxy_pass", extra_vars.get("pass", "")),
                           nginx.Key("proxy_redirect", "off"),
                           nginx.Key("proxy_buffering", "off"),
                           nginx.Key("proxy_set_header", "Host $host"))
        ]
        if extra_vars:
            if not extra_vars.get("type") or not extra_vars.get("pass"):
                raise errors.InvalidConfigError(
                    "Must enter ReverseProxy type and location to pass to")
            elif extra_vars.get("type") in ["fastcgi", "uwsgi"]:
                self.block = uwsgi_block
            else:
                self.block = default_block
            if extra_vars.get("xrip"):
                self.block[0].add(
                    nginx.Key("proxy_set_header", "X-Real-IP $remote_addr"))
            if extra_vars.get("xff") == "1":
                xff_key = "X-Forwarded-For $proxy_add_x_forwarded_for"
                self.block[0].add(nginx.Key("proxy_set_header", xff_key))

        # Create the nginx serverblock and arkOS metadata files
        block = nginx.Conf()
        server = nginx.Server(
            nginx.Key("listen", self.port),
            nginx.Key("listen", "[::]:" + str(self.port)),
            nginx.Key("server_name", self.domain),
            nginx.Key("root", self.base_path or self.path),
            nginx.Location("/.well-known/acme-challenge/",
                           nginx.Key("root", self.path)))
        server.add(*[x for x in self.block])
        block.add(server)
        nginx.dumpf(block, os.path.join("/etc/nginx/sites-available", self.id))
        challenge_dir = os.path.join(self.path, ".well-known/acme-challenge/")
        if not os.path.exists(challenge_dir):
            os.makedirs(challenge_dir)
        meta = configparser.SafeConfigParser()
        ssl = self.cert.id if getattr(self, "cert", None) else "None"
        meta.add_section("website")
        meta.set("website", "id", self.id)
        meta.set("website", "app", self.app.id if self.app else "None")
        meta.set("website", "version", "None")
        meta.set("website", "ssl", ssl)
        with open(os.path.join(self.path, ".arkos"), "w") as f:
            meta.write(f)

        # Track port and reload daemon
        self.installed = True
        storage.websites[self.id] = self
        signals.emit("websites", "site_installed", self)
        self.nginx_enable()