コード例 #1
0
ファイル: tools.py プロジェクト: YunoHost/yunohost
def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False):
    """
    Main domain change tool

    Keyword argument:
        new_domain
        old_domain

    """
    if not old_domain:
        with open('/etc/yunohost/current_host', 'r') as f:
            old_domain = f.readline().rstrip()

        if not new_domain:
            return { 'current_main_domain': old_domain }

    if not new_domain:
        raise MoulinetteError(errno.EINVAL, m18n.n('new_domain_required'))
    if new_domain not in domain_list(auth)['domains']:
        domain_add(auth, new_domain)

    os.system('rm /etc/ssl/private/yunohost_key.pem')
    os.system('rm /etc/ssl/certs/yunohost_crt.pem')

    command_list = [
        'ln -s /etc/yunohost/certs/%s/key.pem /etc/ssl/private/yunohost_key.pem' % new_domain,
        'ln -s /etc/yunohost/certs/%s/crt.pem /etc/ssl/certs/yunohost_crt.pem'   % new_domain,
        'echo %s > /etc/yunohost/current_host' % new_domain,
    ]

    for command in command_list:
        if os.system(command) != 0:
            raise MoulinetteError(errno.EPERM,
                                  m18n.n('maindomain_change_failed'))

    if dyndns and len(new_domain.split('.')) >= 3:
        try:
            r = requests.get('https://dyndns.yunohost.org/domains')
        except requests.ConnectionError:
            pass
        else:
            dyndomains = json.loads(r.text)
            dyndomain  = '.'.join(new_domain.split('.')[1:])
            if dyndomain in dyndomains:
                dyndns_subscribe(domain=new_domain)

    try:
        with open('/etc/yunohost/installed', 'r') as f:
            service_regen_conf()
    except IOError: pass

    logger.success(m18n.n('maindomain_changed'))
コード例 #2
0
ファイル: app.py プロジェクト: opi/moulinette-yunohost
def app_makedefault(auth, app, domain=None):
    """
    Redirect domain root to an app

    Keyword argument:
        app
        domain

    """
    from yunohost.domain import domain_list

    if not _is_installed(app):
        raise MoulinetteError(errno.EINVAL, m18n.n('app_not_installed', app))

    with open(apps_setting_path + app +'/settings.yml') as f:
        app_settings = yaml.load(f)

    app_domain = app_settings['domain']
    app_path   = app_settings['path']

    if domain is None:
        domain = app_domain
    elif domain not in domain_list(auth)['domains']:
        raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))

    if '/' in app_map(raw=True)[domain]:
        raise MoulinetteError(errno.EEXIST,
                              m18n.n('app_location_already_used'))

    try:
        with open('/etc/ssowat/conf.json.persistent') as json_conf:
            ssowat_conf = json.loads(str(json_conf.read()))
    except IOError:
        ssowat_conf = {}

    if 'redirected_urls' not in ssowat_conf:
        ssowat_conf['redirected_urls'] = {}

    ssowat_conf['redirected_urls'][domain +'/'] = app_domain + app_path

    with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
        json.dump(ssowat_conf, f, sort_keys=True, indent=4)

    os.system('chmod 644 /etc/ssowat/conf.json.persistent')

    msignals.display(m18n.n('ssowat_conf_updated'), 'success')
コード例 #3
0
ファイル: app.py プロジェクト: opi/moulinette-yunohost
def app_checkurl(auth, url, app=None):
    """
    Check availability of a web path

    Keyword argument:
        url -- Url to check
        app -- Write domain & path to app settings for further checks

    """
    from yunohost.domain import domain_list

    if "https://" == url[:8]:
        url = url[8:]
    elif "http://" == url[:7]:
        url = url[7:]

    if url[-1:] != '/':
        url = url + '/'

    domain = url[:url.index('/')]
    path = url[url.index('/'):]

    if path[-1:] != '/':
        path = path + '/'

    apps_map = app_map(raw=True)

    if domain not in domain_list(auth)['domains']:
        raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))

    if domain in apps_map:
        if path in apps_map[domain]:
            raise MoulinetteError(errno.EINVAL, m18n.n('app_location_already_used'))
        for app_path, v in apps_map[domain].items():
            if app_path in path and app_path.count('/') < path.count('/'):
                raise MoulinetteError(errno.EPERM,
                                      m18n.n('app_location_install_failed'))

    if app is not None:
        app_setting(app, 'domain', value=domain)
        app_setting(app, 'path', value=path)
コード例 #4
0
ファイル: user.py プロジェクト: YunoHost/yunohost
def user_create(auth, username, firstname, lastname, mail, password,
        mailbox_quota=0):
    """
    Create user

    Keyword argument:
        firstname
        lastname
        username -- Must be unique
        mail -- Main mail address must be unique
        password
        mailbox_quota -- Mailbox size quota

    """
    import pwd
    from yunohost.domain import domain_list
    from yunohost.hook import hook_callback
    from yunohost.app import app_ssowatconf

    # Validate uniqueness of username and mail in LDAP
    auth.validate_uniqueness({
        'uid'       : username,
        'mail'      : mail
    })

    # Validate uniqueness of username in system users
    try:
        pwd.getpwnam(username)
    except KeyError:
        pass
    else:
        raise MoulinetteError(errno.EEXIST, m18n.n('system_username_exists'))

    # Check that the mail domain exists
    if mail[mail.find('@')+1:] not in domain_list(auth)['domains']:
        raise MoulinetteError(errno.EINVAL,
                              m18n.n('mail_domain_unknown',
                                     domain=mail[mail.find('@')+1:]))

    # Get random UID/GID
    uid_check = gid_check = 0
    while uid_check == 0 and gid_check == 0:
        uid = str(random.randint(200, 99999))
        uid_check = os.system("getent passwd %s" % uid)
        gid_check = os.system("getent group %s" % uid)

    # Adapt values for LDAP
    fullname = '%s %s' % (firstname, lastname)
    rdn = 'uid=%s,ou=users' % username
    char_set = string.ascii_uppercase + string.digits
    salt = ''.join(random.sample(char_set,8))
    salt = '$1$' + salt + '$'
    user_pwd = '{CRYPT}' + crypt.crypt(str(password), salt)
    attr_dict = {
        'objectClass'   : ['mailAccount', 'inetOrgPerson', 'posixAccount'],
        'givenName'     : firstname,
        'sn'            : lastname,
        'displayName'   : fullname,
        'cn'            : fullname,
        'uid'           : username,
        'mail'          : mail,
        'maildrop'      : username,
        'mailuserquota' : mailbox_quota,
        'userPassword'  : user_pwd,
        'gidNumber'     : uid,
        'uidNumber'     : uid,
        'homeDirectory' : '/home/' + username,
        'loginShell'    : '/bin/false'
    }

    # If it is the first user, add some aliases
    if not auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'):
        with open('/etc/yunohost/current_host') as f:
            main_domain = f.readline().rstrip()
        aliases = [
            'root@'+ main_domain,
            'admin@'+ main_domain,
            'webmaster@'+ main_domain,
            'postmaster@'+ main_domain,
        ]
        attr_dict['mail'] = [ attr_dict['mail'] ] + aliases

        # If exists, remove the redirection from the SSO
        try:
            with open('/etc/ssowat/conf.json.persistent') as json_conf:
                ssowat_conf = json.loads(str(json_conf.read()))

            if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']:
                del ssowat_conf['redirected_urls']['/']

            with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
                json.dump(ssowat_conf, f, sort_keys=True, indent=4)

        except IOError: pass


    if auth.add(rdn, attr_dict):
        # Invalidate passwd to take user creation into account
        subprocess.call(['nscd', '-i', 'passwd'])

        # Update SFTP user group
        memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid']
        memberlist.append(username)
        if auth.update('cn=sftpusers,ou=groups', { 'memberUid': memberlist }):
            try:
                # Attempt to create user home folder
                subprocess.check_call(
                    ['su', '-', username, '-c', "''"])
            except subprocess.CalledProcessError:
                if not os.path.isdir('/home/{0}'.format(username)):
                    logger.warning(m18n.n('user_home_creation_failed'),
                                   exc_info=1)
            app_ssowatconf(auth)
            #TODO: Send a welcome mail to user
            logger.success(m18n.n('user_created'))
            hook_callback('post_user_create',
                          args=[username, mail, password, firstname, lastname])

            return { 'fullname' : fullname, 'username' : username, 'mail' : mail }

    raise MoulinetteError(169, m18n.n('user_creation_failed'))
