Exemple #1
0
def domain_add(auth, domain, dyndns=False):
    """
    Create a custom domain

    Keyword argument:
        domain -- Domain name to add
        dyndns -- Subscribe to DynDNS

    """
    attr_dict = {'objectClass': ['mailDomain', 'top']}
    try:
        ip = str(urlopen('http://ip.yunohost.org').read())
    except IOError:
        ip = "127.0.0.1"
    now = datetime.datetime.now()
    timestamp = str(now.year) + str(now.month) + str(now.day)

    if domain in domain_list(auth)['domains']:
        raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists'))

    # DynDNS domain
    if dyndns:
        if len(domain.split('.')) < 3:
            raise MoulinetteError(errno.EINVAL,
                                  m18n.n('domain_dyndns_invalid'))
        import requests
        from yunohost.dyndns import dyndns_subscribe

        try:
            r = requests.get('https://dyndns.yunohost.org/domains')
        except ConnectionError:
            pass
        else:
            dyndomains = json.loads(r.text)
            dyndomain = '.'.join(domain.split('.')[1:])
            if dyndomain in dyndomains:
                if os.path.exists('/etc/cron.d/yunohost-dyndns'):
                    raise MoulinetteError(
                        errno.EPERM,
                        m18n.n('domain_dyndns_already_subscribed'))
                dyndns_subscribe(domain=domain)
            else:
                raise MoulinetteError(errno.EINVAL,
                                      m18n.n('domain_dyndns_root_unknown'))

    try:
        # Commands
        ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
        ssl_domain_path = '/etc/yunohost/certs/%s' % domain
        with open('%s/serial' % ssl_dir, 'r') as f:
            serial = f.readline().rstrip()
        try:
            os.listdir(ssl_domain_path)
        except OSError:
            os.makedirs(ssl_domain_path)

        command_list = [
            'cp %s/openssl.cnf %s' % (ssl_dir, ssl_domain_path),
            'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' %
            (domain, ssl_domain_path),
            'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch'
            % (ssl_domain_path, ssl_dir, ssl_dir),
            'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch'
            % (ssl_domain_path, ssl_dir, ssl_dir),
            'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' %
            ssl_domain_path,
            'cp %s/certs/yunohost_key.pem    %s/key.pem' %
            (ssl_dir, ssl_domain_path),
            'cp %s/newcerts/%s.pem %s/crt.pem' %
            (ssl_dir, serial, ssl_domain_path),
            'chmod 755 %s' % ssl_domain_path,
            'chmod 640 %s/key.pem' % ssl_domain_path,
            'chmod 640 %s/crt.pem' % ssl_domain_path,
            'chmod 600 %s/openssl.cnf' % ssl_domain_path,
            'chown root:metronome %s/key.pem' % ssl_domain_path,
            'chown root:metronome %s/crt.pem' % ssl_domain_path
        ]

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

        try:
            auth.validate_uniqueness({'virtualdomain': domain})
        except MoulinetteError:
            raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists'))

        attr_dict['virtualdomain'] = domain

        dnsmasq_config_path = '/etc/dnsmasq.d'
        try:
            os.listdir(dnsmasq_config_path)
        except OSError:
            msignals.display(m18n.n('dnsmasq_isnt_installed'), 'warning')
            os.makedirs(dnsmasq_config_path)

        try:
            with open('%s/%s' % (dnsmasq_config_path, domain)) as f:
                pass
        except IOError as e:
            zone_lines = [
                'address=/%s/%s' % (domain, ip),
                'txt-record=%s,"v=spf1 mx a -all"' % domain,
                'mx-host=%s,%s,5' % (domain, domain),
                'srv-host=_xmpp-client._tcp.%s,%s,5222,0,5' % (domain, domain),
                'srv-host=_xmpp-server._tcp.%s,%s,5269,0,5' % (domain, domain),
                'srv-host=_jabber._tcp.%s,%s,5269,0,5' % (domain, domain),
            ]
            with open('%s/%s' % (dnsmasq_config_path, domain), 'w') as zone:
                for line in zone_lines:
                    zone.write(line + '\n')
            os.system('service dnsmasq restart')

        else:
            msignals.display(m18n.n('domain_zone_exists'), 'warning')

        # XMPP
        try:
            with open('/etc/metronome/conf.d/%s.cfg.lua' % domain) as f:
                pass
        except IOError as e:
            conf_lines = [
                'VirtualHost "%s"' % domain,
                '  ssl = {',
                '        key = "%s/key.pem";' % ssl_domain_path,
                '        certificate = "%s/crt.pem";' % ssl_domain_path,
                '  }',
                '  authentication = "ldap2"',
                '  ldap = {',
                '     hostname      = "localhost",',
                '     user = {',
                '       basedn        = "ou=users,dc=yunohost,dc=org",',
                '       filter        = "(&(objectClass=posixAccount)(mail=*@%s))",'
                % domain,
                '       usernamefield = "mail",',
                '       namefield     = "cn",',
                '       },',
                '  }',
            ]
            with open('/etc/metronome/conf.d/%s.cfg.lua' % domain,
                      'w') as conf:
                for line in conf_lines:
                    conf.write(line + '\n')

        os.system('mkdir -p /var/lib/metronome/%s/pep' %
                  domain.replace('.', '%2e'))
        os.system('chown -R metronome: /var/lib/metronome/')
        os.system('chown -R metronome: /etc/metronome/conf.d/')
        os.system('service metronome restart')

        # Nginx
        os.system(
            'cp /usr/share/yunohost/yunohost-config/nginx/template.conf /etc/nginx/conf.d/%s.conf'
            % domain)
        os.system('mkdir /etc/nginx/conf.d/%s.d/' % domain)
        os.system('sed -i s/yunohost.org/%s/g /etc/nginx/conf.d/%s.conf' %
                  (domain, domain))
        os.system('service nginx reload')

        if not auth.add('virtualdomain=%s,ou=domains' % domain, attr_dict):
            raise MoulinetteError(errno.EIO, m18n.n('domain_creation_failed'))

        os.system('yunohost app ssowatconf > /dev/null 2>&1')
    except:
        # Force domain removal silently
        try:
            domain_remove(auth, domain, True)
        except:
            pass
        raise

    msignals.display(m18n.n('domain_created'), 'success')
Exemple #2
0
def dyndns_subscribe(subscribe_host="dyndns.yunohost.org",
                     domain=None,
                     key=None):
    """
    Subscribe to a DynDNS service

    Keyword argument:
        domain -- Full domain to subscribe with
        key -- Public DNS key
        subscribe_host -- Dynette HTTP API to subscribe to

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

    # Verify if domain is available
    try:
        if requests.get('https://%s/test/%s' %
                        (subscribe_host, domain)).status_code != 200:
            raise MoulinetteError(errno.EEXIST, m18n.n('dyndns_unavailable'))
    except requests.ConnectionError:
        raise MoulinetteError(errno.ENETUNREACH,
                              m18n.n('no_internet_connection'))

    if key is None:
        if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0:
            os.makedirs('/etc/yunohost/dyndns')

            logger.info(m18n.n('dyndns_key_generating'))

            os.system(
                'cd /etc/yunohost/dyndns && '
                'dnssec-keygen -a hmac-md5 -b 128 -r /dev/urandom -n USER %s' %
                domain)
            os.system(
                'chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private'
            )

        key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0]
        with open(key_file) as f:
            key = f.readline().strip().split(' ')[-1]

    # Send subscription
    try:
        r = requests.post('https://%s/key/%s' %
                          (subscribe_host, base64.b64encode(key)),
                          data={'subdomain': domain})
    except requests.ConnectionError:
        raise MoulinetteError(errno.ENETUNREACH,
                              m18n.n('no_internet_connection'))
    if r.status_code != 201:
        try:
            error = json.loads(r.text)['error']
        except:
            error = "Server error"
        raise MoulinetteError(
            errno.EPERM, m18n.n('dyndns_registration_failed', error=error))

    logger.success(m18n.n('dyndns_registered'))

    dyndns_installcron()
Exemple #3
0
def user_info(auth, username):
    """
    Get user informations

    Keyword argument:
        username -- Username or mail to get informations

    """
    user_attrs = [
        'cn', 'mail', 'uid', 'maildrop', 'givenName', 'sn', 'mailuserquota'
    ]

    if len(username.split('@')) is 2:
        filter = 'mail=' + username
    else:
        filter = 'uid=' + username

    result = auth.search('ou=users,dc=yunohost,dc=org', filter, user_attrs)

    if result:
        user = result[0]
    else:
        raise MoulinetteError(errno.EINVAL,
                              m18n.n('user_unknown', user=username))

    result_dict = {
        'username': user['uid'][0],
        'fullname': user['cn'][0],
        'firstname': user['givenName'][0],
        'lastname': user['sn'][0],
        'mail': user['mail'][0]
    }

    if len(user['mail']) > 1:
        result_dict['mail-aliases'] = user['mail'][1:]

    if len(user['maildrop']) > 1:
        result_dict['mail-forward'] = user['maildrop'][1:]

    if 'mailuserquota' in user:
        userquota = user['mailuserquota'][0]

        if isinstance(userquota, int):
            userquota = str(userquota)

        # Test if userquota is '0' or '0M' ( quota pattern is ^(\d+[bkMGT])|0$ )
        is_limited = not re.match('0[bkMGT]?', userquota)
        storage_use = '?'

        if service_status("dovecot")["status"] != "running":
            logger.warning(m18n.n('mailbox_used_space_dovecot_down'))
        else:
            cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0]
            cmd_result = subprocess.check_output(cmd,
                                                 stderr=subprocess.STDOUT,
                                                 shell=True)
            # Exemple of return value for cmd:
            # """Quota name=User quota Type=STORAGE Value=0 Limit=- %=0
            # Quota name=User quota Type=MESSAGE Value=0 Limit=- %=0"""
            has_value = re.search(r'Value=(\d+)', cmd_result)

            if has_value:
                storage_use = int(has_value.group(1))
                storage_use = _convertSize(storage_use)

                if is_limited:
                    has_percent = re.search(r'%=(\d+)', cmd_result)

                    if has_percent:
                        percentage = int(has_percent.group(1))
                        storage_use += ' (%s%%)' % percentage

        result_dict['mailbox-quota'] = {
            'limit': userquota if is_limited else m18n.n('unlimit'),
            'use': storage_use
        }

    if result:
        return result_dict
    else:
        raise MoulinetteError(167, m18n.n('user_info_failed'))
