def process(self, args, timeout=None, **kwargs): """ Parse arguments and process the proper action Keyword arguments: - args -- The arguments to parse - timeout -- The time period before failing if the lock cannot be acquired for the action - **kwargs -- Additional interface arguments """ # Perform authentication if needed self.check_authentication_if_required(args, **kwargs) # Parse arguments arguments = vars(self.parser.parse_args(args, **kwargs)) # Retrieve tid and parse arguments with extra parameters tid = arguments.pop("_tid") arguments = self.extraparser.parse_args(tid, arguments) # Return immediately if a value is defined if TO_RETURN_PROP in arguments: return arguments.get(TO_RETURN_PROP) # Retrieve action information if len(tid) == 4: namespace, category, subcategory, action = tid func_name = "{}_{}_{}".format( category, subcategory.replace("-", "_"), action.replace("-", "_"), ) full_action_name = "{}.{}.{}.{}".format( namespace, category, subcategory, action, ) else: assert len(tid) == 3 namespace, category, action = tid subcategory = None func_name = "{}_{}".format(category, action.replace("-", "_")) full_action_name = "{}.{}.{}".format(namespace, category, action) # Lock the moulinette for the namespace with MoulinetteLock(namespace, timeout, self.enable_lock): start = time() try: mod = __import__( "{}.{}".format(namespace, category), globals=globals(), level=0, fromlist=[func_name], ) logger.debug( "loading python module %s took %.3fs", "{}.{}".format(namespace, category), time() - start, ) func = getattr(mod, func_name) except (AttributeError, ImportError) as e: import traceback traceback.print_exc() error_message = "unable to load function {}.{} because: {}".format( namespace, func_name, e, ) logger.exception(error_message) raise MoulinetteError(error_message, raw_msg=True) else: log_id = start_action_logging() if logger.isEnabledFor(logging.DEBUG): # Log arguments in debug mode only for safety reasons logger.debug( "processing action [%s]: %s with args=%s", log_id, full_action_name, arguments, ) else: logger.debug("processing action [%s]: %s", log_id, full_action_name) # Load translation and process the action start = time() try: return func(**arguments) finally: stop = time() logger.debug("action [%s] executed in %.3fs", log_id, stop - start)
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'))
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' # 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 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) # Execute app restore script subprocess.call(['install', '-Dm555', app_script, tmp_script]) # Prepare env. var. to pass to script env_dict = {} app_id, app_instance_nb = _parse_app_instance_name( app_instance_name) 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('restore_app_failed', 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
def _authenticate_credentials(self, credentials=None): if not credentials == self.name: raise MoulinetteError("invalid_password", raw_msg=True) return
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
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}
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: d['avail'] = d['size'] - d['used'] 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', unit=u)) if result_dname is not None: return result[result_dname] return result
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()
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
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)
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')
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')
def _error(self, message): # TODO: Raise a proper exception raise MoulinetteError(1, message)
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()
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}
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}
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
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']}
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
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 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')) logger.info(m18n.n('yunohost_installing')) # 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') # Initialize LDAP for YunoHost # TODO: Improve this part by integrate ldapinit into conf_regen hook tools_ldapinit(auth) # 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 IOError: ssowat_conf = {} if 'redirected_urls' not in ssowat_conf: ssowat_conf['redirected_urls'] = {} ssowat_conf['redirected_urls']['/'] = domain + '/yunohost/admin' with open('/etc/ssowat/conf.json.persistent', 'w+') as f: json.dump(ssowat_conf, f, sort_keys=True, indent=4) os.system('chmod 644 /etc/ssowat/conf.json.persistent') # 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 tools_maindomain(auth, old_domain='yunohost.org', new_domain=domain, dyndns=dyndns) # 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'))
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')
def user_create(auth, username, firstname, lastname, mail, password): """ Create user Keyword argument: firstname lastname username -- Must be unique mail -- Main mail address must be unique password """ 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', mail[mail.find('@') + 1:])) # Get random UID/GID uid_check = gid_check = 0 while uid_check == 0 and gid_check == 0: uid = str(random.randint(200, 99999)) uid_check = os.system("getent passwd %s" % uid) gid_check = os.system("getent group %s" % uid) # Adapt values for LDAP fullname = '%s %s' % (firstname, lastname) rdn = 'uid=%s,ou=users' % username char_set = string.ascii_uppercase + string.digits salt = ''.join(random.sample(char_set, 8)) salt = '$1$' + salt + '$' pwd = '{CRYPT}' + crypt.crypt(str(password), salt) attr_dict = { 'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount'], 'givenName': firstname, 'sn': lastname, 'displayName': fullname, 'cn': fullname, 'uid': username, 'mail': mail, 'maildrop': username, 'userPassword': pwd, 'gidNumber': uid, 'uidNumber': uid, 'homeDirectory': '/home/' + username, 'loginShell': '/bin/false' } # If it is the first user, add some aliases if not auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'): with open('/etc/yunohost/current_host') as f: main_domain = f.readline().rstrip() aliases = [ 'root@' + main_domain, 'admin@' + main_domain, 'webmaster@' + main_domain, 'postmaster@' + main_domain, ] attr_dict['mail'] = [attr_dict['mail']] + aliases # If exists, remove the redirection from the SSO try: with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf[ 'redirected_urls']: del ssowat_conf['redirected_urls']['/'] with open('/etc/ssowat/conf.json.persistent', 'w+') as f: json.dump(ssowat_conf, f, sort_keys=True, indent=4) except IOError: pass if auth.add(rdn, attr_dict): # Update SFTP user group memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid'] memberlist.append(username) if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}): os.system("su - %s -c ''" % username) app_ssowatconf(auth) #TODO: Send a welcome mail to user msignals.display(m18n.n('user_created'), 'success') hook_callback('post_user_create', [username, mail, password, firstname, lastname]) return {'fullname': fullname, 'username': username, 'mail': mail} raise MoulinetteError(169, m18n.n('user_creation_failed'))
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')
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 # 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')) # 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.debug("temporary directory for backup '%s' already exists", tmp_dir) filesystem.rm(tmp_dir, recursive=True) filesystem.mkdir(tmp_dir, 0750, parents=True, uid='admin') 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) else: logger.warning(m18n.n('backup_cleaning_failed')) # 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]) 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 env_dict = {} app_id, app_instance_nb = _parse_app_instance_name( app_instance_name) 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 size = subprocess.check_output(['du', '-sb', tmp_dir]).split()[0].decode('utf-8') info['size'] = int(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')) 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.debug("unable to open '%s' for writing", archive_file, exc_info=1) tar = None else: logger.debug("unable to open '%s' for writing", archive_file, exc_info=1) if tar is None: _clean_tmp_dir(2) raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed')) tar.add(tmp_dir, arcname='') tar.close() # Move info file os.rename(tmp_dir + '/info.json', '{:s}/{:s}.info.json'.format(archives_path, name)) # Clean temporary directory if tmp_dir != output_directory: _clean_tmp_dir() logger.success(m18n.n('backup_complete')) # Return backup info info['name'] = name return {'archive': info}
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'))
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'))
def _validate_conf(self, configuration, is_global=False): """Validate configuration for the parser Return the validated configuration for the interface's actions map parser. Keyword arguments: - configuration -- The configuration to pre-format """ # TODO: Create a class with a validator method for each configuration conf = {} # -- 'authenficate' try: ifaces = configuration['authenticate'] except KeyError: pass else: if ifaces == 'all': conf['authenticate'] = ifaces elif ifaces is False: conf['authenticate'] = False elif isinstance(ifaces, list): # Store only if authentication is needed conf[ 'authenticate'] = True if self.interface in ifaces else False else: logger.error( "expecting 'all', 'False' or a list for " "configuration 'authenticate', got %r", ifaces) raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) # -- 'authenticator' try: auth = configuration['authenticator'] except KeyError: pass else: if not is_global and isinstance(auth, str): try: # Store needed authenticator profile conf['authenticator'] = self.global_conf['authenticator'][ auth] except KeyError: logger.error( "requesting profile '%s' which is undefined in " "global configuration of 'authenticator'", auth) raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) elif is_global and isinstance(auth, dict): if len(auth) == 0: logger.warning( 'no profile defined in global configuration ' "for 'authenticator'") else: auths = {} for auth_name, auth_conf in auth.items(): # Add authenticator profile as a 3-tuple # (identifier, configuration, parameters) with # - identifier: the authenticator vendor and its # profile name as a 2-tuple # - configuration: a dict of additional global # configuration (i.e. 'help') # - parameters: a dict of arguments for the # authenticator profile auths[auth_name] = ((auth_conf.get('vendor'), auth_name), { 'help': auth_conf.get('help', None) }, auth_conf.get('parameters', {})) conf['authenticator'] = auths else: logger.error( "expecting a dict of profile(s) or a profile name " "for configuration 'authenticator', got %r", auth) raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) # -- 'argument_auth' try: arg_auth = configuration['argument_auth'] except KeyError: pass else: if isinstance(arg_auth, bool): conf['argument_auth'] = arg_auth else: logger.error( "expecting a boolean for configuration " "'argument_auth', got %r", arg_auth) raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) # -- 'lock' try: lock = configuration['lock'] except KeyError: pass else: if isinstance(lock, bool): conf['lock'] = lock else: logger.error( "expecting a boolean for configuration 'lock', " "got %r", lock) raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) return conf