コード例 #5
0
ファイル: user.py プロジェクト: YunoHost/yunohost
def user_update(auth, username, firstname=None, lastname=None, mail=None,
        change_password=None, add_mailforward=None, remove_mailforward=None,
        add_mailalias=None, remove_mailalias=None, mailbox_quota=None):
    """
    Update user informations

    Keyword argument:
        lastname
        mail
        firstname
        add_mailalias -- Mail aliases to add
        remove_mailforward -- Mailforward addresses to remove
        username -- Username of user to update
        add_mailforward -- Mailforward addresses to add
        change_password -- New password to set
        remove_mailalias -- Mail aliases to remove

    """
    from yunohost.domain import domain_list
    from yunohost.app import app_ssowatconf

    attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
    new_attr_dict = {}
    domains = domain_list(auth)['domains']

    # Populate user informations
    result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch)
    if not result:
        raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
    user = result[0]

    # Get modifications from arguments
    if firstname:
        new_attr_dict['givenName'] = firstname # TODO: Validate
        new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + user['sn'][0]

    if lastname:
        new_attr_dict['sn'] = lastname # TODO: Validate
        new_attr_dict['cn'] = new_attr_dict['displayName'] = user['givenName'][0] + ' ' + lastname

    if lastname and firstname:
        new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + lastname

    if change_password:
        char_set = string.ascii_uppercase + string.digits
        salt = ''.join(random.sample(char_set,8))
        salt = '$1$' + salt + '$'
        new_attr_dict['userPassword'] = '******' + crypt.crypt(str(change_password), salt)

    if mail:
        auth.validate_uniqueness({ 'mail': mail })
        if mail[mail.find('@')+1:] not in domains:
            raise MoulinetteError(errno.EINVAL,
                                  m18n.n('mail_domain_unknown',
                                          domain=mail[mail.find('@')+1:]))
        del user['mail'][0]
        new_attr_dict['mail'] = [mail] + user['mail']

    if add_mailalias:
        if not isinstance(add_mailalias, list):
            add_mailalias = [ add_mailalias ]
        for mail in add_mailalias:
            auth.validate_uniqueness({ 'mail': mail })
            if mail[mail.find('@')+1:] not in domains:
                raise MoulinetteError(errno.EINVAL,
                                      m18n.n('mail_domain_unknown',
                                             domain=mail[mail.find('@')+1:]))
            user['mail'].append(mail)
        new_attr_dict['mail'] = user['mail']

    if remove_mailalias:
        if not isinstance(remove_mailalias, list):
            remove_mailalias = [ remove_mailalias ]
        for mail in remove_mailalias:
            if len(user['mail']) > 1 and mail in user['mail'][1:]:
                user['mail'].remove(mail)
            else:
                raise MoulinetteError(errno.EINVAL,
                                      m18n.n('mail_alias_remove_failed', mail=mail))
        new_attr_dict['mail'] = user['mail']

    if add_mailforward:
        if not isinstance(add_mailforward, list):
            add_mailforward = [ add_mailforward ]
        for mail in add_mailforward:
            if mail in user['maildrop'][1:]:
                continue
            user['maildrop'].append(mail)
        new_attr_dict['maildrop'] = user['maildrop']

    if remove_mailforward:
        if not isinstance(remove_mailforward, list):
            remove_mailforward = [ remove_mailforward ]
        for mail in remove_mailforward:
            if len(user['maildrop']) > 1 and mail in user['maildrop'][1:]:
                user['maildrop'].remove(mail)
            else:
                raise MoulinetteError(errno.EINVAL,
                                      m18n.n('mail_forward_remove_failed', mail=mail))
        new_attr_dict['maildrop'] = user['maildrop']

    if mailbox_quota is not None:
        new_attr_dict['mailuserquota'] = mailbox_quota

    if auth.update('uid=%s,ou=users' % username, new_attr_dict):
       logger.success(m18n.n('user_updated'))
       app_ssowatconf(auth)
       return user_info(auth, username)
    else:
       raise MoulinetteError(169, m18n.n('user_update_failed'))
コード例 #6
0
def user_create(
    operation_logger,
    username,
    firstname,
    lastname,
    domain,
    password,
    mailbox_quota="0",
    mail=None,
):

    from yunohost.domain import domain_list, _get_maindomain
    from yunohost.hook import hook_callback
    from yunohost.utils.password import assert_password_is_strong_enough
    from yunohost.utils.ldap import _get_ldap_interface

    # Ensure sufficiently complex password
    assert_password_is_strong_enough("user", password)

    if mail is not None:
        logger.warning(
            "Packagers ! Using --mail in 'yunohost user create' is deprecated ... please use --domain instead."
        )
        domain = mail.split("@")[-1]

    # Validate domain used for email address/xmpp account
    if domain is None:
        if msettings.get("interface") == "api":
            raise YunohostValidationError(
                "Invalid usage, you should specify a domain argument")
        else:
            # On affiche les differents domaines possibles
            msignals.display(m18n.n("domains_available"))
            for domain in domain_list()["domains"]:
                msignals.display("- {}".format(domain))

            maindomain = _get_maindomain()
            domain = msignals.prompt(
                m18n.n("ask_user_domain") + " (default: %s)" % maindomain)
            if not domain:
                domain = maindomain

    # Check that the domain exists
    if domain not in domain_list()["domains"]:
        raise YunohostValidationError("domain_name_unknown", domain=domain)

    mail = username + "@" + domain
    ldap = _get_ldap_interface()

    if username in user_list()["users"]:
        raise YunohostValidationError("user_already_exists", user=username)

    # Validate uniqueness of username and mail in LDAP
    try:
        ldap.validate_uniqueness({
            "uid": username,
            "mail": mail,
            "cn": username
        })
    except Exception as e:
        raise YunohostValidationError("user_creation_failed",
                                      user=username,
                                      error=e)

    # Validate uniqueness of username in system users
    all_existing_usernames = {x.pw_name for x in pwd.getpwall()}
    if username in all_existing_usernames:
        raise YunohostValidationError("system_username_exists")

    main_domain = _get_maindomain()
    aliases = [
        "root@" + main_domain,
        "admin@" + main_domain,
        "webmaster@" + main_domain,
        "postmaster@" + main_domain,
        "abuse@" + main_domain,
    ]

    if mail in aliases:
        raise YunohostValidationError("mail_unavailable")

    operation_logger.start()

    # Get random UID/GID
    all_uid = {str(x.pw_uid) for x in pwd.getpwall()}
    all_gid = {str(x.gr_gid) for x in grp.getgrall()}

    uid_guid_found = False
    while not uid_guid_found:
        # LXC uid number is limited to 65536 by default
        uid = str(random.randint(1001, 65000))
        uid_guid_found = uid not in all_uid and uid not in all_gid

    # Adapt values for LDAP
    fullname = "%s %s" % (firstname, lastname)

    attr_dict = {
        "objectClass": [
            "mailAccount",
            "inetOrgPerson",
            "posixAccount",
            "userPermissionYnh",
        ],
        "givenName": [firstname],
        "sn": [lastname],
        "displayName": [fullname],
        "cn": [fullname],
        "uid": [username],
        "mail":
        mail,  # NOTE: this one seems to be already a list
        "maildrop": [username],
        "mailuserquota": [mailbox_quota],
        "userPassword": [_hash_user_password(password)],
        "gidNumber": [uid],
        "uidNumber": [uid],
        "homeDirectory": ["/home/" + username],
        "loginShell": ["/bin/bash"],
    }

    # If it is the first user, add some aliases
    if not ldap.search(base="ou=users,dc=yunohost,dc=org", filter="uid=*"):
        attr_dict["mail"] = [attr_dict["mail"]] + aliases

    try:
        ldap.add("uid=%s,ou=users" % username, attr_dict)
    except Exception as e:
        raise YunohostError("user_creation_failed", user=username, error=e)

    # Invalidate passwd and group to take user and group creation into account
    subprocess.call(["nscd", "-i", "passwd"])
    subprocess.call(["nscd", "-i", "group"])

    try:
        # Attempt to create user home folder
        subprocess.check_call(["mkhomedir_helper", username])
    except subprocess.CalledProcessError:
        if not os.path.isdir("/home/{0}".format(username)):
            logger.warning(m18n.n("user_home_creation_failed"), exc_info=1)

    try:
        subprocess.check_call(
            ["setfacl", "-m", "g:all_users:---",
             "/home/%s" % username])
    except subprocess.CalledProcessError:
        logger.warning("Failed to protect /home/%s" % username, exc_info=1)

    # Create group for user and add to group 'all_users'
    user_group_create(groupname=username,
                      gid=uid,
                      primary_group=True,
                      sync_perm=False)
    user_group_update(groupname="all_users",
                      add=username,
                      force=True,
                      sync_perm=True)

    # Trigger post_user_create hooks
    env_dict = {
        "YNH_USER_USERNAME": username,
        "YNH_USER_MAIL": mail,
        "YNH_USER_PASSWORD": password,
        "YNH_USER_FIRSTNAME": firstname,
        "YNH_USER_LASTNAME": lastname,
    }

    hook_callback("post_user_create", args=[username, mail], env=env_dict)

    # TODO: Send a welcome mail to user
    logger.success(m18n.n("user_created"))

    return {"fullname": fullname, "username": username, "mail": mail}