Exemple #4
0
def monitor_network(units=None, human_readable=False):
    """
    Monitor network interfaces

    Keyword argument:
        units -- Unit(s) to monitor
        human_readable -- Print sizes in human readable format

    """
    glances = _get_glances_api()
    result = {}

    if units is None:
        units = ['usage', 'infos']

    # Get network devices and their addresses
    devices = {}
    output = subprocess.check_output('ip addr show'.split())
    for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE):
        d = re.sub('\n[ ]+', ' % ', d)          # Replace new lines by %
        m = re.match('([a-z]+[0-9]?): (.*)', d) # Extract device name (1) and its addresses (2)
        if m:
            devices[m.group(1)] = m.group(2)

    # Retrieve monitoring for unit(s)
    for u in units:
        if u == 'usage':
            result[u] = {}
            for i in json.loads(glances.getNetwork()):
                iname = i['interface_name']
                if iname in devices.keys():
                    del i['interface_name']
                    if human_readable:
                        for k in i.keys():
                            if k != 'time_since_update':
                                i[k] = _binary_to_human(i[k]) + 'B'
                    result[u][iname] = i
        elif u == 'infos':
            try:
                p_ip = str(urlopen('http://ip.yunohost.org').read())
            except:
                p_ip = 'unknown'

            l_ip = 'unknown'
            for name, addrs in devices.items():
                if name == 'lo':
                    continue
                if not isinstance(l_ip, dict):
                    l_ip = {}
                l_ip[name] = _extract_inet(addrs)

            gateway = 'unknown'
            output = subprocess.check_output('ip route show'.split())
            m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output)
            if m:
                addr = _extract_inet(m.group(1), True)
                if len(addr) == 1:
                    proto, gateway = addr.popitem()

            result[u] = {
                'public_ip': p_ip,
                'local_ip': l_ip,
                'gateway': gateway
            }
        else:
            raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u))

    if len(units) == 1:
        return result[units[0]]
    return result
Exemple #5
0
def monitor_disk(units=None, mountpoint=None, human_readable=False):
    """
    Monitor disk space and usage

    Keyword argument:
        units -- Unit(s) to monitor
        mountpoint -- Device mountpoint
        human_readable -- Print sizes in human readable format

    """
    glances = _get_glances_api()
    result_dname = None
    result = {}

    if units is None:
        units = ['io', 'filesystem']

    _format_dname = lambda d: (os.path.realpath(d)).replace('/dev/', '')

    # Get mounted devices
    devices = {}
    for p in psutil.disk_partitions(all=True):
        if not p.device.startswith('/dev/') or not p.mountpoint:
            continue
        if mountpoint is None:
            devices[_format_dname(p.device)] = p.mountpoint
        elif mountpoint == p.mountpoint:
            dn = _format_dname(p.device)
            devices[dn] = p.mountpoint
            result_dname = dn
    if len(devices) == 0:
        if mountpoint is not None:
            raise MoulinetteError(errno.ENODEV, m18n.n('mountpoint_unknown'))
        return result

    # Retrieve monitoring for unit(s)
    for u in units:
        if u == 'io':
            ## Define setter
            if len(units) > 1:
                def _set(dn, dvalue):
                    try:
                        result[dn][u] = dvalue
                    except KeyError:
                        result[dn] = { u: dvalue }
            else:
                def _set(dn, dvalue):
                    result[dn] = dvalue

            # Iterate over values
            devices_names = devices.keys()
            for d in json.loads(glances.getDiskIO()):
                dname = d.pop('disk_name')
                try:
                    devices_names.remove(dname)
                except:
                    continue
                else:
                    _set(dname, d)
            for dname in devices_names:
                _set(dname, 'not-available')
        elif u == 'filesystem':
            ## Define setter
            if len(units) > 1:
                def _set(dn, dvalue):
                    try:
                        result[dn][u] = dvalue
                    except KeyError:
                        result[dn] = { u: dvalue }
            else:
                def _set(dn, dvalue):
                    result[dn] = dvalue

            # Iterate over values
            devices_names = devices.keys()
            for d in json.loads(glances.getFs()):
                dname = _format_dname(d.pop('device_name'))
                try:
                    devices_names.remove(dname)
                except:
                    continue
                else:
                    if human_readable:
                        for i in ['used', 'avail', 'size']:
                            d[i] = _binary_to_human(d[i]) + 'B'
                    _set(dname, d)
            for dname in devices_names:
                _set(dname, 'not-available')
        else:
            raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u))

    if result_dname is not None:
        return result[result_dname]
    return result
Exemple #6
0
def backup_info(name, with_details=False, human_readable=False):
    """
    Get info about a local backup archive

    Keyword arguments:
        name -- Name of the local backup archive
        with_details -- Show additional backup information
        human_readable -- Print sizes in human readable format

    """
    archive_file = '%s/%s.tar.gz' % (archives_path, name)

    # Check file exist (even if it's a broken symlink)
    if not os.path.lexists(archive_file):
        raise MoulinetteError(errno.EIO,
                              m18n.n('backup_archive_name_unknown', name=name))

    # If symlink, retrieve the real path
    if os.path.islink(archive_file):
        archive_file = os.path.realpath(archive_file)

        # Raise exception if link is broken (e.g. on unmounted external storage)
        if not os.path.exists(archive_file):
            raise MoulinetteError(
                errno.EIO,
                m18n.n('backup_archive_broken_link', path=archive_file))

    info_file = "%s/%s.info.json" % (archives_path, name)

    try:
        with open(info_file) as f:
            # Retrieve backup info
            info = json.load(f)
    except:
        # TODO: Attempt to extract backup info file from tarball
        logger.debug("unable to load '%s'", info_file, exc_info=1)
        raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))

    # Retrieve backup size
    size = info.get('size', 0)
    if not size:
        tar = tarfile.open(archive_file, "r:gz")
        size = reduce(
            lambda x, y: getattr(x, 'size', x) + getattr(y, 'size', y),
            tar.getmembers())
        tar.close()
    if human_readable:
        size = binary_to_human(size) + 'B'

    result = {
        'path':
        archive_file,
        'created_at':
        time.strftime(m18n.n('format_datetime_short'),
                      time.gmtime(info['created_at'])),
        'description':
        info['description'],
        'size':
        size,
    }

    if with_details:
        for d in ['apps', 'hooks']:
            result[d] = info[d]
    return result
Exemple #7
0
def tools_update(ignore_apps=False, ignore_packages=False):
    """
    Update apps & package cache, then display changelog

    Keyword arguments:
        ignore_apps -- Ignore app list update and changelog
        ignore_packages -- Ignore apt cache update and changelog

    """
    packages = []
    if not ignore_packages:
        cache = apt.Cache()

        # Update APT cache
        logger.info(m18n.n('updating_apt_cache'))
        if not cache.update():
            raise MoulinetteError(errno.EPERM, m18n.n('update_cache_failed'))

        logger.info(m18n.n('done'))

        cache.open(None)
        cache.upgrade(True)

        # Add changelogs to the result
        for pkg in cache.get_changes():
            packages.append({
                'name': pkg.name,
                'fullname': pkg.fullname,
                'changelog': pkg.get_changelog()
            })

    apps = []
    if not ignore_apps:
        try:
            app_fetchlist()
        except MoulinetteError:
            pass
        app_list = os.listdir(apps_setting_path)
        if len(app_list) > 0:
            for app_id in app_list:
                if '__' in app_id:
                    original_app_id = app_id[:app_id.index('__')]
                else:
                    original_app_id = app_id

                current_app_dict = app_info(app_id, raw=True)
                new_app_dict = app_info(original_app_id, raw=True)

                # Custom app
                if new_app_dict is None or 'lastUpdate' not in new_app_dict or 'git' not in new_app_dict:
                    continue

                if (new_app_dict['lastUpdate'] > current_app_dict['lastUpdate']) \
                      or ('update_time' not in current_app_dict['settings'] \
                           and (new_app_dict['lastUpdate'] > current_app_dict['settings']['install_time'])) \
                      or ('update_time' in current_app_dict['settings'] \
                           and (new_app_dict['lastUpdate'] > current_app_dict['settings']['update_time'])):
                    apps.append({
                        'id': app_id,
                        'label': current_app_dict['settings']['label']
                    })

    if len(apps) == 0 and len(packages) == 0:
        logger.info(m18n.n('packages_no_upgrade'))

    return {'packages': packages, 'apps': apps}
Exemple #8
0
def backup_create(name=None, description=None, output_directory=None,
                  no_compress=False, ignore_apps=False):
    """
    Create a backup local archive

    Keyword arguments:
        name -- Name of the backup archive
        description -- Short description of the backup
        output_directory -- Output directory for the backup
        no_compress -- Do not create an archive file
        ignore_apps -- Do not backup apps

    """
    # TODO: Add a 'clean' argument to clean output directory
    from yunohost.hook import hook_add
    from yunohost.hook import hook_callback

    tmp_dir = None

    # Validate and define backup name
    timestamp = int(time.time())
    if not name:
        name = str(timestamp)
    if name in backup_list()['archives']:
        raise MoulinetteError(errno.EINVAL,
                              m18n.n('backup_archive_name_exists'))

    # Validate additional arguments
    if no_compress and not output_directory:
        raise MoulinetteError(errno.EINVAL,
                              m18n.n('backup_output_directory_required'))
    if output_directory:
        output_directory = os.path.abspath(output_directory)

        # Check for forbidden folders
        if output_directory.startswith(archives_path) or \
           re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$',
                    output_directory):
            logger.error("forbidden output directory '%'", output_directory)
            raise MoulinetteError(errno.EINVAL,
                                  m18n.n('backup_output_directory_forbidden'))

        # Create the output directory
        if not os.path.isdir(output_directory):
            logger.info("creating output directory '%s'", output_directory)
            os.makedirs(output_directory, 0750)
        # Check that output directory is empty
        elif no_compress and os.listdir(output_directory):
            logger.error("not empty output directory '%'", output_directory)
            raise MoulinetteError(errno.EIO,
                                  m18n.n('backup_output_directory_not_empty'))

        # Define temporary directory
        if no_compress:
            tmp_dir = output_directory
    else:
        output_directory = archives_path

    # Create temporary directory
    if not tmp_dir:
        tmp_dir = "%s/tmp/%s" % (backup_path, name)
        if os.path.isdir(tmp_dir):
            logger.warning("temporary directory for backup '%s' already exists",
                           tmp_dir)
            os.system('rm -rf %s' % tmp_dir)
        try:
            os.mkdir(tmp_dir, 0750)
        except OSError:
            # Create temporary directory recursively
            os.makedirs(tmp_dir, 0750)
            os.system('chown -hR admin: %s' % backup_path)
        else:
            os.system('chown -hR admin: %s' % tmp_dir)

    # Initialize backup info
    info = {
        'description': description or '',
        'created_at': timestamp,
        'apps': {},
    }

    # Add apps backup hook
    if not ignore_apps:
        from yunohost.app import app_info
        try:
            for app_id in os.listdir('/etc/yunohost/apps'):
                hook = '/etc/yunohost/apps/%s/scripts/backup' % app_id
                if os.path.isfile(hook):
                    hook_add(app_id, hook)

                    # Add app info
                    i = app_info(app_id)
                    info['apps'][app_id] = {
                        'version': i['version'],
                    }
                else:
                    logger.warning("unable to find app's backup hook '%s'",
                                   hook)
                    msignals.display(m18n.n('unbackup_app', app_id),
                                     'warning')
        except IOError as e:
            logger.info("unable to add apps backup hook: %s", str(e))

    # Run hooks
    msignals.display(m18n.n('backup_running_hooks'))
    hook_callback('backup', [tmp_dir])

    # Create backup info file
    with open("%s/info.json" % tmp_dir, 'w') as f:
        f.write(json.dumps(info))

    # Create the archive
    if not no_compress:
        msignals.display(m18n.n('backup_creating_archive'))
        archive_file = "%s/%s.tar.gz" % (output_directory, name)
        try:
            tar = tarfile.open(archive_file, "w:gz")
        except:
            tar = None

            # Create the archives directory and retry
            if not os.path.isdir(archives_path):
                os.mkdir(archives_path, 0750)
                try:
                    tar = tarfile.open(archive_file, "w:gz")
                except:
                    logger.exception("unable to open the archive '%s' for writing "
                                     "after creating directory '%s'",
                                     archive_file, archives_path)
                    tar = None
            else:
                logger.exception("unable to open the archive '%s' for writing",
                                 archive_file)
            if tar is None:
                raise MoulinetteError(errno.EIO,
                                      m18n.n('backup_archive_open_failed'))
        tar.add(tmp_dir, arcname='')
        tar.close()

        # Copy info file
        os.system('mv %s/info.json %s/%s.info.json' %
                  (tmp_dir, archives_path, name))

    # Clean temporary directory
    if tmp_dir != output_directory:
        os.system('rm -rf %s' % tmp_dir)

    msignals.display(m18n.n('backup_complete'), 'success')