コード例 #7
0
def user_update(
    operation_logger,
    username,
    firstname=None,
    lastname=None,
    mail=None,
    change_password=None,
    add_mailforward=None,
    remove_mailforward=None,
    add_mailalias=None,
    remove_mailalias=None,
    mailbox_quota=None,
):
    """
    Update user informations

    Keyword argument:
        lastname
        mail
        firstname
        add_mailalias -- Mail aliases to add
        remove_mailforward -- Mailforward addresses to remove
        username -- Username of user to update
        add_mailforward -- Mailforward addresses to add
        change_password -- New password to set
        remove_mailalias -- Mail aliases to remove

    """
    from yunohost.domain import domain_list, _get_maindomain
    from yunohost.app import app_ssowatconf
    from yunohost.utils.password import assert_password_is_strong_enough
    from yunohost.utils.ldap import _get_ldap_interface
    from yunohost.hook import hook_callback

    domains = domain_list()["domains"]

    # Populate user informations
    ldap = _get_ldap_interface()
    attrs_to_fetch = ["givenName", "sn", "mail", "maildrop"]
    result = ldap.search(
        base="ou=users,dc=yunohost,dc=org",
        filter="uid=" + username,
        attrs=attrs_to_fetch,
    )
    if not result:
        raise YunohostValidationError("user_unknown", user=username)
    user = result[0]
    env_dict = {"YNH_USER_USERNAME": username}

    # Get modifications from arguments
    new_attr_dict = {}
    if firstname:
        new_attr_dict["givenName"] = [firstname]  # TODO: Validate
        new_attr_dict["cn"] = new_attr_dict["displayName"] = [
            firstname + " " + user["sn"][0]
        ]
        env_dict["YNH_USER_FIRSTNAME"] = firstname

    if lastname:
        new_attr_dict["sn"] = [lastname]  # TODO: Validate
        new_attr_dict["cn"] = new_attr_dict["displayName"] = [
            user["givenName"][0] + " " + lastname
        ]
        env_dict["YNH_USER_LASTNAME"] = lastname

    if lastname and firstname:
        new_attr_dict["cn"] = new_attr_dict["displayName"] = [
            firstname + " " + lastname
        ]

    # change_password is None if user_update is not called to change the password
    if change_password is not None:
        # when in the cli interface if the option to change the password is called
        # without a specified value, change_password will be set to the const 0.
        # In this case we prompt for the new password.
        if msettings.get("interface") == "cli" and not change_password:
            change_password = msignals.prompt(m18n.n("ask_password"), True,
                                              True)
        # Ensure sufficiently complex password
        assert_password_is_strong_enough("user", change_password)

        new_attr_dict["userPassword"] = [_hash_user_password(change_password)]
        env_dict["YNH_USER_PASSWORD"] = change_password

    if mail:
        main_domain = _get_maindomain()
        aliases = [
            "root@" + main_domain,
            "admin@" + main_domain,
            "webmaster@" + main_domain,
            "postmaster@" + main_domain,
        ]
        try:
            ldap.validate_uniqueness({"mail": mail})
        except Exception as e:
            raise YunohostValidationError("user_update_failed",
                                          user=username,
                                          error=e)
        if mail[mail.find("@") + 1:] not in domains:
            raise YunohostValidationError("mail_domain_unknown",
                                          domain=mail[mail.find("@") + 1:])
        if mail in aliases:
            raise YunohostValidationError("mail_unavailable")

        del user["mail"][0]
        new_attr_dict["mail"] = [mail] + user["mail"]

    if add_mailalias:
        if not isinstance(add_mailalias, list):
            add_mailalias = [add_mailalias]
        for mail in add_mailalias:
            try:
                ldap.validate_uniqueness({"mail": mail})
            except Exception as e:
                raise YunohostValidationError("user_update_failed",
                                              user=username,
                                              error=e)
            if mail[mail.find("@") + 1:] not in domains:
                raise YunohostValidationError("mail_domain_unknown",
                                              domain=mail[mail.find("@") + 1:])
            user["mail"].append(mail)
        new_attr_dict["mail"] = user["mail"]

    if remove_mailalias:
        if not isinstance(remove_mailalias, list):
            remove_mailalias = [remove_mailalias]
        for mail in remove_mailalias:
            if len(user["mail"]) > 1 and mail in user["mail"][1:]:
                user["mail"].remove(mail)
            else:
                raise YunohostValidationError("mail_alias_remove_failed",
                                              mail=mail)
        new_attr_dict["mail"] = user["mail"]

    if "mail" in new_attr_dict:
        env_dict["YNH_USER_MAILS"] = ",".join(new_attr_dict["mail"])

    if add_mailforward:
        if not isinstance(add_mailforward, list):
            add_mailforward = [add_mailforward]
        for mail in add_mailforward:
            if mail in user["maildrop"][1:]:
                continue
            user["maildrop"].append(mail)
        new_attr_dict["maildrop"] = user["maildrop"]

    if remove_mailforward:
        if not isinstance(remove_mailforward, list):
            remove_mailforward = [remove_mailforward]
        for mail in remove_mailforward:
            if len(user["maildrop"]) > 1 and mail in user["maildrop"][1:]:
                user["maildrop"].remove(mail)
            else:
                raise YunohostValidationError("mail_forward_remove_failed",
                                              mail=mail)
        new_attr_dict["maildrop"] = user["maildrop"]

    if "maildrop" in new_attr_dict:
        env_dict["YNH_USER_MAILFORWARDS"] = ",".join(new_attr_dict["maildrop"])

    if mailbox_quota is not None:
        new_attr_dict["mailuserquota"] = [mailbox_quota]
        env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota

    operation_logger.start()

    try:
        ldap.update("uid=%s,ou=users" % username, new_attr_dict)
    except Exception as e:
        raise YunohostError("user_update_failed", user=username, error=e)

    # Trigger post_user_update hooks
    hook_callback("post_user_update", env=env_dict)

    logger.success(m18n.n("user_updated"))
    app_ssowatconf()
    return user_info(username)
コード例 #8
0
ファイル: tools.py プロジェクト: YunoHost/yunohost
def tools_diagnosis(auth, private=False):
    """
    Return global info about current yunohost instance to help debugging

    """
    diagnosis = OrderedDict();

    # Debian release
    try:
        with open('/etc/debian_version', 'r') as f:
            debian_version = f.read().rstrip()
    except IOError as e:
        logger.warning(m18n.n('diagnosis_debian_version_error', error=format(e)), exc_info=1)
    else:
        diagnosis['host'] = "Debian %s" % debian_version

    # Kernel version
    try:
        with open('/proc/sys/kernel/osrelease', 'r') as f:
            kernel_version = f.read().rstrip()
    except IOError as e:
        logger.warning(m18n.n('diagnosis_kernel_version_error', error=format(e)), exc_info=1)
    else:
        diagnosis['kernel'] = kernel_version

    # Packages version
    diagnosis['packages'] = ynh_packages_version()

    # Server basic monitoring
    diagnosis['system'] = OrderedDict()
    try:
        disks = monitor_disk(units=['filesystem'], human_readable=True)
    except MoulinetteError as e:
        logger.warning(m18n.n('diagnosis_monitor_disk_error', error=format(e)), exc_info=1)
    else:
        diagnosis['system']['disks'] = {}
        for disk in disks:
            diagnosis['system']['disks'][disk] = 'Mounted on %s, %s (%s free)' % (
                disks[disk]['mnt_point'],
                disks[disk]['size'],
                disks[disk]['avail']
            )

    try:
        system = monitor_system(units=['cpu', 'memory'], human_readable=True)
    except MoulinetteError as e:
        logger.warning(m18n.n('diagnosis_monitor_system_error', error=format(e)), exc_info=1)
    else:
        diagnosis['system']['memory'] = {
            'ram' : '%s (%s free)' % (system['memory']['ram']['total'], system['memory']['ram']['free']),
            'swap' : '%s (%s free)' % (system['memory']['swap']['total'], system['memory']['swap']['free']),
        }

    # Services status
    services = service_status()
    diagnosis['services'] = {}
    for service in services:
        diagnosis['services'][service] = "%s (%s)" % (services[service]['status'], services[service]['loaded'])

    # YNH Applications
    try:
        applications = app_list()['apps']
    except MoulinetteError as e:
        diagnosis['applications'] = m18n.n('diagnosis_no_apps')
    else:
        diagnosis['applications'] = {}
        for application in applications:
            if application['installed']:
                diagnosis['applications'][application['id']] = application['label'] if application['label'] else application['name']

    # Private data
    if private:
        diagnosis['private'] = OrderedDict()
        # Public IP
        diagnosis['private']['public_ip'] = {}
        try:
            diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4)
        except MoulinetteError as e:
            pass
        try:
            diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6)
        except MoulinetteError as e:
            pass

        # Domains
        diagnosis['private']['domains'] = domain_list(auth)['domains']

    return diagnosis
コード例 #9
0
def _validate_and_sanitize_permission_url(url, app_base_path, app):
    """
    Check and normalize the urls passed for all permissions
    Also check that the Regex is valid

    As documented in the 'ynh_permission_create' helper:

    If provided, 'url' is assumed to be relative to the app domain/path if they
    start with '/'.  For example:
       /                             -> domain.tld/app
       /admin                        -> domain.tld/app/admin
       domain.tld/app/api            -> domain.tld/app/api
       domain.tld                    -> domain.tld

    'url' can be later treated as a regex if it starts with "re:".
    For example:
       re:/api/[A-Z]*$               -> domain.tld/app/api/[A-Z]*$
       re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$

    We can also have less-trivial regexes like:
        re:^/api/.*|/scripts/api.js$
    """

    from yunohost.domain import domain_list
    from yunohost.app import _assert_no_conflicting_apps

    domains = domain_list()["domains"]

    #
    # Regexes
    #

    def validate_regex(regex):
        if "%" in regex:
            logger.warning(
                "/!\\ Packagers! You are probably using a lua regex. You should use a PCRE regex instead."
            )
            return

        try:
            re.compile(regex)
        except Exception:
            raise YunohostError("invalid_regex", regex=regex)

    if url.startswith("re:"):

        # regex without domain
        # we check for the first char after 're:'
        if url[3] in ["/", "^", "\\"]:
            validate_regex(url[3:])
            return url

        # regex with domain

        if "/" not in url:
            raise YunohostError("regex_with_only_domain")
        domain, path = url[3:].split("/", 1)
        path = "/" + path

        if domain.replace("%", "").replace("\\", "") not in domains:
            raise YunohostError("domain_name_unknown", domain=domain)

        validate_regex(path)

        return "re:" + domain + path

    #
    # "Regular" URIs
    #

    def split_domain_path(url):
        url = url.strip("/")
        (domain, path) = url.split("/", 1) if "/" in url else (url, "/")
        if path != "/":
            path = "/" + path
        return (domain, path)

    # uris without domain
    if url.startswith("/"):
        # if url is for example /admin/
        # we want sanitized_url to be: /admin
        # and (domain, path) to be   : (domain.tld, /app/admin)
        sanitized_url = "/" + url.strip("/")
        domain, path = split_domain_path(app_base_path)
        path = "/" + path.strip("/") + sanitized_url

    # uris with domain
    else:
        # if url is for example domain.tld/wat/
        # we want sanitized_url to be: domain.tld/wat
        # and (domain, path) to be   : (domain.tld, /wat)
        domain, path = split_domain_path(url)
        sanitized_url = domain + path

        if domain not in domains:
            raise YunohostError("domain_name_unknown", domain=domain)

    _assert_no_conflicting_apps(domain, path, ignore_app=app)

    return sanitized_url
コード例 #10
0
ファイル: test_permission.py プロジェクト: trogeat/yunohost
def setup_function(function):
    clean_user_groups_permission()

    global maindomain
    global other_domains
    maindomain = _get_maindomain()

    markers = {
        m.name: {
            "args": m.args,
            "kwargs": m.kwargs
        }
        for m in function.__dict__.get("pytestmark", [])
    }

    if "other_domains" in markers:
        other_domains = [
            "domain_%s.dev" % string.ascii_lowercase[number]
            for number in range(markers["other_domains"]["kwargs"]["number"])
        ]
        for domain in other_domains:
            if domain not in domain_list()["domains"]:
                domain_add(domain)

    # Dirty patch of DNS resolution. Force the DNS to 127.0.0.1 address even if dnsmasq have the public address.
    # Mainly used for 'can_access_webpage' function
    dns_cache = {(maindomain, 443, 0, 1): [(2, 1, 6, "", ("127.0.0.1", 443))]}
    for domain in other_domains:
        dns_cache[(domain, 443, 0, 1)] = [(2, 1, 6, "", ("127.0.0.1", 443))]

    def new_getaddrinfo(*args):
        try:
            return dns_cache[args]
        except KeyError:
            res = prv_getaddrinfo(*args)
            dns_cache[args] = res
            return res

    socket.getaddrinfo = new_getaddrinfo

    user_create("alice", "Alice", "White", maindomain, dummy_password)
    user_create("bob", "Bob", "Snow", maindomain, dummy_password)
    _permission_create_with_dummy_app(
        permission="wiki.main",
        url="/",
        additional_urls=["/whatever", "/idontnow"],
        auth_header=False,
        label="Wiki",
        show_tile=True,
        allowed=["all_users"],
        protected=False,
        sync_perm=False,
        domain=maindomain,
        path="/wiki",
    )
    _permission_create_with_dummy_app(
        permission="blog.main",
        url="/",
        auth_header=True,
        show_tile=False,
        protected=False,
        sync_perm=False,
        allowed=["alice"],
        domain=maindomain,
        path="/blog",
    )
    _permission_create_with_dummy_app(permission="blog.api",
                                      allowed=["visitors"],
                                      protected=True,
                                      sync_perm=True)
コード例 #11
0
ファイル: 21-web.py プロジェクト: trogeat/yunohost
    def run(self):

        all_domains = domain_list()["domains"]
        domains_to_check = []
        for domain in all_domains:

            # If the diagnosis location ain't defined, can't do diagnosis,
            # probably because nginx conf manually modified...
            nginx_conf = "/etc/nginx/conf.d/%s.conf" % domain
            if ".well-known/ynh-diagnosis/" not in read_file(nginx_conf):
                yield dict(
                    meta={"domain": domain},
                    status="WARNING",
                    summary="diagnosis_http_nginx_conf_not_up_to_date",
                    details=[
                        "diagnosis_http_nginx_conf_not_up_to_date_details"
                    ],
                )
            else:
                domains_to_check.append(domain)

        self.nonce = "".join(
            random.choice("0123456789abcedf") for i in range(16))
        os.system("rm -rf /tmp/.well-known/ynh-diagnosis/")
        os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/")
        os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % self.nonce)

        if not domains_to_check:
            return

        # To perform hairpinning test, we gotta make sure that port forwarding
        # is working and therefore we'll do it only if at least one ipv4 domain
        # works.
        self.do_hairpinning_test = False

        ipversions = []
        ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {}
        if ipv4.get("status") == "SUCCESS":
            ipversions.append(4)

        # To be discussed: we could also make this check dependent on the
        # existence of an AAAA record...
        ipv6 = Diagnoser.get_cached_report("ip", item={"test": "ipv6"}) or {}
        if ipv6.get("status") == "SUCCESS":
            ipversions.append(6)

        for item in self.test_http(domains_to_check, ipversions):
            yield item

        # If at least one domain is correctly exposed to the outside,
        # attempt to diagnose hairpinning situations. On network with
        # hairpinning issues, the server may be correctly exposed on the
        # outside, but from the outside, it will be as if the port forwarding
        # was not configured... Hence, calling for example
        # "curl --head the.global.ip" will simply timeout...
        if self.do_hairpinning_test:
            global_ipv4 = ipv4.get("data", {}).get("global", None)
            if global_ipv4:
                try:
                    requests.head("http://" + global_ipv4, timeout=5)
                except requests.exceptions.Timeout:
                    yield dict(
                        meta={"test": "hairpinning"},
                        status="WARNING",
                        summary="diagnosis_http_hairpinning_issue",
                        details=["diagnosis_http_hairpinning_issue_details"],
                    )
                except Exception:
                    # Well I dunno what to do if that's another exception
                    # type... That'll most probably *not* be an hairpinning
                    # issue but something else super weird ...
                    pass