Exemple #9
0
def dyndns_update(dyn_host="dynhost.yunohost.org",
                  domain=None,
                  key=None,
                  ip=None):
    """
    Update IP on DynDNS platform

    Keyword argument:
        domain -- Full domain to subscribe with
        dyn_host -- Dynette DNS server to inform
        key -- Public DNS key
        ip -- IP address to send

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

    if ip is None:
        try:
            new_ip = requests.get('http://ip.yunohost.org').text
        except ConnectionError:
            raise MoulinetteError(errno.ENETUNREACH,
                                  m18n.n('no_internet_connection'))
    else:
        new_ip = ip

    try:
        with open('/etc/yunohost/dyndns/old_ip', 'r') as f:
            old_ip = f.readline().rstrip()
    except IOError:
        old_ip = '0.0.0.0'

    # IPv6
    # TODO: Put global IPv6 in the DNS zone instead of ULA
    new_ipv6 = None
    try:
        with open('/etc/yunohost/ipv6') as f:
            old_ipv6 = f.readline().rstrip()
    except IOError:
        old_ipv6 = '0000:0000:0000:0000:0000:0000:0000:0000'

    try:
        # Get the interface
        with open('/etc/yunohost/interface') as f:
            interface = f.readline().rstrip()
        # Get the ULA
        with open('/etc/yunohost/ula') as f:
            ula = f.readline().rstrip()
        # Get the IPv6 address given by radvd and sanitize it
        with open('/proc/net/if_inet6') as f:
            plain_ula = ''
            for hextet in ula.split(':')[0:3]:
                if len(hextet) < 4:
                    hextet = '0000' + hextet
                    hextet = hextet[-4:]
                plain_ula = plain_ula + hextet
            for line in f.readlines():
                if interface in line and plain_ula == line[0:12]:
                    new_ipv6 = ':'.join(
                        [line[0:32][i:i + 4] for i in range(0, 32, 4)])
                    with open('/etc/yunohost/ipv6', 'w+') as f:
                        f.write(new_ipv6)
                    break
    except IOError:
        pass

    if new_ipv6 is None:
        new_ipv6 = '0000:0000:0000:0000:0000:0000:0000:0000'

    if old_ip != new_ip or old_ipv6 != new_ipv6 and new_ipv6 is not None:
        host = domain.split('.')[1:]
        host = '.'.join(host)
        lines = [
            'server %s' % dyn_host,
            'zone %s' % host,
            'update delete %s. A' % domain,
            'update delete %s. AAAA' % domain,
            'update delete %s. MX' % domain,
            'update delete %s. TXT' % domain,
            'update delete pubsub.%s. A' % domain,
            'update delete muc.%s. A' % domain,
            'update delete vjud.%s. A' % domain,
            'update delete _xmpp-client._tcp.%s. SRV' % domain,
            'update delete _xmpp-server._tcp.%s. SRV' % domain,
            'update add %s. 1800 A %s' % (domain, new_ip),
            'update add %s. 1800 AAAA %s' % (domain, new_ipv6),
            'update add %s. 14400 MX 5 %s.' % (domain, domain),
            'update add %s. 14400 TXT "v=spf1 a mx -all"' % domain,
            'update add pubsub.%s. 1800 A %s' % (domain, new_ip),
            'update add pubsub.%s. 1800 AAAA %s' % (domain, new_ipv6),
            'update add muc.%s. 1800 A %s' % (domain, new_ip),
            'update add muc.%s. 1800 AAAA %s' % (domain, new_ipv6),
            'update add vjud.%s. 1800 A %s' % (domain, new_ip),
            'update add vjud.%s. 1800 AAAA %s' % (domain, new_ipv6),
            'update add _xmpp-client._tcp.%s. 14400 SRV 0 5 5222 %s.' %
            (domain, domain),
            'update add _xmpp-server._tcp.%s. 14400 SRV 0 5 5269 %s.' %
            (domain, domain), 'show', 'send'
        ]
        with open('/etc/yunohost/dyndns/zone', 'w') as zone:
            for line in lines:
                zone.write(line + '\n')

        if key is None:
            private_key_file = glob.glob('/etc/yunohost/dyndns/*.private')[0]
        else:
            private_key_file = key
        if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' %
                     private_key_file) == 0:
            msignals.display(m18n.n('dyndns_ip_updated'), 'success')
            with open('/etc/yunohost/dyndns/old_ip', 'w') as f:
                f.write(new_ip)
        else:
            os.system('rm /etc/yunohost/dyndns/old_ip > /dev/null 2>&1')
            raise MoulinetteError(errno.EPERM,
                                  m18n.n('dyndns_ip_update_failed'))
Exemple #10
0
def hook_exec(path,
              args=None,
              raise_on_error=False,
              no_trace=False,
              chdir=None,
              env=None):
    """
    Execute hook from a file with arguments

    Keyword argument:
        path -- Path of the script to execute
        args -- Ordered list of arguments to pass to the script
        raise_on_error -- Raise if the script returns a non-zero exit code
        no_trace -- Do not print each command that will be executed
        chdir -- The directory from where the script will be executed
        env -- Dictionnary of environment variables to export

    """
    from moulinette.utils.process import call_async_output
    from yunohost.app import _value_for_locale

    # Validate hook path
    if path[0] != '/':
        path = os.path.realpath(path)
    if not os.path.isfile(path):
        raise MoulinetteError(errno.EIO, m18n.g('file_not_exist', path=path))

    # Construct command variables
    cmd_args = ''
    if args and isinstance(args, list):
        # Concatenate escaped arguments
        cmd_args = ' '.join(shell_quote(s) for s in args)
    if not chdir:
        # use the script directory as current one
        chdir, cmd_script = os.path.split(path)
        cmd_script = './{0}'.format(cmd_script)
    else:
        cmd_script = path

    # Construct command to execute
    command = ['sudo', '-n', '-u', 'admin', '-H', 'sh', '-c']
    if no_trace:
        cmd = '/bin/bash "{script}" {args}'
    else:
        # use xtrace on fd 7 which is redirected to stdout
        cmd = 'BASH_XTRACEFD=7 /bin/bash -x "{script}" {args} 7>&1'
    if env:
        # prepend environment variables
        cmd = '{0} {1}'.format(
            ' '.join(['{0}={1}'.format(k, shell_quote(v)) \
                    for k, v in env.items()]), cmd)
    command.append(cmd.format(script=cmd_script, args=cmd_args))

    if logger.isEnabledFor(log.DEBUG):
        logger.info(m18n.n('executing_command', command=' '.join(command)))
    else:
        logger.info(m18n.n('executing_script', script=path))

    # Define output callbacks and call command
    callbacks = (
        lambda l: logger.info(l.rstrip()),
        lambda l: logger.warning(l.rstrip()),
    )
    returncode = call_async_output(command, callbacks, shell=False, cwd=chdir)

    # Check and return process' return code
    if returncode is None:
        if raise_on_error:
            raise MoulinetteError(
                errno.EIO, m18n.n('hook_exec_not_terminated', path=path))
        else:
            logger.error(m18n.n('hook_exec_not_terminated', path=path))
            return 1
    elif raise_on_error and returncode != 0:
        raise MoulinetteError(errno.EIO, m18n.n('hook_exec_failed', path=path))
    return returncode
Exemple #11
0
def backup_restore(name, ignore_apps=False, force=False):
    """
    Restore from a local backup archive

    Keyword argument:
        name -- Name of the local backup archive
        ignore_apps -- Do not restore apps
        force -- Force restauration on an already installed system

    """
    from yunohost.hook import hook_add
    from yunohost.hook import hook_callback

    # Retrieve and open the archive
    archive_file = backup_info(name)['path']
    try:
        tar = tarfile.open(archive_file, "r:gz")
    except:
        logger.exception("unable to open the archive '%s' for reading",
                         archive_file)
        raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed'))

    # Check temporary directory
    tmp_dir = "%s/tmp/%s" % (backup_path, name)
    if os.path.isdir(tmp_dir):
        logger.warning("temporary directory for restoration '%s' already exists",
                       tmp_dir)
        os.system('rm -rf %s' % tmp_dir)

    # Extract the tarball
    msignals.display(m18n.n('backup_extracting_archive'))
    tar.extractall(tmp_dir)
    tar.close()

    # Retrieve backup info
    try:
        with open("%s/info.json" % tmp_dir, 'r') as f:
            info = json.load(f)
    except IOError:
        logger.error("unable to retrieve backup info from '%s/info.json'",
                     tmp_dir)
        raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
    else:
        logger.info("restoring from backup '%s' created on %s", name,
                    time.ctime(info['created_at']))

    # Retrieve domain from the backup
    try:
        with open("%s/yunohost/current_host" % tmp_dir, 'r') as f:
            domain = f.readline().rstrip()
    except IOError:
        logger.error("unable to retrieve domain from '%s/yunohost/current_host'",
                     tmp_dir)
        raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))

    # Check if YunoHost is installed
    if os.path.isfile('/etc/yunohost/installed'):
        msignals.display(m18n.n('yunohost_already_installed'), 'warning')
        if not force:
            try:
                # Ask confirmation for restoring
                i = msignals.prompt(m18n.n('restore_confirm_yunohost_installed',
                                           answers='y/N'))
            except NotImplemented:
                pass
            else:
                if i == 'y' or i == 'Y':
                    force = True
            if not force:
                raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed'))
    else:
        from yunohost.tools import tools_postinstall
        logger.info("executing the post-install...")
        tools_postinstall(domain, 'yunohost', True)

    # Add apps restore hook
    if not ignore_apps:
        for app_id in info['apps'].keys():
            hook = "/etc/yunohost/apps/%s/scripts/restore" % app_id
            if os.path.isfile(hook):
                hook_add(app_id, hook)
                logger.info("app '%s' will be restored", app_id)
            else:
                msignals.display(m18n.n('unrestore_app', app_id), 'warning')

    # Run hooks
    msignals.display(m18n.n('restore_running_hooks'))
    hook_callback('restore', [tmp_dir])

    # Remove temporary directory
    os.system('rm -rf %s' % tmp_dir)

    msignals.display(m18n.n('restore_complete'), 'success')
Exemple #12
0
def hook_callback(action,
                  hooks=[],
                  args=None,
                  no_trace=False,
                  chdir=None,
                  env=None,
                  pre_callback=None,
                  post_callback=None):
    """
    Execute all scripts binded to an action

    Keyword argument:
        action -- Action name
        hooks -- List of hooks names to execute
        args -- Ordered list of arguments to pass to the scripts
        no_trace -- Do not print each command that will be executed
        chdir -- The directory from where the scripts will be executed
        env -- Dictionnary of environment variables to export
        pre_callback -- An object to call before each script execution with
            (name, priority, path, args) as arguments and which must return
            the arguments to pass to the script
        post_callback -- An object to call after each script execution with
            (name, priority, path, succeed) as arguments

    """
    result = {'succeed': {}, 'failed': {}}
    hooks_dict = {}

    # Retrieve hooks
    if not hooks:
        hooks_dict = hook_list(action, list_by='priority',
                               show_info=True)['hooks']
    else:
        hooks_names = hook_list(action, list_by='name',
                                show_info=True)['hooks']

        # Add similar hooks to the list
        # For example: Having a 16-postfix hook in the list will execute a
        # xx-postfix_dkim as well
        all_hooks = []
        for n in hooks:
            for key in hooks_names.keys():
                if key == n or key.startswith("%s_" % n) \
                  and key not in all_hooks:
                    all_hooks.append(key)

        # Iterate over given hooks names list
        for n in all_hooks:
            try:
                hl = hooks_names[n]
            except KeyError:
                raise MoulinetteError(errno.EINVAL,
                                      m18n.n('hook_name_unknown', n))
            # Iterate over hooks with this name
            for h in hl:
                # Update hooks dict
                d = hooks_dict.get(h['priority'], dict())
                d.update({n: {'path': h['path']}})
                hooks_dict[h['priority']] = d
    if not hooks_dict:
        return result

    # Validate callbacks
    if not callable(pre_callback):
        pre_callback = lambda name, priority, path, args: args
    if not callable(post_callback):
        post_callback = lambda name, priority, path, succeed: None

    # Iterate over hooks and execute them
    for priority in sorted(hooks_dict):
        for name, info in iter(hooks_dict[priority].items()):
            state = 'succeed'
            path = info['path']
            try:
                hook_args = pre_callback(name=name,
                                         priority=priority,
                                         path=path,
                                         args=args)
                hook_exec(path,
                          args=hook_args,
                          chdir=chdir,
                          env=env,
                          no_trace=no_trace,
                          raise_on_error=True)
            except MoulinetteError as e:
                state = 'failed'
                logger.error(e.strerror, exc_info=1)
                post_callback(name=name,
                              priority=priority,
                              path=path,
                              succeed=False)
            else:
                post_callback(name=name,
                              priority=priority,
                              path=path,
                              succeed=True)
            try:
                result[state][name].append(path)
            except KeyError:
                result[state][name] = [path]
    return result
Exemple #13
0
def hook_list(action, list_by='name', show_info=False):
    """
    List available hooks for an action

    Keyword argument:
        action -- Action name
        list_by -- Property to list hook by
        show_info -- Show hook information

    """
    result = {}

    # Process the property to list hook by
    if list_by == 'priority':
        if show_info:

            def _append_hook(d, priority, name, path):
                # Use the priority as key and a dict of hooks names
                # with their info as value
                value = {'path': path}
                try:
                    d[priority][name] = value
                except KeyError:
                    d[priority] = {name: value}
        else:

            def _append_hook(d, priority, name, path):
                # Use the priority as key and the name as value
                try:
                    d[priority].add(name)
                except KeyError:
                    d[priority] = set([name])
    elif list_by == 'name' or list_by == 'folder':
        if show_info:

            def _append_hook(d, priority, name, path):
                # Use the name as key and a list of hooks info - the
                # executed ones with this name - as value
                l = d.get(name, list())
                for h in l:
                    # Only one priority for the hook is accepted
                    if h['priority'] == priority:
                        # Custom hooks overwrite system ones and they
                        # are appended at the end - so overwite it
                        if h['path'] != path:
                            h['path'] = path
                        return
                l.append({'priority': priority, 'path': path})
                d[name] = l
        else:
            if list_by == 'name':
                result = set()

            def _append_hook(d, priority, name, path):
                # Add only the name
                d.add(name)
    else:
        raise MoulinetteError(errno.EINVAL, m18n.n('hook_list_by_invalid'))

    def _append_folder(d, folder):
        # Iterate over and add hook from a folder
        for f in os.listdir(folder + action):
            if f[0] == '.' or f[-1] == '~':
                continue
            path = '%s%s/%s' % (folder, action, f)
            priority, name = _extract_filename_parts(f)
            _append_hook(d, priority, name, path)

    try:
        # Append system hooks first
        if list_by == 'folder':
            result['system'] = dict() if show_info else set()
            _append_folder(result['system'], hook_folder)
        else:
            _append_folder(result, hook_folder)
    except OSError:
        logger.debug("system hook folder not found for action '%s' in %s",
                     action, hook_folder)

    try:
        # Append custom hooks
        if list_by == 'folder':
            result['custom'] = dict() if show_info else set()
            _append_folder(result['custom'], custom_hook_folder)
        else:
            _append_folder(result, custom_hook_folder)
    except OSError:
        logger.debug("custom hook folder not found for action '%s' in %s",
                     action, custom_hook_folder)

    return {'hooks': result}
Exemple #14
0
def monitor_network(units=None, human_readable=False):
    """
    Monitor network interfaces

    Keyword argument:
        units -- Unit(s) to monitor
        human_readable -- Print sizes in human readable format

    """
    glances = _get_glances_api()
    result = {}

    if units is None:
        units = ['check', 'usage', 'infos']

    # Get network devices and their addresses
    devices = {}
    output = subprocess.check_output('ip addr show'.split())
    for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE):
        # Extract device name (1) and its addresses (2)
        m = re.match('([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL)
        if m:
            devices[m.group(1)] = m.group(2)

    # Retrieve monitoring for unit(s)
    for u in units:
        if u == 'check':
            result[u] = {}
            with open('/etc/yunohost/current_host', 'r') as f:
                domain = f.readline().rstrip()
            cmd_check_smtp = os.system('/bin/nc -z -w1 yunohost.org 25')
            if cmd_check_smtp == 0:
                smtp_check = m18n.n('network_check_smtp_ok')
            else:
                smtp_check = m18n.n('network_check_smtp_ko')

            try:
                answers = dns.resolver.query(domain, 'MX')
                mx_check = {}
                i = 0
                for server in answers:
                    mx_id = 'mx%s' % i
                    mx_check[mx_id] = server
                    i = i + 1
            except:
                mx_check = m18n.n('network_check_mx_ko')
            result[u] = {'smtp_check': smtp_check, 'mx_check': mx_check}
        elif u == 'usage':
            result[u] = {}
            for i in json.loads(glances.getNetwork()):
                iname = i['interface_name']
                if iname in devices.keys():
                    del i['interface_name']
                    if human_readable:
                        for k in i.keys():
                            if k != 'time_since_update':
                                i[k] = binary_to_human(i[k]) + 'B'
                    result[u][iname] = i
                else:
                    logger.debug('interface name %s was not found', iname)
        elif u == 'infos':
            try:
                p_ipv4 = get_public_ip()
            except:
                p_ipv4 = 'unknown'

            l_ip = 'unknown'
            for name, addrs in devices.items():
                if name == 'lo':
                    continue
                if not isinstance(l_ip, dict):
                    l_ip = {}
                l_ip[name] = _extract_inet(addrs)

            gateway = 'unknown'
            output = subprocess.check_output('ip route show'.split())
            m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output)
            if m:
                addr = _extract_inet(m.group(1), True)
                if len(addr) == 1:
                    proto, gateway = addr.popitem()

            result[u] = {
                'public_ip': p_ipv4,
                'local_ip': l_ip,
                'gateway': gateway,
            }
        else:
            raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))

    if len(units) == 1:
        return result[units[0]]
    return result
Exemple #15
0
def backup_restore(auth,
                   name,
                   hooks=[],
                   ignore_hooks=False,
                   apps=[],
                   ignore_apps=False,
                   force=False):
    """
    Restore from a local backup archive

    Keyword argument:
        name -- Name of the local backup archive
        hooks -- List of restoration hooks names to execute
        ignore_hooks -- Do not execute backup hooks
        apps -- List of application names to restore
        ignore_apps -- Do not restore apps
        force -- Force restauration on an already installed system

    """
    # Validate what to restore
    if ignore_hooks and ignore_apps:
        raise MoulinetteError(errno.EINVAL, m18n.n('restore_action_required'))

    # Retrieve and open the archive
    info = backup_info(name)
    archive_file = info['path']
    try:
        tar = tarfile.open(archive_file, "r:gz")
    except:
        logger.debug("cannot open backup archive '%s'",
                     archive_file,
                     exc_info=1)
        raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed'))

    # Check temporary directory
    tmp_dir = "%s/tmp/%s" % (backup_path, name)
    if os.path.isdir(tmp_dir):
        logger.debug("temporary directory for restoration '%s' already exists",
                     tmp_dir)
        os.system('rm -rf %s' % tmp_dir)

    # Check available disk space
    statvfs = os.statvfs(backup_path)
    free_space = statvfs.f_frsize * statvfs.f_bavail
    if free_space < info['size']:
        logger.debug("%dB left but %dB is needed", free_space, info['size'])
        raise MoulinetteError(
            errno.EIO, m18n.n('not_enough_disk_space', path=backup_path))

    def _clean_tmp_dir(retcode=0):
        ret = hook_callback('post_backup_restore', args=[tmp_dir, retcode])
        if not ret['failed']:
            filesystem.rm(tmp_dir, True, True)
        else:
            logger.warning(m18n.n('restore_cleaning_failed'))

    # Extract the tarball
    logger.info(m18n.n('backup_extracting_archive'))
    tar.extractall(tmp_dir)
    tar.close()

    # Retrieve backup info
    info_file = "%s/info.json" % tmp_dir
    try:
        with open(info_file, 'r') as f:
            info = json.load(f)
    except IOError:
        logger.debug("unable to load '%s'", info_file, exc_info=1)
        raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
    else:
        logger.debug("restoring from backup '%s' created on %s", name,
                     time.ctime(info['created_at']))

    # Initialize restauration summary result
    result = {
        'apps': [],
        'hooks': {},
    }

    # Check if YunoHost is installed
    if os.path.isfile('/etc/yunohost/installed'):
        logger.warning(m18n.n('yunohost_already_installed'))
        if not force:
            try:
                # Ask confirmation for restoring
                i = msignals.prompt(
                    m18n.n('restore_confirm_yunohost_installed',
                           answers='y/N'))
            except NotImplemented:
                pass
            else:
                if i == 'y' or i == 'Y':
                    force = True
            if not force:
                _clean_tmp_dir()
                raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed'))
    else:
        # Retrieve the domain from the backup
        try:
            with open("%s/conf/ynh/current_host" % tmp_dir, 'r') as f:
                domain = f.readline().rstrip()
        except IOError:
            logger.debug("unable to retrieve current_host from the backup",
                         exc_info=1)
            raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))

        logger.debug("executing the post-install...")
        tools_postinstall(domain, 'yunohost', True)

    # Run system hooks
    if not ignore_hooks:
        # Filter hooks to execute
        hooks_list = set(info['hooks'].keys())
        _is_hook_in_backup = lambda h: True
        if hooks:

            def _is_hook_in_backup(h):
                if h in hooks_list:
                    return True
                logger.error(m18n.n('backup_archive_hook_not_exec', hook=h))
                return False
        else:
            hooks = hooks_list

        # Check hooks availibility
        hooks_filtered = set()
        for h in hooks:
            if not _is_hook_in_backup(h):
                continue
            try:
                hook_info('restore', h)
            except:
                tmp_hooks = glob('{:s}/hooks/restore/*-{:s}'.format(
                    tmp_dir, h))
                if not tmp_hooks:
                    logger.exception(m18n.n('restore_hook_unavailable',
                                            hook=h))
                    continue
                # Add restoration hook from the backup to the system
                # FIXME: Refactor hook_add and use it instead
                restore_hook_folder = custom_hook_folder + 'restore'
                filesystem.mkdir(restore_hook_folder, 755, True)
                for f in tmp_hooks:
                    logger.debug(
                        "adding restoration hook '%s' to the system "
                        "from the backup archive '%s'", f, archive_file)
                    shutil.copy(f, restore_hook_folder)
            hooks_filtered.add(h)

        if hooks_filtered:
            logger.info(m18n.n('restore_running_hooks'))
            ret = hook_callback('restore', hooks_filtered, args=[tmp_dir])
            result['hooks'] = ret['succeed']

    # Add apps restore hook
    if not ignore_apps:
        # Filter applications to restore
        apps_list = set(info['apps'].keys())
        apps_filtered = set()
        if apps:
            for a in apps:
                if a not in apps_list:
                    logger.error(m18n.n('backup_archive_app_not_found', app=a))
                else:
                    apps_filtered.add(a)
        else:
            apps_filtered = apps_list

        for app_instance_name in apps_filtered:
            tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_instance_name)
            tmp_app_bkp_dir = tmp_app_dir + '/backup'

            # Parse app instance name and id
            # TODO: Use app_id to check if app is installed?
            app_id, app_instance_nb = _parse_app_instance_name(
                app_instance_name)

            # Check if the app is not already installed
            if _is_installed(app_instance_name):
                logger.error(
                    m18n.n('restore_already_installed_app',
                           app=app_instance_name))
                continue

            # Check if the app has a restore script
            app_script = tmp_app_dir + '/settings/scripts/restore'
            if not os.path.isfile(app_script):
                logger.warning(m18n.n('unrestore_app', app=app_instance_name))
                continue

            tmp_script = '/tmp/restore_' + app_instance_name
            app_setting_path = '/etc/yunohost/apps/' + app_instance_name
            logger.info(
                m18n.n('restore_running_app_script', app=app_instance_name))
            try:
                # Copy app settings and set permissions
                # TODO: Copy app hooks too
                shutil.copytree(tmp_app_dir + '/settings', app_setting_path)
                filesystem.chmod(app_setting_path, 0555, 0444, True)
                filesystem.chmod(app_setting_path + '/settings.yml', 0400)

                # Copy restore script in a tmp file
                subprocess.call(['install', '-Dm555', app_script, tmp_script])

                # Prepare env. var. to pass to script
                env_dict = {}
                env_dict["YNH_APP_ID"] = app_id
                env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name
                env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
                env_dict["YNH_APP_BACKUP_DIR"] = tmp_app_bkp_dir

                # Execute app restore script
                hook_exec(tmp_script,
                          args=[tmp_app_bkp_dir, app_instance_name],
                          raise_on_error=True,
                          chdir=tmp_app_bkp_dir,
                          env=env_dict)
            except:
                logger.exception(
                    m18n.n('restore_app_failed', app=app_instance_name))

                # Copy remove script in a tmp file
                filesystem.rm(tmp_script, force=True)
                app_script = tmp_app_dir + '/settings/scripts/remove'
                tmp_script = '/tmp/remove_' + app_instance_name
                subprocess.call(['install', '-Dm555', app_script, tmp_script])

                # Setup environment for remove script
                env_dict_remove = {}
                env_dict_remove["YNH_APP_ID"] = app_id
                env_dict_remove["YNH_APP_INSTANCE_NAME"] = app_instance_name
                env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(
                    app_instance_nb)

                # Execute remove script
                # TODO: call app_remove instead
                if hook_exec(tmp_script,
                             args=[app_instance_name],
                             env=env_dict_remove) != 0:
                    logger.warning(
                        m18n.n('app_not_properly_removed',
                               app=app_instance_name))

                # Cleaning app directory
                shutil.rmtree(app_setting_path, ignore_errors=True)
            else:
                result['apps'].append(app_instance_name)
            finally:
                filesystem.rm(tmp_script, force=True)

    # Check if something has been restored
    if not result['hooks'] and not result['apps']:
        _clean_tmp_dir(1)
        raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done'))
    if result['apps']:
        app_ssowatconf(auth)

    _clean_tmp_dir()
    logger.success(m18n.n('restore_complete'))

    return result
Exemple #16
0
def domain_add(auth, domain, dyndns=False):
    """
    Create a custom domain

    Keyword argument:
        domain -- Domain name to add
        dyndns -- Subscribe to DynDNS

    """
    from yunohost.hook import hook_callback

    attr_dict = {'objectClass': ['mailDomain', 'top']}

    now = datetime.datetime.now()
    timestamp = str(now.year) + str(now.month) + str(now.day)

    if domain in domain_list(auth)['domains']:
        raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists'))

    # DynDNS domain
    if dyndns:
        if len(domain.split('.')) < 3:
            raise MoulinetteError(errno.EINVAL,
                                  m18n.n('domain_dyndns_invalid'))
        from yunohost.dyndns import dyndns_subscribe

        try:
            r = requests.get('https://dyndns.yunohost.org/domains')
        except requests.ConnectionError:
            pass
        else:
            dyndomains = json.loads(r.text)
            dyndomain = '.'.join(domain.split('.')[1:])
            if dyndomain in dyndomains:
                if os.path.exists('/etc/cron.d/yunohost-dyndns'):
                    raise MoulinetteError(
                        errno.EPERM,
                        m18n.n('domain_dyndns_already_subscribed'))
                dyndns_subscribe(domain=domain)
            else:
                raise MoulinetteError(errno.EINVAL,
                                      m18n.n('domain_dyndns_root_unknown'))

    try:
        # Commands
        ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
        ssl_domain_path = '/etc/yunohost/certs/%s' % domain
        with open('%s/serial' % ssl_dir, 'r') as f:
            serial = f.readline().rstrip()
        try:
            os.listdir(ssl_domain_path)
        except OSError:
            os.makedirs(ssl_domain_path)

        command_list = [
            'cp %s/openssl.cnf %s' % (ssl_dir, ssl_domain_path),
            'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' %
            (domain, ssl_domain_path),
            'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch'
            % (ssl_domain_path, ssl_dir, ssl_dir),
            'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch'
            % (ssl_domain_path, ssl_dir, ssl_dir),
            'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' %
            ssl_domain_path,
            'cp %s/certs/yunohost_key.pem    %s/key.pem' %
            (ssl_dir, ssl_domain_path),
            'cp %s/newcerts/%s.pem %s/crt.pem' %
            (ssl_dir, serial, ssl_domain_path),
            'chmod 755 %s' % ssl_domain_path,
            'chmod 640 %s/key.pem' % ssl_domain_path,
            'chmod 640 %s/crt.pem' % ssl_domain_path,
            'chmod 600 %s/openssl.cnf' % ssl_domain_path,
            'chown root:metronome %s/key.pem' % ssl_domain_path,
            'chown root:metronome %s/crt.pem' % ssl_domain_path,
            'cat %s/ca.pem >> %s/crt.pem' % (ssl_domain_path, ssl_domain_path)
        ]

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

        try:
            auth.validate_uniqueness({'virtualdomain': domain})
        except MoulinetteError:
            raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists'))

        attr_dict['virtualdomain'] = domain

        if not auth.add('virtualdomain=%s,ou=domains' % domain, attr_dict):
            raise MoulinetteError(errno.EIO, m18n.n('domain_creation_failed'))

        try:
            with open('/etc/yunohost/installed', 'r') as f:
                service_regen_conf(
                    names=['nginx', 'metronome', 'dnsmasq', 'rmilter'])
                os.system('yunohost app ssowatconf > /dev/null 2>&1')
        except IOError:
            pass
    except:
        # Force domain removal silently
        try:
            domain_remove(auth, domain, True)
        except:
            pass
        raise

    hook_callback('post_domain_add', args=[domain])

    logger.success(m18n.n('domain_created'))
Exemple #17
0
def backup_create(name=None,
                  description=None,
                  output_directory=None,
                  no_compress=False,
                  ignore_hooks=False,
                  hooks=[],
                  ignore_apps=False,
                  apps=[]):
    """
    Create a backup local archive

    Keyword arguments:
        name -- Name of the backup archive
        description -- Short description of the backup
        output_directory -- Output directory for the backup
        no_compress -- Do not create an archive file
        hooks -- List of backup hooks names to execute
        ignore_hooks -- Do not execute backup hooks
        apps -- List of application names to backup
        ignore_apps -- Do not backup apps

    """
    # TODO: Add a 'clean' argument to clean output directory
    tmp_dir = None
    env_var = {}

    # Validate what to backup
    if ignore_hooks and ignore_apps:
        raise MoulinetteError(errno.EINVAL, m18n.n('backup_action_required'))

    # Validate and define backup name
    timestamp = int(time.time())
    if not name:
        name = time.strftime('%Y%m%d-%H%M%S')
    if name in backup_list()['archives']:
        raise MoulinetteError(errno.EINVAL,
                              m18n.n('backup_archive_name_exists'))

    # Validate additional arguments
    if no_compress and not output_directory:
        raise MoulinetteError(errno.EINVAL,
                              m18n.n('backup_output_directory_required'))
    if output_directory:
        output_directory = os.path.abspath(output_directory)

        # Check for forbidden folders
        if output_directory.startswith(archives_path) or \
           re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$',
                    output_directory):
            raise MoulinetteError(errno.EINVAL,
                                  m18n.n('backup_output_directory_forbidden'))

        # Create the output directory
        if not os.path.isdir(output_directory):
            logger.debug("creating output directory '%s'", output_directory)
            os.makedirs(output_directory, 0750)
        # Check that output directory is empty
        elif no_compress and os.listdir(output_directory):
            raise MoulinetteError(errno.EIO,
                                  m18n.n('backup_output_directory_not_empty'))

        # Do not compress, so set temporary directory to output one and
        # disable bind mounting to prevent data loss in case of a rm
        # See: https://dev.yunohost.org/issues/298
        if no_compress:
            logger.debug('bind mounting will be disabled')
            tmp_dir = output_directory
            env_var['CAN_BIND'] = 0
    else:
        output_directory = archives_path

    # Create archives directory if it does not exists
    if not os.path.isdir(archives_path):
        os.mkdir(archives_path, 0750)

    def _clean_tmp_dir(retcode=0):
        ret = hook_callback('post_backup_create', args=[tmp_dir, retcode])
        if not ret['failed']:
            filesystem.rm(tmp_dir, True, True)
            return True
        else:
            logger.warning(m18n.n('backup_cleaning_failed'))
            return False

    # Create temporary directory
    if not tmp_dir:
        tmp_dir = "%s/tmp/%s" % (backup_path, name)
        if os.path.isdir(tmp_dir):
            logger.debug("temporary directory for backup '%s' already exists",
                         tmp_dir)
            if not _clean_tmp_dir():
                raise MoulinetteError(
                    errno.EIO, m18n.n('backup_output_directory_not_empty'))
        filesystem.mkdir(tmp_dir, 0750, parents=True, uid='admin')

    # Initialize backup info
    info = {
        'description': description or '',
        'created_at': timestamp,
        'apps': {},
        'hooks': {},
    }

    # Run system hooks
    if not ignore_hooks:
        # Check hooks availibility
        hooks_filtered = set()
        if hooks:
            for hook in hooks:
                try:
                    hook_info('backup', hook)
                except:
                    logger.error(m18n.n('backup_hook_unknown', hook=hook))
                else:
                    hooks_filtered.add(hook)

        if not hooks or hooks_filtered:
            logger.info(m18n.n('backup_running_hooks'))
            ret = hook_callback('backup',
                                hooks_filtered,
                                args=[tmp_dir],
                                env=env_var)
            if ret['succeed']:
                info['hooks'] = ret['succeed']

                # Save relevant restoration hooks
                tmp_hooks_dir = tmp_dir + '/hooks/restore'
                filesystem.mkdir(tmp_hooks_dir, 0750, True, uid='admin')
                for h in ret['succeed'].keys():
                    try:
                        i = hook_info('restore', h)
                    except:
                        logger.warning(m18n.n('restore_hook_unavailable',
                                              hook=h),
                                       exc_info=1)
                    else:
                        for f in i['hooks']:
                            shutil.copy(f['path'], tmp_hooks_dir)

    # Backup apps
    if not ignore_apps:
        # Filter applications to backup
        apps_list = set(os.listdir('/etc/yunohost/apps'))
        apps_filtered = set()
        if apps:
            for a in apps:
                if a not in apps_list:
                    logger.warning(m18n.n('unbackup_app', app=a))
                else:
                    apps_filtered.add(a)
        else:
            apps_filtered = apps_list

        # Run apps backup scripts
        tmp_script = '/tmp/backup_' + str(timestamp)
        for app_instance_name in apps_filtered:
            app_setting_path = '/etc/yunohost/apps/' + app_instance_name

            # Check if the app has a backup and restore script
            app_script = app_setting_path + '/scripts/backup'
            app_restore_script = app_setting_path + '/scripts/restore'
            if not os.path.isfile(app_script):
                logger.warning(m18n.n('unbackup_app', app=app_instance_name))
                continue
            elif not os.path.isfile(app_restore_script):
                logger.warning(m18n.n('unrestore_app', app=app_instance_name))

            tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_instance_name)
            tmp_app_bkp_dir = tmp_app_dir + '/backup'
            logger.info(
                m18n.n('backup_running_app_script', app=app_instance_name))
            try:
                # Prepare backup directory for the app
                filesystem.mkdir(tmp_app_bkp_dir, 0750, True, uid='admin')
                shutil.copytree(app_setting_path, tmp_app_dir + '/settings')

                # Copy app backup script in a temporary folder and execute it
                subprocess.call(['install', '-Dm555', app_script, tmp_script])

                # Prepare env. var. to pass to script
                app_id, app_instance_nb = _parse_app_instance_name(
                    app_instance_name)
                env_dict = env_var.copy()
                env_dict["YNH_APP_ID"] = app_id
                env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name
                env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
                env_dict["YNH_APP_BACKUP_DIR"] = tmp_app_bkp_dir

                hook_exec(tmp_script,
                          args=[tmp_app_bkp_dir, app_instance_name],
                          raise_on_error=True,
                          chdir=tmp_app_bkp_dir,
                          env=env_dict)
            except:
                logger.exception(
                    m18n.n('backup_app_failed', app=app_instance_name))
                # Cleaning app backup directory
                shutil.rmtree(tmp_app_dir, ignore_errors=True)
            else:
                # Add app info
                i = app_info(app_instance_name)
                info['apps'][app_instance_name] = {
                    'version': i['version'],
                    'name': i['name'],
                    'description': i['description'],
                }
            finally:
                filesystem.rm(tmp_script, force=True)

    # Check if something has been saved
    if not info['hooks'] and not info['apps']:
        _clean_tmp_dir(1)
        raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done'))

    # Calculate total size
    backup_size = int(
        subprocess.check_output(['du', '-sb',
                                 tmp_dir]).split()[0].decode('utf-8'))
    info['size'] = backup_size

    # Create backup info file
    with open("%s/info.json" % tmp_dir, 'w') as f:
        f.write(json.dumps(info))

    # Create the archive
    if not no_compress:
        logger.info(m18n.n('backup_creating_archive'))

        # Check free space in output directory at first
        avail_output = subprocess.check_output(
            ['df', '--block-size=1', '--output=avail', tmp_dir]).split()
        if len(avail_output) < 2 or int(avail_output[1]) < backup_size:
            logger.debug('not enough space at %s (free: %s / needed: %d)',
                         output_directory, avail_output[1], backup_size)
            _clean_tmp_dir(3)
            raise MoulinetteError(
                errno.EIO,
                m18n.n('not_enough_disk_space', path=output_directory))

        # Open archive file for writing
        archive_file = "%s/%s.tar.gz" % (output_directory, name)
        try:
            tar = tarfile.open(archive_file, "w:gz")
        except:
            logger.debug("unable to open '%s' for writing",
                         archive_file,
                         exc_info=1)
            _clean_tmp_dir(2)
            raise MoulinetteError(errno.EIO,
                                  m18n.n('backup_archive_open_failed'))

        # Add files to the archive
        try:
            tar.add(tmp_dir, arcname='')
            tar.close()
        except IOError as e:
            logger.error(m18n.n('backup_archive_writing_error'), exc_info=1)
            _clean_tmp_dir(3)
            raise MoulinetteError(errno.EIO, m18n.n('backup_creation_failed'))

        # FIXME : it looks weird that the "move info file" is not enabled if
        # user activated "no_compress" ... or does it really means
        # "dont_keep_track_of_this_backup_in_history" ?

        # Move info file
        shutil.move(tmp_dir + '/info.json',
                    '{:s}/{:s}.info.json'.format(archives_path, name))

        # If backuped to a non-default location, keep a symlink of the archive
        # to that location
        if output_directory != archives_path:
            link = "%s/%s.tar.gz" % (archives_path, name)
            os.symlink(archive_file, link)

    # Clean temporary directory
    if tmp_dir != output_directory:
        _clean_tmp_dir()

    logger.success(m18n.n('backup_created'))

    # Return backup info
    info['name'] = name
    return {'archive': info}
Exemple #18
0
def firewall_reload(skip_upnp=False):
    """
    Reload all firewall rules

    Keyword arguments:
        skip_upnp -- Do not refresh port forwarding using UPnP

    """
    from yunohost.hook import hook_callback

    reloaded = False
    errors = False

    # Check if SSH port is allowed
    ssh_port = _get_ssh_port()
    if ssh_port not in firewall_list()['opened_ports']:
        firewall_allow('TCP', ssh_port, no_reload=True)

    # Retrieve firewall rules and UPnP status
    firewall = firewall_list(raw=True)
    upnp = firewall_upnp()['enabled'] if not skip_upnp else False

    # IPv4
    try:
        process.check_output("iptables -L")
    except process.CalledProcessError as e:
        logger.debug('iptables seems to be not available, it outputs:\n%s',
                     prependlines(e.output.rstrip(), '> '))
        logger.warning(m18n.n('iptables_unavailable'))
    else:
        rules = [
            "iptables -F",
            "iptables -X",
            "iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT",
        ]
        # Iterate over ports and add rule
        for protocol in ['TCP', 'UDP']:
            for port in firewall['ipv4'][protocol]:
                rules.append("iptables -A INPUT -p %s --dport %s -j ACCEPT" \
                                 % (protocol, process.quote(str(port))))
        rules += [
            "iptables -A INPUT -i lo -j ACCEPT",
            "iptables -A INPUT -p icmp -j ACCEPT",
            "iptables -P INPUT DROP",
        ]

        # Execute each rule
        if process.check_commands(rules, callback=_on_rule_command_error):
            errors = True
        reloaded = True

    # IPv6
    try:
        process.check_output("ip6tables -L")
    except process.CalledProcessError as e:
        logger.debug('ip6tables seems to be not available, it outputs:\n%s',
                     prependlines(e.output.rstrip(), '> '))
        logger.warning(m18n.n('ip6tables_unavailable'))
    else:
        rules = [
            "ip6tables -F",
            "ip6tables -X",
            "ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT",
        ]
        # Iterate over ports and add rule
        for protocol in ['TCP', 'UDP']:
            for port in firewall['ipv6'][protocol]:
                rules.append("ip6tables -A INPUT -p %s --dport %s -j ACCEPT" \
                                 % (protocol, process.quote(str(port))))
        rules += [
            "ip6tables -A INPUT -i lo -j ACCEPT",
            "ip6tables -A INPUT -p icmpv6 -j ACCEPT",
            "ip6tables -P INPUT DROP",
        ]

        # Execute each rule
        if process.check_commands(rules, callback=_on_rule_command_error):
            errors = True
        reloaded = True

    if not reloaded:
        raise MoulinetteError(errno.ESRCH, m18n.n('firewall_reload_failed'))

    hook_callback('post_iptable_rules',
                  args=[upnp, os.path.exists("/proc/net/if_inet6")])

    if upnp:
        # Refresh port forwarding with UPnP
        firewall_upnp(no_refresh=False)

    # TODO: Use service_restart
    os.system("service fail2ban restart")

    if errors:
        logger.warning(m18n.n('firewall_rules_cmd_failed'))
    else:
        logger.success(m18n.n('firewall_reloaded'))
    return firewall_list()
Exemple #19
0
def tools_postinstall(domain, password, ignore_dyndns=False):
    """
    YunoHost post-install

    Keyword argument:
        domain -- YunoHost main domain
        ignore_dyndns -- Do not subscribe domain to a DynDNS service (only
        needed for nohost.me, noho.st domains)
        password -- YunoHost admin password

    """
    dyndns = not ignore_dyndns

    # Do some checks at first
    if os.path.isfile('/etc/yunohost/installed'):
        raise MoulinetteError(errno.EPERM,
                              m18n.n('yunohost_already_installed'))
    if len(domain.split('.')) >= 3 and not ignore_dyndns:
        try:
            r = requests.get('https://dyndns.yunohost.org/domains')
        except requests.ConnectionError:
            pass
        else:
            dyndomains = json.loads(r.text)
            dyndomain = '.'.join(domain.split('.')[1:])

            if dyndomain in dyndomains:
                if requests.get('https://dyndns.yunohost.org/test/%s' %
                                domain).status_code == 200:
                    dyndns = True
                else:
                    raise MoulinetteError(errno.EEXIST,
                                          m18n.n('dyndns_unavailable'))
            else:
                dyndns = False
    else:
        dyndns = False

    logger.info(m18n.n('yunohost_installing'))

    # Initialize LDAP for YunoHost
    # TODO: Improve this part by integrate ldapinit into conf_regen hook
    auth = tools_ldapinit()

    # Create required folders
    folders_to_create = [
        '/etc/yunohost/apps', '/etc/yunohost/certs',
        '/var/cache/yunohost/repo', '/home/yunohost.backup',
        '/home/yunohost.app'
    ]

    for folder in folders_to_create:
        try:
            os.listdir(folder)
        except OSError:
            os.makedirs(folder)

    # Change folders permissions
    os.system('chmod 755 /home/yunohost.app')

    # Set hostname to avoid amavis bug
    if os.system('hostname -d') != 0:
        os.system('hostname yunohost.yunohost.org')

    # Add a temporary SSOwat rule to redirect SSO to admin page
    try:
        with open('/etc/ssowat/conf.json.persistent') as json_conf:
            ssowat_conf = json.loads(str(json_conf.read()))
    except ValueError as e:
        raise MoulinetteError(
            errno.EINVAL,
            m18n.n('ssowat_persistent_conf_read_error', error=e.strerror))
    except IOError:
        ssowat_conf = {}

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

    ssowat_conf['redirected_urls']['/'] = domain + '/yunohost/admin'

    try:
        with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
            json.dump(ssowat_conf, f, sort_keys=True, indent=4)
    except IOError as e:
        raise MoulinetteError(
            errno.EPERM,
            m18n.n('ssowat_persistent_conf_write_error', error=e.strerror))

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

    # Create SSL CA
    service_regen_conf(['ssl'], force=True)
    ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
    command_list = [
        'echo "01" > %s/serial' % ssl_dir,
        'rm %s/index.txt' % ssl_dir,
        'touch %s/index.txt' % ssl_dir,
        'cp %s/openssl.cnf %s/openssl.ca.cnf' % (ssl_dir, ssl_dir),
        'sed -i "s/yunohost.org/%s/g" %s/openssl.ca.cnf ' % (domain, ssl_dir),
        'openssl req -x509 -new -config %s/openssl.ca.cnf -days 3650 -out %s/ca/cacert.pem -keyout %s/ca/cakey.pem -nodes -batch'
        % (ssl_dir, ssl_dir, ssl_dir),
        'cp %s/ca/cacert.pem /etc/ssl/certs/ca-yunohost_crt.pem' % ssl_dir,
        'update-ca-certificates'
    ]

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

    # New domain config
    domain_add(auth, domain, dyndns)
    tools_maindomain(auth, domain)

    # Generate SSOwat configuration file
    app_ssowatconf(auth)

    # Change LDAP admin password
    tools_adminpw(auth, password)

    # Enable UPnP silently and reload firewall
    firewall_upnp('enable', no_refresh=True)

    os.system('touch /etc/yunohost/installed')

    # Enable and start YunoHost firewall at boot time
    os.system('update-rc.d yunohost-firewall enable')
    os.system('service yunohost-firewall start')

    service_regen_conf(force=True)
    logger.success(m18n.n('yunohost_configured'))
Exemple #20
0
def firewall_upnp(action='status', no_refresh=False):
    """
    Manage port forwarding using UPnP

    Note: 'reload' action is deprecated and will be removed in the near
    future. You should use 'status' instead - which retrieve UPnP status
    and automatically refresh port forwarding if 'no_refresh' is False.

    Keyword argument:
        action -- Action to perform
        no_refresh -- Do not refresh port forwarding

    """
    firewall = firewall_list(raw=True)
    enabled = firewall['uPnP']['enabled']

    # Compatibility with previous version
    if action == 'reload':
        logger.info("'reload' action is deprecated and will be removed")
        try:
            # Remove old cron job
            os.remove('/etc/cron.d/yunohost-firewall')
        except:
            pass
        action = 'status'
        no_refresh = False

    if action == 'status' and no_refresh:
        # Only return current state
        return {'enabled': enabled}
    elif action == 'enable' or (enabled and action == 'status'):
        # Add cron job
        with open(upnp_cron_job, 'w+') as f:
            f.write('*/50 * * * * root '
                    '/usr/bin/yunohost firewall upnp status >>/dev/null\n')
        # Open port 1900 to receive discovery message
        if 1900 not in firewall['ipv4']['UDP']:
            firewall_allow('UDP', 1900, no_upnp=True, no_reload=True)
            if not enabled:
                firewall_reload(skip_upnp=True)
        enabled = True
    elif action == 'disable' or (not enabled and action == 'status'):
        try:
            # Remove cron job
            os.remove(upnp_cron_job)
        except:
            pass
        enabled = False
        if action == 'status':
            no_refresh = True
    else:
        raise MoulinetteError(errno.EINVAL,
                              m18n.n('action_invalid', action=action))

    # Refresh port mapping using UPnP
    if not no_refresh:
        upnpc = miniupnpc.UPnP()
        upnpc.discoverdelay = 3000

        # Discover UPnP device(s)
        logger.debug('discovering UPnP devices...')
        nb_dev = upnpc.discover()
        logger.debug('found %d UPnP device(s)', int(nb_dev))
        if nb_dev < 1:
            logger.error(m18n.n('upnp_dev_not_found'))
            enabled = False
        else:
            try:
                # Select UPnP device
                upnpc.selectigd()
            except:
                logger.info('unable to select UPnP device', exc_info=1)
                enabled = False
            else:
                # Iterate over ports
                for protocol in ['TCP', 'UDP']:
                    for port in firewall['uPnP'][protocol]:
                        # Clean the mapping of this port
                        if upnpc.getspecificportmapping(port, protocol):
                            try:
                                upnpc.deleteportmapping(port, protocol)
                            except:
                                pass
                        if not enabled:
                            continue
                        try:
                            # Add new port mapping
                            upnpc.addportmapping(
                                port, protocol, upnpc.lanaddr, port,
                                'yunohost firewall: port %d' % port, '')
                        except:
                            logger.info('unable to add port %d using UPnP',
                                        port,
                                        exc_info=1)
                            enabled = False

    if enabled != firewall['uPnP']['enabled']:
        firewall = firewall_list(raw=True)
        firewall['uPnP']['enabled'] = enabled

        # Make a backup and update firewall file
        os.system("cp {0} {0}.old".format(firewall_file))
        with open(firewall_file, 'w') as f:
            yaml.safe_dump(firewall, f, default_flow_style=False)

        if not no_refresh:
            # Display success message if needed
            if action == 'enable' and enabled:
                logger.success(m18n.n('upnp_enabled'))
            elif action == 'disable' and not enabled:
                logger.success(m18n.n('upnp_disabled'))
            # Make sure to disable UPnP
            elif action != 'disable' and not enabled:
                firewall_upnp('disable', no_refresh=True)

    if not enabled and (action == 'enable' or 1900 in firewall['ipv4']['UDP']):
        # Close unused port 1900
        firewall_disallow('UDP', 1900, no_reload=True)
        if not no_refresh:
            firewall_reload(skip_upnp=True)

    if action == 'enable' and not enabled:
        raise MoulinetteError(errno.ENXIO, m18n.n('upnp_port_open_failed'))
    return {'enabled': enabled}
Exemple #21
0
def tools_ldapinit():
    """
    YunoHost LDAP initialization


    """

    # Instantiate LDAP Authenticator
    auth = init_authenticator(
        ('ldap', 'default'), {
            'uri': "ldap://localhost:389",
            'base_dn': "dc=yunohost,dc=org",
            'user_rdn': "cn=admin"
        })
    auth.authenticate('yunohost')

    with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml'
              ) as f:
        ldap_map = yaml.load(f)

    for rdn, attr_dict in ldap_map['parents'].items():
        try:
            auth.add(rdn, attr_dict)
        except:
            pass

    for rdn, attr_dict in ldap_map['children'].items():
        try:
            auth.add(rdn, attr_dict)
        except:
            pass

    admin_dict = {
        'cn':
        'admin',
        'uid':
        'admin',
        'description':
        'LDAP Administrator',
        'gidNumber':
        '1007',
        'uidNumber':
        '1007',
        'homeDirectory':
        '/home/admin',
        'loginShell':
        '/bin/bash',
        'objectClass':
        ['organizationalRole', 'posixAccount', 'simpleSecurityObject'],
        'userPassword':
        '******'
    }

    auth.update('cn=admin', admin_dict)

    # Force nscd to refresh cache to take admin creation into account
    subprocess.call(['nscd', '-i', 'passwd'])

    # Check admin actually exists now
    try:
        pwd.getpwnam("admin")
    except KeyError:
        logger.error(m18n.n('ldap_init_failed_to_create_admin'))
        raise MoulinetteError(errno.EINVAL, m18n.n('installation_failed'))

    logger.success(m18n.n('ldap_initialized'))
    return auth
Exemple #22
0
def firewall_reload():
    """
    Reload all firewall rules


    """
    from yunohost.hook import hook_callback

    firewall = firewall_list(raw=True)
    upnp = firewall['uPnP']['enabled']

    # IPv4
    if os.system("iptables -P INPUT ACCEPT") != 0:
        raise MoulinetteError(errno.ESRCH, m18n.n('iptables_unavailable'))
    if upnp:
        firewall_upnp(action=['reload'])

    os.system("iptables -F")
    os.system("iptables -X")
    os.system(
        "iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT")

    if ssh_port not in firewall['ipv4']['TCP']:
        firewall_allow(ssh_port)

    # Loop
    for protocol in ['TCP', 'UDP']:
        for port in firewall['ipv4'][protocol]:
            os.system("iptables -A INPUT -p %s --dport %d -j ACCEPT" %
                      (protocol, port))

    hook_callback('post_iptable_rules',
                  [upnp, os.path.exists("/proc/net/if_inet6")])

    os.system("iptables -A INPUT -i lo -j ACCEPT")
    os.system("iptables -A INPUT -p icmp -j ACCEPT")
    os.system("iptables -P INPUT DROP")

    # IPv6
    if os.path.exists("/proc/net/if_inet6"):
        os.system("ip6tables -P INPUT ACCEPT")
        os.system("ip6tables -F")
        os.system("ip6tables -X")
        os.system(
            "ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT"
        )

        if ssh_port not in firewall['ipv6']['TCP']:
            firewall_allow(ssh_port, ipv6=True)

        # Loop v6
        for protocol in ['TCP', 'UDP']:
            for port in firewall['ipv6'][protocol]:
                os.system("ip6tables -A INPUT -p %s --dport %d -j ACCEPT" %
                          (protocol, port))

        os.system("ip6tables -A INPUT -i lo -j ACCEPT")
        os.system("ip6tables -A INPUT -p icmpv6 -j ACCEPT")
        os.system("ip6tables -P INPUT DROP")

    os.system("service fail2ban restart")
    msignals.display(m18n.n('firewall_reloaded'), 'success')

    return firewall_list()
Exemple #23
0
def monitor_update_stats(period):
    """
    Update monitoring statistics

    Keyword argument:
        period -- Time period to update (day, week, month)

    """
    if period not in ['day', 'week', 'month']:
        raise MoulinetteError(errno.EINVAL, m18n.n('monitor_period_invalid'))

    stats = _retrieve_stats(period)
    if not stats:
        stats = { 'disk': {}, 'network': {}, 'system': {}, 'timestamp': [] }

    monitor = None
    # Get monitoring stats
    if period == 'day':
        monitor = _monitor_all('day')
    else:
        t = stats['timestamp']
        p = 'day' if period == 'week' else 'week'
        if len(t) > 0:
            monitor = _monitor_all(p, t[len(t) - 1])
        else:
            monitor = _monitor_all(p, 0)
    if not monitor:
        raise MoulinetteError(errno.ENODATA, m18n.n('monitor_stats_no_update'))

    stats['timestamp'].append(time.time())

    # Append disk stats
    for dname, units in monitor['disk'].items():
        disk = {}
        # Retrieve current stats for disk name
        if dname in stats['disk'].keys():
            disk = stats['disk'][dname]

        for unit, values in units.items():
            # Continue if unit doesn't contain stats
            if not isinstance(values, dict):
                continue

            # Retrieve current stats for unit and append new ones
            curr = disk[unit] if unit in disk.keys() else {}
            if unit == 'io':
                disk[unit] = _append_to_stats(curr, values, 'time_since_update')
            elif unit == 'filesystem':
                disk[unit] = _append_to_stats(curr, values, ['fs_type', 'mnt_point'])
        stats['disk'][dname] = disk

    # Append network stats
    net_usage = {}
    for iname, values in monitor['network']['usage'].items():
        # Continue if units doesn't contain stats
        if not isinstance(values, dict):
            continue

        # Retrieve current stats and append new ones
        curr = {}
        if 'usage' in stats['network'] and iname in stats['network']['usage']:
            curr = stats['network']['usage'][iname]
        net_usage[iname] = _append_to_stats(curr, values, 'time_since_update')
    stats['network'] = { 'usage': net_usage, 'infos': monitor['network']['infos'] }

    # Append system stats
    for unit, values in monitor['system'].items():
        # Continue if units doesn't contain stats
        if not isinstance(values, dict):
            continue

        # Set static infos unit
        if unit == 'infos':
            stats['system'][unit] = values
            continue

        # Retrieve current stats and append new ones
        curr = stats['system'][unit] if unit in stats['system'].keys() else {}
        stats['system'][unit] = _append_to_stats(curr, values)

    _save_stats(stats, period)
Exemple #24
0
def firewall_upnp(action=None):
    """
    Add uPnP cron and enable uPnP in firewall.yml, or the opposite.

    Keyword argument:
        action -- enable/disable/reload

    """
    firewall = firewall_list(raw=True)

    if action:
        action = action[0]

    if action == 'enable':
        firewall['uPnP']['enabled'] = True

        with open('/etc/cron.d/yunohost-firewall', 'w+') as f:
            f.write(
                'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
            \n*/50 * * * * root yunohost firewall upnp reload >>/dev/null')

        msignals.display(m18n.n('upnp_enabled'), 'success')

    if action == 'disable':
        firewall['uPnP']['enabled'] = False

        try:
            upnpc = miniupnpc.UPnP()
            upnpc.discoverdelay = 3000
            if upnpc.discover() == 1:
                upnpc.selectigd()
                for protocol in ['TCP', 'UDP']:
                    for port in firewall['uPnP'][protocol]:
                        if upnpc.getspecificportmapping(port, protocol):
                            try:
                                upnpc.deleteportmapping(port, protocol)
                            except:
                                pass
        except:
            pass

        try:
            os.remove('/etc/cron.d/yunohost-firewall')
        except:
            pass

        msignals.display(m18n.n('upnp_disabled'), 'success')

    if action == 'reload':
        upnp = firewall['uPnP']['enabled']

        if upnp:
            try:
                upnpc = miniupnpc.UPnP()
                upnpc.discoverdelay = 3000
                if upnpc.discover() == 1:
                    upnpc.selectigd()
                    for protocol in ['TCP', 'UDP']:
                        for port in firewall['uPnP'][protocol]:
                            if upnpc.getspecificportmapping(port, protocol):
                                try:
                                    upnpc.deleteportmapping(port, protocol)
                                except:
                                    pass
                            upnpc.addportmapping(
                                port, protocol, upnpc.lanaddr, port,
                                'yunohost firewall : port %d' % port, '')
                else:
                    raise MoulinetteError(errno.ENXIO,
                                          m18n.n('upnp_dev_not_found'))
            except:
                msignals.display(m18n.n('upnp_port_open_failed'), 'warning')

    if action:
        os.system(
            "cp /etc/yunohost/firewall.yml /etc/yunohost/firewall.yml.old")
        with open('/etc/yunohost/firewall.yml', 'w') as f:
            yaml.safe_dump(firewall, f, default_flow_style=False)

    return {"enabled": firewall['uPnP']['enabled']}
Exemple #25
0
def dyndns_update(dyn_host="dyndns.yunohost.org",
                  domain=None,
                  key=None,
                  ipv4=None,
                  ipv6=None):
    """
    Update IP on DynDNS platform

    Keyword argument:
        domain -- Full domain to update
        dyn_host -- Dynette DNS server to inform
        key -- Public DNS key
        ipv4 -- IP address to send
        ipv6 -- IPv6 address to send

    """
    # IPv4
    if ipv4 is None:
        ipv4 = get_public_ip()

    try:
        with open('/etc/yunohost/dyndns/old_ip', 'r') as f:
            old_ip = f.readline().rstrip()
    except IOError:
        old_ip = '0.0.0.0'

    # IPv6
    if ipv6 is None:
        try:
            ip_route_out = subprocess.check_output(
                ['ip', 'route', 'get', '2000::']).split('\n')

            if len(ip_route_out) > 0:
                route = IPRouteLine(ip_route_out[0])
                if not route.unreachable:
                    ipv6 = route.src_addr

        except (OSError, ValueError) as e:
            # Unlikely case "ip route" does not return status 0
            # or produces unexpected output
            raise MoulinetteError(errno.EBADMSG,
                                  "ip route cmd error : {}".format(e))

        if ipv6 is None:
            logger.info(m18n.n('no_ipv6_connectivity'))

    try:
        with open('/etc/yunohost/dyndns/old_ipv6', 'r') as f:
            old_ipv6 = f.readline().rstrip()
    except IOError:
        old_ipv6 = '0000:0000:0000:0000:0000:0000:0000:0000'

    if old_ip != ipv4 or old_ipv6 != ipv6:
        if domain is None:
            # Retrieve the first registered domain
            for path in glob.iglob('/etc/yunohost/dyndns/K*.private'):
                match = re_dyndns_private_key.match(path)
                if not match:
                    continue
                _domain = match.group('domain')
                try:
                    # Check if domain is registered
                    if requests.get('https://{0}/test/{1}'.format(
                            dyn_host, _domain)).status_code == 200:
                        continue
                except requests.ConnectionError:
                    raise MoulinetteError(errno.ENETUNREACH,
                                          m18n.n('no_internet_connection'))
                domain = _domain
                key = path
                break
            if not domain:
                raise MoulinetteError(errno.EINVAL,
                                      m18n.n('dyndns_no_domain_registered'))

        if key is None:
            keys = glob.glob(
                '/etc/yunohost/dyndns/K{0}.+*.private'.format(domain))
            if len(keys) > 0:
                key = keys[0]
        if not key:
            raise MoulinetteError(errno.EIO, m18n.n('dyndns_key_not_found'))

        host = domain.split('.')[1:]
        host = '.'.join(host)
        lines = [
            'server %s' % dyn_host,
            'zone %s' % host,
            'update delete %s. A' % domain,
            'update delete %s. AAAA' % domain,
            'update delete %s. MX' % domain,
            'update delete %s. TXT' % domain,
            'update delete pubsub.%s. A' % domain,
            'update delete pubsub.%s. AAAA' % domain,
            'update delete muc.%s. A' % domain,
            'update delete muc.%s. AAAA' % domain,
            'update delete vjud.%s. A' % domain,
            'update delete vjud.%s. AAAA' % domain,
            'update delete _xmpp-client._tcp.%s. SRV' % domain,
            'update delete _xmpp-server._tcp.%s. SRV' % domain,
            'update add %s. 1800 A %s' % (domain, ipv4),
            'update add %s. 14400 MX 5 %s.' % (domain, domain),
            'update add %s. 14400 TXT "v=spf1 a mx -all"' % domain,
            'update add pubsub.%s. 1800 A %s' % (domain, ipv4),
            'update add muc.%s. 1800 A %s' % (domain, ipv4),
            'update add vjud.%s. 1800 A %s' % (domain, ipv4),
            'update add _xmpp-client._tcp.%s. 14400 SRV 0 5 5222 %s.' %
            (domain, domain),
            'update add _xmpp-server._tcp.%s. 14400 SRV 0 5 5269 %s.' %
            (domain, domain)
        ]
        if ipv6 is not None:
            lines += [
                'update add %s. 1800 AAAA %s' % (domain, ipv6),
                'update add pubsub.%s. 1800 AAAA %s' % (domain, ipv6),
                'update add muc.%s. 1800 AAAA %s' % (domain, ipv6),
                'update add vjud.%s. 1800 AAAA %s' % (domain, ipv6),
            ]
        lines += ['show', 'send']
        with open('/etc/yunohost/dyndns/zone', 'w') as zone:
            for line in lines:
                zone.write(line + '\n')

        if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' %
                     key) == 0:
            logger.success(m18n.n('dyndns_ip_updated'))
            with open('/etc/yunohost/dyndns/old_ip', 'w') as f:
                f.write(ipv4)
            if ipv6 is not None:
                with open('/etc/yunohost/dyndns/old_ipv6', 'w') as f:
                    f.write(ipv6)
        else:
            os.system('rm -f /etc/yunohost/dyndns/old_ip')
            os.system('rm -f /etc/yunohost/dyndns/old_ipv6')
            raise MoulinetteError(errno.EPERM,
                                  m18n.n('dyndns_ip_update_failed'))
Exemple #26
0
def service_status(names=[]):
    """
    Show status information about one or more services (all by default)

    Keyword argument:
        names -- Services name to show

    """
    services = _get_services()
    check_names = True
    result = {}

    if isinstance(names, str):
        names = [names]
    elif len(names) == 0:
        names = services.keys()
        check_names = False

    for name in names:
        if check_names and name not in services.keys():
            raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown',
                                                       name))

        status = None
        if services[name]['status'] == 'service':
            status = 'service %s status' % name
        else:
            status = str(services[name]['status'])

        runlevel = 5
        if 'runlevel' in services[name].keys():
            runlevel = int(services[name]['runlevel'])

        result[name] = {'status': 'unknown', 'loaded': 'unknown'}

        # Retrieve service status
        try:
            ret = subprocess.check_output(status,
                                          stderr=subprocess.STDOUT,
                                          shell=True)
        except subprocess.CalledProcessError as e:
            if 'usage:' in e.output.lower():
                msignals.display(m18n.n('service_status_failed', name),
                                 'warning')
            else:
                result[name]['status'] = 'inactive'
        else:
            result[name]['status'] = 'running'

        # Retrieve service loading
        rc_path = glob.glob("/etc/rc%d.d/S[0-9][0-9]%s" % (runlevel, name))
        if len(rc_path) == 1 and os.path.islink(rc_path[0]):
            result[name]['loaded'] = 'enabled'
        elif os.path.isfile("/etc/init.d/%s" % name):
            result[name]['loaded'] = 'disabled'
        else:
            result[name]['loaded'] = 'not-found'

    if len(names) == 1:
        return result[names[0]]
    return result
Exemple #27
0
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'))
Exemple #28
0
    def _authenticate_credentials(self, credentials=None):

        if not credentials == self.name:
            raise MoulinetteError("invalid_password", raw_msg=True)

        return
Exemple #29
0
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()))
        except ValueError as e:
            raise MoulinetteError(
                errno.EINVAL,
                m18n.n('ssowat_persistent_conf_read_error', error=e.strerror))
        except IOError:
            ssowat_conf = {}

        if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf[
                'redirected_urls']:
            del ssowat_conf['redirected_urls']['/']
            try:
                with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
                    json.dump(ssowat_conf, f, sort_keys=True, indent=4)
            except IOError as e:
                raise MoulinetteError(
                    errno.EPERM,
                    m18n.n('ssowat_persistent_conf_write_error',
                           error=e.strerror))

    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'))
Exemple #30
0
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')