コード例 #12
0
ファイル: user.py プロジェクト: Josue-T/moulinette-yunohost
def user_create(auth, username, firstname, lastname, mail, password):
    """
    Create user

    Keyword argument:
        firstname
        lastname
        username -- Must be unique
        mail -- Main mail address must be unique
        password

    """
    from yunohost.domain import domain_list
    from yunohost.hook import hook_callback

    # Validate password length
    if len(password) < 4:
        raise MoulinetteError(errno.EINVAL, m18n.n('password_too_short'))

    auth.validate_uniqueness({
        'uid'       : username,
        'mail'      : mail
    })

    if mail[mail.find('@')+1:] not in domain_list(auth)['domains']:
        raise MoulinetteError(errno.EINVAL,
                              m18n.n('mail_domain_unknown')
                                      % mail[mail.find('@')+1:])

    # Get random UID/GID
    uid_check = gid_check = 0
    while uid_check == 0 and gid_check == 0:
        uid = str(random.randint(200, 99999))
        uid_check = os.system("getent passwd %s" % uid)
        gid_check = os.system("getent group %s" % uid)

    # Adapt values for LDAP
    fullname = '%s %s' % (firstname, lastname)
    rdn = 'uid=%s,ou=users' % username
    char_set = string.ascii_uppercase + string.digits
    salt = ''.join(random.sample(char_set,8))
    salt = '$1$' + salt + '$'
    pwd = '{CRYPT}' + crypt.crypt(str(password), salt)
    attr_dict = {
        'objectClass'   : ['mailAccount', 'inetOrgPerson', 'posixAccount'],
        'givenName'     : firstname,
        'sn'            : lastname,
        'displayName'   : fullname,
        'cn'            : fullname,
        'uid'           : username,
        'mail'          : mail,
        'maildrop'      : username,
        'userPassword'  : pwd,
        'gidNumber'     : uid,
        'uidNumber'     : uid,
        'homeDirectory' : '/home/' + username,
        'loginShell'    : '/bin/false'
    }

    # If it is the first user, add some aliases
    if not auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'):
        with open('/etc/yunohost/current_host') as f:
            main_domain = f.readline().rstrip()
        aliases = [
            'root@'+ main_domain,
            'admin@'+ main_domain,
            'webmaster@'+ main_domain,
            'postmaster@'+ main_domain,
        ]
        attr_dict['mail'] = [ attr_dict['mail'] ] + aliases

        # If exists, remove the redirection from the SSO
        try:
            with open('/etc/ssowat/conf.json.persistent') as json_conf:
                ssowat_conf = json.loads(str(json_conf.read()))

            if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']:
                del ssowat_conf['redirected_urls']['/']

            with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
                json.dump(ssowat_conf, f, sort_keys=True, indent=4)

        except IOError: pass

    
    if auth.add(rdn, attr_dict):
        # Update SFTP user group
        memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid']
        memberlist.append(username)
        if auth.update('cn=sftpusers,ou=groups', { 'memberUid': memberlist }):
            os.system("su - %s -c ''" % username)
            os.system('yunohost app ssowatconf > /dev/null 2>&1')
            #TODO: Send a welcome mail to user
            msignals.display(m18n.n('user_created'), 'success')
            hook_callback('post_user_create', [username, mail, password, firstname, lastname])

            return { 'fullname' : fullname, 'username' : username, 'mail' : mail }

    raise MoulinetteError(169, m18n.n('user_creation_failed'))
コード例 #13
0
ファイル: tools.py プロジェクト: opi/moulinette-yunohost
def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False):
    """
    Main domain change tool

    Keyword argument:
        new_domain
        old_domain

    """
    from yunohost.domain import domain_add, domain_list
    from yunohost.dyndns import dyndns_subscribe

    if not old_domain:
        with open('/etc/yunohost/current_host', 'r') as f:
            old_domain = f.readline().rstrip()

        if not new_domain:
            return { 'current_main_domain': old_domain }

    if not new_domain:
        raise MoulinetteError(errno.EINVAL, m18n.n('new_domain_required'))
    if new_domain not in domain_list(auth)['domains']:
        domain_add(auth, new_domain)

    config_files = [
        '/etc/postfix/main.cf',
        '/etc/metronome/metronome.cfg.lua',
        '/etc/dovecot/dovecot.conf',
        '/usr/share/yunohost/yunohost-config/others/startup',
        '/etc/amavis/conf.d/05-node_id',
        '/etc/amavis/conf.d/50-user'
    ]

    config_dir = []

    for dir in config_dir:
        for file in os.listdir(dir):
            config_files.append(dir + '/' + file)

    for file in config_files:
        with open(file, "r") as sources:
            lines = sources.readlines()
        with open(file, "w") as sources:
            for line in lines:
                sources.write(re.sub(r''+ old_domain +'', new_domain, line))

    ## Update DNS zone file for old and new domains
    main_subdomains = ['pubsub', 'muc', 'vjud']
    try:
        with open('/var/lib/bind/%s.zone' % old_domain, 'r') as f:
            old_zone = f.read()
    except IOError:
        pass
    else:
        # Remove unneeded subdomains entries
        for sub in main_subdomains:
            old_zone = re.sub(
                r'^({sub}.{domain}.|{sub})[\ \t]+(IN).*$[\n]?'.format(
                    sub=sub, domain=old_domain),
                '', old_zone, 1, re.MULTILINE)
        with open('/var/lib/bind/%s.zone' % old_domain, 'w') as f:
            f.write(old_zone)
    try:
        with open('/var/lib/bind/%s.zone' % new_domain, 'r') as f:
            new_zone = f.read()
    except IOError:
        msignals.display(m18n.n('domain_zone_not_found', new_domain), 'warning')
    else:
        # Add main subdomains entries
        for sub in main_subdomains:
            new_zone += '{sub}  IN  CNAME   {domain}.\n'.format(
                sub=sub, domain=new_domain)
        with open('/var/lib/bind/%s.zone' % new_domain, 'w') as f:
            f.write(new_zone)

    os.system('rm /etc/ssl/private/yunohost_key.pem')
    os.system('rm /etc/ssl/certs/yunohost_crt.pem')

    command_list = [
        'rm -f /etc/nginx/conf.d/%s.d/yunohost_local.conf' % old_domain,
        'cp /usr/share/yunohost/yunohost-config/nginx/yunohost_local.conf /etc/nginx/conf.d/%s.d/' % new_domain,
        'ln -s /etc/yunohost/certs/%s/key.pem /etc/ssl/private/yunohost_key.pem' % new_domain,
        'ln -s /etc/yunohost/certs/%s/crt.pem /etc/ssl/certs/yunohost_crt.pem'   % new_domain,
        'echo %s > /etc/yunohost/current_host' % new_domain,
        'service metronome restart',
        'service postfix restart',
        'service dovecot restart',
        'service amavis restart',
        'service nginx restart',
    ]

    for command in command_list:
        if os.system(command) != 0:
            raise MoulinetteError(errno.EPERM,
                                  m18n.n('maindomain_change_failed'))

    if dyndns and len(new_domain.split('.')) >= 3:
        try:
            r = requests.get('https://dyndns.yunohost.org/domains')
        except ConnectionError:
            pass
        else:
            dyndomains = json.loads(r.text)
            dyndomain  = '.'.join(new_domain.split('.')[1:])
            if dyndomain in dyndomains:
                dyndns_subscribe(domain=new_domain)

    msignals.display(m18n.n('maindomain_changed'), 'success')
コード例 #14
0
ファイル: user.py プロジェクト: grenagit/yunohost
def user_create(operation_logger,
                username,
                firstname,
                lastname,
                domain,
                password,
                mailbox_quota="0",
                mail=None):

    from yunohost.domain import domain_list, _get_maindomain
    from yunohost.hook import hook_callback
    from yunohost.utils.password import assert_password_is_strong_enough
    from yunohost.utils.ldap import _get_ldap_interface

    # Ensure sufficiently complex password
    assert_password_is_strong_enough("user", password)

    if mail is not None:
        logger.warning(
            "Packagers ! Using --mail in 'yunohost user create' is deprecated ... please use --domain instead."
        )
        domain = mail.split("@")[-1]

    # Validate domain used for email address/xmpp account
    if domain is None:
        if msettings.get('interface') == 'api':
            raise YunohostError('Invalide usage, specify domain argument')
        else:
            # On affiche les differents domaines possibles
            msignals.display(m18n.n('domains_available'))
            for domain in domain_list()['domains']:
                msignals.display("- {}".format(domain))

            maindomain = _get_maindomain()
            domain = msignals.prompt(
                m18n.n('ask_user_domain') + ' (default: %s)' % maindomain)
            if not domain:
                domain = maindomain

    # Check that the domain exists
    if domain not in domain_list()['domains']:
        raise YunohostError('domain_name_unknown', domain=domain)

    mail = username + '@' + domain
    ldap = _get_ldap_interface()

    if username in user_list()["users"]:
        raise YunohostError("user_already_exists", user=username)

    # Validate uniqueness of username and mail in LDAP
    try:
        ldap.validate_uniqueness({
            'uid': username,
            'mail': mail,
            'cn': username
        })
    except Exception as e:
        raise YunohostError('user_creation_failed', user=username, error=e)

    # Validate uniqueness of username in system users
    all_existing_usernames = {x.pw_name for x in pwd.getpwall()}
    if username in all_existing_usernames:
        raise YunohostError('system_username_exists')

    main_domain = _get_maindomain()
    aliases = [
        'root@' + main_domain,
        'admin@' + main_domain,
        'webmaster@' + main_domain,
        'postmaster@' + main_domain,
        'abuse@' + main_domain,
    ]

    if mail in aliases:
        raise YunohostError('mail_unavailable')

    operation_logger.start()

    # Get random UID/GID
    all_uid = {str(x.pw_uid) for x in pwd.getpwall()}
    all_gid = {str(x.gr_gid) for x in grp.getgrall()}

    uid_guid_found = False
    while not uid_guid_found:
        # LXC uid number is limited to 65536 by default
        uid = str(random.randint(1001, 65000))
        uid_guid_found = uid not in all_uid and uid not in all_gid

    # Adapt values for LDAP
    fullname = '%s %s' % (firstname, lastname)

    attr_dict = {
        'objectClass':
        ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh'],
        'givenName': [firstname],
        'sn': [lastname],
        'displayName': [fullname],
        'cn': [fullname],
        'uid': [username],
        'mail':
        mail,  # NOTE: this one seems to be already a list
        'maildrop': [username],
        'mailuserquota': [mailbox_quota],
        'userPassword': [_hash_user_password(password)],
        'gidNumber': [uid],
        'uidNumber': [uid],
        'homeDirectory': ['/home/' + username],
        'loginShell': ['/bin/false']
    }

    # If it is the first user, add some aliases
    if not ldap.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'):
        attr_dict['mail'] = [attr_dict['mail']] + aliases

    try:
        ldap.add('uid=%s,ou=users' % username, attr_dict)
    except Exception as e:
        raise YunohostError('user_creation_failed', user=username, error=e)

    # Invalidate passwd and group to take user and group creation into account
    subprocess.call(['nscd', '-i', 'passwd'])
    subprocess.call(['nscd', '-i', 'group'])

    try:
        # Attempt to create user home folder
        subprocess.check_call(["mkhomedir_helper", username])
    except subprocess.CalledProcessError:
        if not os.path.isdir('/home/{0}'.format(username)):
            logger.warning(m18n.n('user_home_creation_failed'), exc_info=1)

    # Create group for user and add to group 'all_users'
    user_group_create(groupname=username,
                      gid=uid,
                      primary_group=True,
                      sync_perm=False)
    user_group_update(groupname='all_users',
                      add=username,
                      force=True,
                      sync_perm=True)

    # Trigger post_user_create hooks
    env_dict = {
        "YNH_USER_USERNAME": username,
        "YNH_USER_MAIL": mail,
        "YNH_USER_PASSWORD": password,
        "YNH_USER_FIRSTNAME": firstname,
        "YNH_USER_LASTNAME": lastname
    }

    hook_callback('post_user_create', args=[username, mail], env=env_dict)

    # TODO: Send a welcome mail to user
    logger.success(m18n.n('user_created'))

    return {'fullname': fullname, 'username': username, 'mail': mail}
コード例 #15
0
ファイル: tools.py プロジェクト: zimo2001/yunohost
def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False):
    """
    Main domain change tool

    Keyword argument:
        new_domain
        old_domain

    """
    from yunohost.domain import domain_add, domain_list
    from yunohost.dyndns import dyndns_subscribe

    if not old_domain:
        with open('/etc/yunohost/current_host', 'r') as f:
            old_domain = f.readline().rstrip()

        if not new_domain:
            return { 'current_main_domain': old_domain }

    if not new_domain:
        raise MoulinetteError(errno.EINVAL, m18n.n('new_domain_required'))
    if new_domain not in domain_list(auth)['domains']:
        domain_add(auth, new_domain)

    config_files = [
        '/etc/postfix/main.cf',
        '/etc/metronome/metronome.cfg.lua',
        '/etc/dovecot/dovecot.conf',
        '/usr/share/yunohost/yunohost-config/others/startup',
        '/etc/amavis/conf.d/05-node_id',
        '/etc/amavis/conf.d/50-user'
    ]

    config_dir = []

    for dir in config_dir:
        for file in os.listdir(dir):
            config_files.append(dir + '/' + file)

    for file in config_files:
        with open(file, "r") as sources:
            lines = sources.readlines()
        with open(file, "w") as sources:
            for line in lines:
                sources.write(re.sub(r''+ old_domain +'', new_domain, line))

    ## Update DNS zone file for old and new domains
    main_subdomains = ['pubsub', 'muc', 'vjud']
    try:
        with open('/var/lib/bind/%s.zone' % old_domain, 'r') as f:
            old_zone = f.read()
    except IOError:
        pass
    else:
        # Remove unneeded subdomains entries
        for sub in main_subdomains:
            old_zone = re.sub(
                r'^({sub}.{domain}.|{sub})[\ \t]+(IN).*$[\n]?'.format(
                    sub=sub, domain=old_domain),
                '', old_zone, 1, re.MULTILINE)
        with open('/var/lib/bind/%s.zone' % old_domain, 'w') as f:
            f.write(old_zone)
    try:
        with open('/var/lib/bind/%s.zone' % new_domain, 'r') as f:
            new_zone = f.read()
    except IOError:
        msignals.display(m18n.n('domain_zone_not_found', new_domain), 'warning')
    else:
        # Add main subdomains entries
        for sub in main_subdomains:
            new_zone += '{sub}  IN  CNAME   {domain}.\n'.format(
                sub=sub, domain=new_domain)
        with open('/var/lib/bind/%s.zone' % new_domain, 'w') as f:
            f.write(new_zone)

    os.system('rm /etc/ssl/private/yunohost_key.pem')
    os.system('rm /etc/ssl/certs/yunohost_crt.pem')

    command_list = [
        'rm -f /etc/nginx/conf.d/%s.d/yunohost_local.conf' % old_domain,
        'cp /usr/share/yunohost/yunohost-config/nginx/yunohost_local.conf /etc/nginx/conf.d/%s.d/' % new_domain,
        'ln -s /etc/yunohost/certs/%s/key.pem /etc/ssl/private/yunohost_key.pem' % new_domain,
        'ln -s /etc/yunohost/certs/%s/crt.pem /etc/ssl/certs/yunohost_crt.pem'   % new_domain,
        'echo %s > /etc/yunohost/current_host' % new_domain,
        'service metronome restart',
        'service postfix restart',
        'service dovecot restart',
        'service amavis restart',
        'service nginx restart',
    ]

    for command in command_list:
        if os.system(command) != 0:
            raise MoulinetteError(errno.EPERM,
                                  m18n.n('maindomain_change_failed'))

    if dyndns and len(new_domain.split('.')) >= 3:
        try:
            r = requests.get('https://dyndns.yunohost.org/domains')
        except ConnectionError:
            pass
        else:
            dyndomains = json.loads(r.text)
            dyndomain  = '.'.join(new_domain.split('.')[1:])
            if dyndomain in dyndomains:
                dyndns_subscribe(domain=new_domain)

    msignals.display(m18n.n('maindomain_changed'), 'success')
コード例 #16
0
ファイル: app.py プロジェクト: opi/moulinette-yunohost
def app_ssowatconf(auth):
    """
    Regenerate SSOwat configuration file


    """
    from yunohost.domain import domain_list
    from yunohost.user import user_list

    with open('/etc/yunohost/current_host', 'r') as f:
        main_domain = f.readline().rstrip()

    domains = domain_list(auth)['domains']

    users = {}
    for user in user_list(auth)['users']:
        users[user['username']] = app_map(user=user['username'])

    skipped_urls = []
    skipped_regex = []
    unprotected_urls = []
    unprotected_regex = []
    protected_urls = []
    protected_regex = []
    redirected_regex = { main_domain +'/yunohost[\/]?$': 'https://'+ main_domain +'/yunohost/sso/' }
    redirected_urls ={}
    apps = {}
    try:
        apps_list = app_list()['apps']
    except:
        apps_list = []

    for app in apps_list:
        if _is_installed(app['id']):
            with open(apps_setting_path + app['id'] +'/settings.yml') as f:
                app_settings = yaml.load(f)
                if 'skipped_uris' in app_settings:
                    for item in app_settings['skipped_uris'].split(','):
                        if item[-1:] == '/':
                            item = item[:-1]
                        skipped_urls.append(app_settings['domain'] + app_settings['path'][:-1] + item)
                if 'skipped_regex' in app_settings:
                    for item in app_settings['skipped_regex'].split(','):
                        skipped_regex.append(item)
                if 'unprotected_uris' in app_settings:
                    for item in app_settings['unprotected_uris'].split(','):
                        if item[-1:] == '/':
                            item = item[:-1]
                        unprotected_urls.append(app_settings['domain'] + app_settings['path'][:-1] + item)
                if 'unprotected_regex' in app_settings:
                    for item in app_settings['unprotected_regex'].split(','):
                        unprotected_regex.append(item)
                if 'protected_uris' in app_settings:
                    for item in app_settings['protected_uris'].split(','):
                        if item[-1:] == '/':
                            item = item[:-1]
                        protected_urls.append(app_settings['domain'] + app_settings['path'][:-1] + item)
                if 'redirected_urls' in app_settings:
                    redirected_urls.update(app_settings['redirected_urls'])
                if 'redirected_regex' in app_settings:
                    redirected_regex.update(app_settings['redirected_regex'])

    for domain in domains:
        skipped_urls.extend(['/yunohost/admin', '/yunohost/api'])

    conf_dict = {
        'portal_domain': main_domain,
        'portal_path': '/yunohost/sso/',
        'additional_headers': {
            'Auth-User': '******',
            'Remote-User': '******',
            'Name': 'cn',
            'Email': 'mail'
        },
        'domains': domains,
        'skipped_urls': skipped_urls,
        'unprotected_urls': unprotected_urls,
        'protected_urls': protected_urls,
        'skipped_regex': skipped_regex,
        'unprotected_regex': unprotected_regex,
        'protected_regex': protected_regex,
        'redirected_urls': redirected_urls,
        'redirected_regex': redirected_regex,
        'users': users,
    }

    with open('/etc/ssowat/conf.json', 'w+') as f:
        json.dump(conf_dict, f, sort_keys=True, indent=4)

    msignals.display(m18n.n('ssowat_conf_generated'), 'success')
コード例 #17
0
def regen_conf(operation_logger,
               names=[],
               with_diff=False,
               force=False,
               dry_run=False,
               list_pending=False):
    """
    Regenerate the configuration file(s)

    Keyword argument:
        names -- Categories to regenerate configuration of
        with_diff -- Show differences in case of configuration changes
        force -- Override all manual modifications in configuration files
        dry_run -- Show what would have been regenerated
        list_pending -- List pending configuration files and exit

    """

    result = {}

    # Return the list of pending conf
    if list_pending:
        pending_conf = _get_pending_conf(names)

        if not with_diff:
            return pending_conf

        for category, conf_files in pending_conf.items():
            for system_path, pending_path in conf_files.items():

                pending_conf[category][system_path] = {
                    'pending_conf': pending_path,
                    'diff': _get_files_diff(system_path, pending_path, True),
                }

        return pending_conf

    if not dry_run:
        operation_logger.related_to = [('configuration', x) for x in names]
        if not names:
            operation_logger.name_parameter_override = 'all'
        elif len(names) != 1:
            operation_logger.name_parameter_override = str(
                len(operation_logger.related_to)) + '_categories'
        operation_logger.start()

    # Clean pending conf directory
    if os.path.isdir(PENDING_CONF_DIR):
        if not names:
            shutil.rmtree(PENDING_CONF_DIR, ignore_errors=True)
        else:
            for name in names:
                shutil.rmtree(os.path.join(PENDING_CONF_DIR, name),
                              ignore_errors=True)
    else:
        filesystem.mkdir(PENDING_CONF_DIR, 0o755, True)

    # Format common hooks arguments
    common_args = [1 if force else 0, 1 if dry_run else 0]

    # Execute hooks for pre-regen
    pre_args = [
        'pre',
    ] + common_args

    def _pre_call(name, priority, path, args):
        # create the pending conf directory for the category
        category_pending_path = os.path.join(PENDING_CONF_DIR, name)
        filesystem.mkdir(category_pending_path, 0o755, True, uid='root')

        # return the arguments to pass to the script
        return pre_args + [
            category_pending_path,
        ]

    ssh_explicitly_specified = isinstance(names, list) and "ssh" in names

    # By default, we regen everything
    if not names:
        names = hook_list('conf_regen', list_by='name',
                          show_info=False)['hooks']

    # Dirty hack for legacy code : avoid attempting to regen the conf for
    # glances because it got removed ...  This is only needed *once*
    # during the upgrade from 3.7 to 3.8 because Yunohost will attempt to
    # regen glance's conf *before* it gets automatically removed from
    # services.yml (which will happens only during the regen-conf of
    # 'yunohost', so at the very end of the regen-conf cycle) Anyway,
    # this can be safely removed once we're in >= 4.0
    if "glances" in names:
        names.remove("glances")

    # [Optimization] We compute and feed the domain list to the conf regen
    # hooks to avoid having to call "yunohost domain list" so many times which
    # ends up in wasted time (about 3~5 seconds per call on a RPi2)
    from yunohost.domain import domain_list
    env = {}
    # Well we can only do domain_list() if postinstall is done ...
    # ... but hooks that effectively need the domain list are only
    # called only after the 'installed' flag is set so that's all good,
    # though kinda tight-coupled to the postinstall logic :s
    if os.path.exists("/etc/yunohost/installed"):
        env["YNH_DOMAINS"] = " ".join(domain_list()["domains"])

    pre_result = hook_callback('conf_regen',
                               names,
                               pre_callback=_pre_call,
                               env=env)

    # Keep only the hook names with at least one success
    names = [
        hook for hook, infos in pre_result.items()
        if any(result["state"] == "succeed" for result in infos.values())
    ]

    # FIXME : what do in case of partial success/failure ...
    if not names:
        ret_failed = [
            hook for hook, infos in pre_result.items()
            if any(result["state"] == "failed" for result in infos.values())
        ]
        raise YunohostError('regenconf_failed',
                            categories=', '.join(ret_failed))

    # Set the processing method
    _regen = _process_regen_conf if not dry_run else lambda *a, **k: True

    operation_logger.related_to = []

    # Iterate over categories and process pending conf
    for category, conf_files in _get_pending_conf(names).items():
        if not dry_run:
            operation_logger.related_to.append(('configuration', category))

        if dry_run:
            logger.debug(
                m18n.n('regenconf_pending_applying', category=category))
        else:
            logger.debug(
                m18n.n('regenconf_dry_pending_applying', category=category))

        conf_hashes = _get_conf_hashes(category)
        succeed_regen = {}
        failed_regen = {}

        # Here we are doing some weird legacy shit
        # The thing is, on some very old or specific setup, the sshd_config file
        # was absolutely not managed by the regenconf ...
        # But we now want to make sure that this file is managed.
        # However, we don't want to overwrite a specific custom sshd_config
        # which may make the admin unhappy ...
        # So : if the hash for this file does not exists, we set the hash as the
        # hash of the pending configuration ...
        # That way, the file will later appear as manually modified.
        sshd_config = "/etc/ssh/sshd_config"
        if category == "ssh" and sshd_config not in conf_hashes and sshd_config in conf_files:
            conf_hashes[sshd_config] = _calculate_hash(conf_files[sshd_config])
            _update_conf_hashes(category, conf_hashes)

        # Consider the following scenario:
        # - you add a domain foo.bar
        # - the regen-conf creates file /etc/dnsmasq.d/foo.bar
        # - the admin manually *deletes* /etc/dnsmasq.d/foo.bar
        # - the file is now understood as manually deleted because there's the old file hash in regenconf.yml
        #
        # ... so far so good, that's the expected behavior.
        #
        # But then:
        # - the admin remove domain foo.bar entirely
        # - but now the hash for /etc/dnsmasq.d/foo.bar is *still* in
        # regenconf.yml and and the file is still flagged as manually
        # modified/deleted... And the user cannot even do anything about it
        # except removing the hash in regenconf.yml...
        #
        # Expected behavior: it should forget about that
        # hash because dnsmasq's regen-conf doesn't say anything about what's
        # the state of that file so it should assume that it should be deleted.
        #
        # - then the admin tries to *re-add* foo.bar !
        # - ... but because the file is still flagged as manually modified
        # the regen-conf refuses to re-create the file.
        #
        # Excepted behavior : the regen-conf should have forgot about the hash
        # from earlier and this wouldnt happen.
        # ------
        # conf_files contain files explicitly set by the current regen conf run
        # conf_hashes contain all files known from the past runs
        # we compare these to get the list of stale hashes and flag the file as
        # "should be removed"
        stale_files = set(conf_hashes.keys()) - set(conf_files.keys())
        stale_files_with_non_empty_hash = [
            f for f in stale_files if conf_hashes.get(f)
        ]
        for f in stale_files_with_non_empty_hash:
            conf_files[f] = None
        # </> End discussion about stale file hashes

        force_update_hashes_for_this_category = False

        for system_path, pending_path in conf_files.items():
            logger.debug("processing pending conf '%s' to system conf '%s'",
                         pending_path, system_path)
            conf_status = None
            regenerated = False

            # Get the diff between files
            conf_diff = _get_files_diff(system_path, pending_path,
                                        True) if with_diff else None

            # Check if the conf must be removed
            to_remove = True if pending_path and os.path.getsize(
                pending_path) == 0 else False

            # Retrieve and calculate hashes
            system_hash = _calculate_hash(system_path)
            saved_hash = conf_hashes.get(system_path, None)
            new_hash = None if to_remove else _calculate_hash(pending_path)

            # -> configuration was previously managed by yunohost but should now
            # be removed / unmanaged
            if system_path in stale_files_with_non_empty_hash:
                # File is already deleted, so let's just silently forget about this hash entirely
                if not system_hash:
                    logger.debug("> forgetting about stale file/hash")
                    conf_hashes[system_path] = None
                    conf_status = 'forget-about-it'
                    regenerated = True
                # Otherwise there's still a file on the system but it's not managed by
                # Yunohost anymore... But if user requested --force we shall
                # force-erase it
                elif force:
                    logger.debug("> force-remove stale file")
                    regenerated = _regen(system_path)
                    conf_status = 'force-removed'
                # Otherwise, flag the file as manually modified
                else:
                    logger.warning(
                        m18n.n('regenconf_file_manually_modified',
                               conf=system_path))
                    conf_status = 'modified'

            # -> system conf does not exists
            elif not system_hash:
                if to_remove:
                    logger.debug("> system conf is already removed")
                    os.remove(pending_path)
                    conf_hashes[system_path] = None
                    conf_status = 'forget-about-it'
                    force_update_hashes_for_this_category = True
                    continue
                elif not saved_hash or force:
                    if force:
                        logger.debug("> system conf has been manually removed")
                        conf_status = 'force-created'
                    else:
                        logger.debug("> system conf does not exist yet")
                        conf_status = 'created'
                    regenerated = _regen(system_path, pending_path, save=False)
                else:
                    logger.info(
                        m18n.n('regenconf_file_manually_removed',
                               conf=system_path))
                    conf_status = 'removed'

            # -> system conf is not managed yet
            elif not saved_hash:
                logger.debug("> system conf is not managed yet")
                if system_hash == new_hash:
                    logger.debug("> no changes to system conf has been made")
                    conf_status = 'managed'
                    regenerated = True
                elif not to_remove:
                    # If the conf exist but is not managed yet, and is not to be removed,
                    # we assume that it is safe to regen it, since the file is backuped
                    # anyway (by default in _regen), as long as we warn the user
                    # appropriately.
                    logger.info(
                        m18n.n('regenconf_now_managed_by_yunohost',
                               conf=system_path,
                               category=category))
                    regenerated = _regen(system_path, pending_path)
                    conf_status = 'new'
                elif force:
                    regenerated = _regen(system_path)
                    conf_status = 'force-removed'
                else:
                    logger.info(
                        m18n.n('regenconf_file_kept_back',
                               conf=system_path,
                               category=category))
                    conf_status = 'unmanaged'

            # -> system conf has not been manually modified
            elif system_hash == saved_hash:
                if to_remove:
                    regenerated = _regen(system_path)
                    conf_status = 'removed'
                elif system_hash != new_hash:
                    regenerated = _regen(system_path, pending_path)
                    conf_status = 'updated'
                else:
                    logger.debug("> system conf is already up-to-date")
                    os.remove(pending_path)
                    continue

            else:
                logger.debug("> system conf has been manually modified")
                if system_hash == new_hash:
                    logger.debug("> new conf is as current system conf")
                    conf_status = 'managed'
                    regenerated = True
                elif force and system_path == sshd_config and not ssh_explicitly_specified:
                    logger.warning(
                        m18n.n('regenconf_need_to_explicitly_specify_ssh'))
                    conf_status = 'modified'
                elif force:
                    regenerated = _regen(system_path, pending_path)
                    conf_status = 'force-updated'
                else:
                    logger.warning(
                        m18n.n('regenconf_file_manually_modified',
                               conf=system_path))
                    conf_status = 'modified'

            # Store the result
            conf_result = {'status': conf_status}
            if conf_diff is not None:
                conf_result['diff'] = conf_diff
            if regenerated:
                succeed_regen[system_path] = conf_result
                conf_hashes[system_path] = new_hash
                if pending_path and os.path.isfile(pending_path):
                    os.remove(pending_path)
            else:
                failed_regen[system_path] = conf_result

        # Check for category conf changes
        if not succeed_regen and not failed_regen:
            logger.debug(m18n.n('regenconf_up_to_date', category=category))
            continue
        elif not failed_regen:
            if not dry_run:
                logger.success(m18n.n('regenconf_updated', category=category))
            else:
                logger.success(
                    m18n.n('regenconf_would_be_updated', category=category))

        if (succeed_regen
                or force_update_hashes_for_this_category) and not dry_run:
            _update_conf_hashes(category, conf_hashes)

        # Append the category results
        result[category] = {'applied': succeed_regen, 'pending': failed_regen}

    # Return in case of dry run
    if dry_run:
        return result

    # Execute hooks for post-regen
    post_args = [
        'post',
    ] + common_args

    def _pre_call(name, priority, path, args):
        # append coma-separated applied changes for the category
        if name in result and result[name]['applied']:
            regen_conf_files = ','.join(result[name]['applied'].keys())
        else:
            regen_conf_files = ''
        return post_args + [
            regen_conf_files,
        ]

    hook_callback('conf_regen', names, pre_callback=_pre_call, env=env)

    operation_logger.success()

    return result