From f541edbb2a3062d6462addcde5b665add4c2d6f9 Mon Sep 17 00:00:00 2001 From: ariasuni Date: Sat, 26 Aug 2017 20:24:38 +0200 Subject: [PATCH 001/132] [enh] enable gzip compression for common text mimetypes in Nginx --- data/templates/nginx/plain/global.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/nginx/plain/global.conf b/data/templates/nginx/plain/global.conf index b3a5f356a..a3096d009 100644 --- a/data/templates/nginx/plain/global.conf +++ b/data/templates/nginx/plain/global.conf @@ -1 +1,2 @@ server_tokens off; +gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; From e1b5eead3f24535e4825ac080edf65f5157c7623 Mon Sep 17 00:00:00 2001 From: ariasuni Date: Mon, 28 Aug 2017 16:40:50 +0200 Subject: [PATCH 002/132] [fix] disable gzip compression for json to avoid BREACH attack --- data/templates/nginx/plain/global.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/global.conf b/data/templates/nginx/plain/global.conf index a3096d009..341f08620 100644 --- a/data/templates/nginx/plain/global.conf +++ b/data/templates/nginx/plain/global.conf @@ -1,2 +1,2 @@ server_tokens off; -gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; +gzip_types text/plain text/css application/javascript text/xml application/xml application/xml+rss text/javascript; From 75d80848524c00cf85323ada62fb627ee6c93a31 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 28 Aug 2017 21:08:45 +0200 Subject: [PATCH 003/132] [enh] Add hooks on app management operations --- src/yunohost/app.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 476723000..5560fabaf 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -430,7 +430,7 @@ def app_change_url(auth, app, domain, path): path -- New path at which the application will be move """ - from yunohost.hook import hook_exec + from yunohost.hook import hook_exec, hook_callback installed = _is_installed(app) if not installed: @@ -518,6 +518,8 @@ def app_change_url(auth, app, domain, path): logger.success(m18n.n("app_change_url_success", app=app, domain=domain, path=path)) + hook_callback('post_app_change_url', args=args_list, env=env_dict) + def app_upgrade(auth, app=[], url=None, file=None): """ @@ -529,7 +531,8 @@ def app_upgrade(auth, app=[], url=None, file=None): url -- Git url to fetch for upgrade """ - from yunohost.hook import hook_add, hook_remove, hook_exec + from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback + # Retrieve interface is_api = msettings.get('interface') == 'api' @@ -628,6 +631,9 @@ def app_upgrade(auth, app=[], url=None, file=None): upgraded_apps.append(app_instance_name) logger.success(m18n.n('app_upgraded', app=app_instance_name)) + hook_callback('post_app_upgrade', args=args_list, env=env_dict) + + if not upgraded_apps: raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade')) @@ -651,7 +657,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False): no_remove_on_failure -- Debug option to avoid removing the app on a failed installation """ - from yunohost.hook import hook_add, hook_remove, hook_exec + from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback # Fetch or extract sources try: @@ -790,6 +796,8 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False): logger.success(m18n.n('installation_complete')) + hook_callback('post_app_install', args=args_list, env=env_dict) + def app_remove(auth, app): """ @@ -799,7 +807,7 @@ def app_remove(auth, app): app -- App(s) to delete """ - from yunohost.hook import hook_exec, hook_remove + from yunohost.hook import hook_exec, hook_remove, hook_callback if not _is_installed(app): raise MoulinetteError(errno.EINVAL, @@ -828,6 +836,8 @@ def app_remove(auth, app): if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, env=env_dict, user="root") == 0: logger.success(m18n.n('app_removed', app=app)) + hook_callback('post_app_remove', args=args_list, env=env_dict) + if os.path.exists(app_setting_path): shutil.rmtree(app_setting_path) shutil.rmtree('/tmp/yunohost_remove') From 0fc80b256bc38fb244a70e7364a2400360424e68 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 18 Sep 2017 21:17:13 +0200 Subject: [PATCH 004/132] [mod] constant needs to be upper cases --- src/yunohost/dyndns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 621043c3e..7bbf5e568 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -63,7 +63,7 @@ class IPRouteLine(object): for k, v in self.m.groupdict().items(): setattr(self, k, v) -re_dyndns_private_key = re.compile( +RE_DYNDNS_PRIVATE_KEY = re.compile( r'.*/K(?P[^\s\+]+)\.\+157.+\.private$' ) @@ -176,7 +176,7 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, 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) + match = RE_DYNDNS_PRIVATE_KEY.match(path) if not match: continue _domain = match.group('domain') From 622bed116935fcb9740b3c3551eb7fadfe05776f Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 18 Sep 2017 21:29:01 +0200 Subject: [PATCH 005/132] [mod] introduce regex for SHA512 --- src/yunohost/dyndns.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 7bbf5e568..842fdb841 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -63,11 +63,17 @@ class IPRouteLine(object): for k, v in self.m.groupdict().items(): setattr(self, k, v) -RE_DYNDNS_PRIVATE_KEY = re.compile( + +RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile( r'.*/K(?P[^\s\+]+)\.\+157.+\.private$' ) +RE_DYNDNS_PRIVATE_KEY_SHA512 = re.compile( + r'.*/K(?P[^\s\+]+)\.\+163.+\.private$' +) + + def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None): """ Subscribe to a DynDNS service @@ -176,7 +182,7 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, 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) + match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path) if not match: continue _domain = match.group('domain') From b53b05eabda62cefb0a9c56e60f30995fb4cb406 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 19 Sep 2017 00:08:56 +0200 Subject: [PATCH 006/132] [enh] register tsig as hmac-sha512 --- src/yunohost/dyndns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 842fdb841..e46598fbd 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -101,7 +101,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None 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) + 'dnssec-keygen -a hmac-sha512 -b 512 -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] @@ -110,7 +110,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None # Send subscription try: - r = requests.post('https://%s/key/%s' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}) + r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (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: From 2c73dd1a2f7529aa85989175715c2f7cbf2feecd Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 19 Sep 2017 00:51:11 +0200 Subject: [PATCH 007/132] [fix] 163 was for sha256 --- src/yunohost/dyndns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index e46598fbd..516ac59de 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -70,7 +70,7 @@ RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile( RE_DYNDNS_PRIVATE_KEY_SHA512 = re.compile( - r'.*/K(?P[^\s\+]+)\.\+163.+\.private$' + r'.*/K(?P[^\s\+]+)\.\+165.+\.private$' ) From f5f813badb326a5fcbfb29ae5aa816ab2a4ff217 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 21 Sep 2017 05:52:59 +0200 Subject: [PATCH 008/132] [fix] hmac-sha512 key can have space in them because why the fuck not --- src/yunohost/dyndns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 516ac59de..c3e216071 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -106,7 +106,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0] with open(key_file) as f: - key = f.readline().strip().split(' ')[-1] + key = f.readline().strip().split(' ', 6)[-1] # Send subscription try: From 87a5759ca427c5e80ddc58d5d53fdb9eb8ecb91e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 21 Sep 2017 06:56:11 +0200 Subject: [PATCH 009/132] [enh] automatically migrate tsig done using md5 to sha512 when doing a dyndns update --- src/yunohost/dyndns.py | 46 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index c3e216071..be37072f9 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -182,9 +182,11 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, if domain is None: # Retrieve the first registered domain for path in glob.iglob('/etc/yunohost/dyndns/K*.private'): - match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path) + match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path) if not match: - continue + match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path) + if not match: + continue _domain = match.group('domain') try: @@ -213,6 +215,11 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, key = keys[0] + # this mean that hmac-md5 is used + if "+157" in key: + print "detecting md5 key" + key = _migrate_from_md5_tsig_to_sha512_tsig(key, domain, dyn_host) + host = domain.split('.')[1:] host = '.'.join(host) @@ -269,6 +276,41 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, f.write(ipv6) +def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): + public_key_path = private_key_path.rsplit(".private", 1)[0] + ".key" + public_key_md5 = open(public_key_path).read().strip().split(' ')[-1] + + os.system('cd /etc/yunohost/dyndns && ' + 'dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER %s' % domain) + os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private') + + # +165 means that this file store a hmac-sha512 key + new_key_path = glob.glob('/etc/yunohost/dyndns/*+165*.key')[0] + public_key_sha512 = open(new_key_path).read().strip().split(' ', 6)[-1] + + try: + r = requests.put('https://%s/migrate_key_to_sha512/' % (dyn_host), + data={ + 'public_key_md5': base64.b64encode(public_key_md5), + 'public_key_sha512': base64.b64encode(public_key_sha512), + }) + except requests.ConnectionError: + raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) + + if r.status_code != 201: + print r.text + error = json.loads(r.text)['error'] + print "ERROR:", error + # raise MoulinetteError(errno.EPERM, + # m18n.n('dyndns_registration_failed', error=error)) + # XXX print warning + os.system("mv /etc/yunohost/dyndns/*+165* /tmp") + return public_key_path + + os.system("mv /etc/yunohost/dyndns/*+157* /tmp") + return new_key_path.rsplit(".key", 1)[0] + ".private" + + def dyndns_installcron(): """ Install IP update cron From 8590d6b5c6112581c021f81a21c45bfe7e140610 Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Sat, 23 Sep 2017 18:45:59 +0200 Subject: [PATCH 010/132] Update php-fpm helpers to handle stretch/php7 and a smooth migration --- data/helpers.d/backend | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index c54e82754..e64b67795 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -154,7 +154,18 @@ ynh_remove_nginx_config () { # # usage: ynh_add_fpm_config ynh_add_fpm_config () { - finalphpconf="/etc/php5/fpm/pool.d/$app.conf" + local debian_release=$(lsb_release --codename --short) + # Configure PHP-FPM 7.0 by default + local fpm_config_dir="/etc/php/7.0/fpm" + local fpm_service="php7.0-fpm" + # Configure PHP-FPM 5 on Debian Jessie + if [ "$debian_release" == "jessie" ]; then + fpm_config_dir="/etc/php5/fpm" + fpm_service="php5-fpm" + fi + ynh_app_setting_set $app fpm_config_dir "$fpm_config_dir" + ynh_app_setting_set $app fpm_service "$fpm_service" + finalphpconf="$fpm_config_dir/pool.d/$app.conf" ynh_backup_if_checksum_is_different "$finalphpconf" sudo cp ../conf/php-fpm.conf "$finalphpconf" ynh_replace_string "__NAMETOCHANGE__" "$app" "$finalphpconf" @@ -165,21 +176,27 @@ ynh_add_fpm_config () { if [ -e "../conf/php-fpm.ini" ] then - finalphpini="/etc/php5/fpm/conf.d/20-$app.ini" + finalphpini="$fpm_config_dir/conf.d/20-$app.ini" ynh_backup_if_checksum_is_different "$finalphpini" sudo cp ../conf/php-fpm.ini "$finalphpini" sudo chown root: "$finalphpini" ynh_store_file_checksum "$finalphpini" fi - - sudo systemctl reload php5-fpm + sudo systemctl reload $fpm_service } # Remove the dedicated php-fpm config # # usage: ynh_remove_fpm_config ynh_remove_fpm_config () { - ynh_secure_remove "/etc/php5/fpm/pool.d/$app.conf" - ynh_secure_remove "/etc/php5/fpm/conf.d/20-$app.ini" 2>&1 - sudo systemctl reload php5-fpm + local fpm_config_dir=$(ynh_app_setting_get $app fpm_config_dir) + local fpm_service=$(ynh_app_setting_get $app fpm_service) + # Assume php version 5 if not set + if [ -z "$fpm_config_dir" ]; then + fpm_config_dir="/etc/php5/fpm" + fpm_service="php5-fpm" + fi + ynh_secure_remove "$fpm_config_dir/pool.d/$app.conf" + ynh_secure_remove "$fpm_config_dir/conf.d/20-$app.ini" 2>&1 + sudo systemctl reload $fpm_service } From 330a6fb9a72a54f7b5636e1659dfd5d56177ed0d Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Mon, 25 Sep 2017 21:59:55 +0200 Subject: [PATCH 011/132] Add and use ynh_get_debian_release --- data/helpers.d/backend | 3 +-- data/helpers.d/system | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index e64b67795..60c9c94de 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -154,12 +154,11 @@ ynh_remove_nginx_config () { # # usage: ynh_add_fpm_config ynh_add_fpm_config () { - local debian_release=$(lsb_release --codename --short) # Configure PHP-FPM 7.0 by default local fpm_config_dir="/etc/php/7.0/fpm" local fpm_service="php7.0-fpm" # Configure PHP-FPM 5 on Debian Jessie - if [ "$debian_release" == "jessie" ]; then + if [ "$(ynh_get_debian_release)" == "jessie" ]; then fpm_config_dir="/etc/php5/fpm" fpm_service="php5-fpm" fi diff --git a/data/helpers.d/system b/data/helpers.d/system index 5f2ad385b..d70129c9a 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -41,3 +41,10 @@ ynh_abort_if_errors () { set -eu # Exit if a command fail, and if a variable is used unset. trap ynh_exit_properly EXIT # Capturing exit signals on shell script } + +# Return the Debian release codename (i.e. jessie, stretch, etc.) +# +# usage: ynh_get_debian_release +ynh_get_debian_release () { + echo $(lsb_release --codename --short) +} \ No newline at end of file From fbdf6842542432be2012b81530136271a178b39d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 9 Oct 2017 12:12:08 +0200 Subject: [PATCH 012/132] [mod] add failure handling mechanism for json decoding --- src/yunohost/dyndns.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index be37072f9..d596e2525 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -299,8 +299,15 @@ def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): if r.status_code != 201: print r.text - error = json.loads(r.text)['error'] - print "ERROR:", error + try: + error = json.loads(r.text)['error'] + print "ERROR:", error + except Exception as e: + import traceback + traceback.print_exc() + print e + error = r.text + # raise MoulinetteError(errno.EPERM, # m18n.n('dyndns_registration_failed', error=error)) # XXX print warning From a32359c76a6005d3a46b8a73aea2d5c9766116dd Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 9 Oct 2017 12:12:41 +0200 Subject: [PATCH 013/132] [mod] wait locally for the dynette cron to have runned during tsig migration --- src/yunohost/dyndns.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index d596e2525..ff6a5d42c 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -27,6 +27,7 @@ import os import re import json import glob +import time import base64 import errno import requests @@ -315,6 +316,10 @@ def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): return public_key_path os.system("mv /etc/yunohost/dyndns/*+157* /tmp") + + # sleep to wait for dyndns cache invalidation + time.sleep(180) + return new_key_path.rsplit(".key", 1)[0] + ".private" From 50867079836553c98c6faac05b524dafd8fdbd89 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 9 Oct 2017 12:14:14 +0200 Subject: [PATCH 014/132] [mod] add some documentation comment --- src/yunohost/dyndns.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index ff6a5d42c..ccbfdaffb 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -315,6 +315,7 @@ def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): os.system("mv /etc/yunohost/dyndns/*+165* /tmp") return public_key_path + # remove old certificates os.system("mv /etc/yunohost/dyndns/*+157* /tmp") # sleep to wait for dyndns cache invalidation From fd2321654441e95dd3fb2d22c7c3a60e76422a08 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Nov 2017 22:17:09 +0100 Subject: [PATCH 015/132] [mod] Use proper functions to enable/start firewall during postinstall --- src/yunohost/service.py | 2 +- src/yunohost/tools.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 5401a1fab..0861d05cd 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -506,7 +506,7 @@ def _run_service_command(action, service): else: raise ValueError("Unknown action '%s'" % action) - need_lock = (services[service].get('need_lock') or False) \ + need_lock = services[service].get('need_lock', False) \ and action in ['start', 'stop', 'restart', 'reload'] try: diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 042671125..51db12cfb 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -47,7 +47,7 @@ from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, a from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp -from yunohost.service import service_status, service_regen_conf, service_log +from yunohost.service import service_status, service_regen_conf, service_log, service_start, service_enable from yunohost.monitor import monitor_disk, monitor_system from yunohost.utils.packages import ynh_packages_version @@ -398,8 +398,8 @@ def tools_postinstall(domain, password, ignore_dyndns=False): 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_enable("yunohost-firewall") + service_start("yunohost-firewall") service_regen_conf(force=True) logger.success(m18n.n('yunohost_configured')) From 164377d5999aafc6523192539d4336c085a64477 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Nov 2017 23:12:13 +0100 Subject: [PATCH 016/132] [mod] Use systemctl for all service operations --- src/yunohost/service.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 0861d05cd..f0948c961 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -498,11 +498,8 @@ def _run_service_command(action, service): raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service)) cmd = None - if action in ['start', 'stop', 'restart', 'reload']: - cmd = 'service %s %s' % (service, action) - elif action in ['enable', 'disable']: - arg = 'defaults' if action == 'enable' else 'remove' - cmd = 'update-rc.d %s %s' % (service, arg) + if action in ['start', 'stop', 'restart', 'reload', 'enable', 'disable']: + cmd = 'systemctl %s %s' % (action, service) else: raise ValueError("Unknown action '%s'" % action) From 4276aebfa379fcbc8f6fc0bca911aaa1a01742b4 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 10 Dec 2017 23:38:09 +0100 Subject: [PATCH 017/132] [enh] Default version number for ynh_install_app_dependencies --- data/helpers.d/package | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/helpers.d/package b/data/helpers.d/package index f28691579..f6c9c07db 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -124,6 +124,9 @@ ynh_install_app_dependencies () { manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place fi version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file. + if [ " ${#version}" -eq 0 ]; then + version="1.0" + if dep_app=${app//_/-} # Replace all '_' by '-' cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build From 2685c42ac605b9a79cb389791f415e97b7214ef4 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 11 Dec 2017 17:23:26 +0100 Subject: [PATCH 018/132] [fix] Only for JimboJoe ;) --- data/helpers.d/package | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/package b/data/helpers.d/package index f6c9c07db..a337bdd09 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -126,7 +126,7 @@ ynh_install_app_dependencies () { version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file. if [ " ${#version}" -eq 0 ]; then version="1.0" - if + fi dep_app=${app//_/-} # Replace all '_' by '-' cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build From 42f9c8fc185a04a999360cb0005bf27abba8d851 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 11 Dec 2017 17:25:28 +0100 Subject: [PATCH 019/132] [fix] Who said I didn't check my code !? --- data/helpers.d/package | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/package b/data/helpers.d/package index a337bdd09..2e60d723e 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -124,7 +124,7 @@ ynh_install_app_dependencies () { manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place fi version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file. - if [ " ${#version}" -eq 0 ]; then + if [ ${#version} -eq 0 ]; then version="1.0" fi dep_app=${app//_/-} # Replace all '_' by '-' From 3a3ec7d9b5329967f64a0fda4dc1c8f2b289d85b Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 17 Dec 2017 20:26:52 +0100 Subject: [PATCH 020/132] [fix/enh] Use local variables --- data/helpers.d/backend | 10 +++++----- data/helpers.d/filesystem | 10 +++++----- data/helpers.d/ip | 10 +++++----- data/helpers.d/mysql | 6 +++--- data/helpers.d/network | 4 ++-- data/helpers.d/package | 18 +++++++++--------- data/helpers.d/print | 4 ++-- data/helpers.d/string | 12 ++++++------ data/helpers.d/system | 2 +- data/helpers.d/user | 4 ++-- data/helpers.d/utils | 29 +++++++++++++++-------------- 11 files changed, 55 insertions(+), 54 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index c32ef02ac..8fef412cf 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -22,12 +22,12 @@ ynh_use_logrotate () { fi if [ $# -gt 0 ]; then if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile - logfile=$1 # In this case, focus logrotate on the logfile + local logfile=$1 # In this case, focus logrotate on the logfile else - logfile=$1/*.log # Else, uses the directory and all logfile into it. + local logfile=$1/*.log # Else, uses the directory and all logfile into it. fi else - logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log + local logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log fi cat > ./${app}-logrotate << EOF # Build a config file for logrotate $logfile { @@ -97,7 +97,7 @@ ynh_add_systemd_config () { # # usage: ynh_remove_systemd_config ynh_remove_systemd_config () { - finalsystemdconf="/etc/systemd/system/$app.service" + local finalsystemdconf="/etc/systemd/system/$app.service" if [ -e "$finalsystemdconf" ]; then sudo systemctl stop $app sudo systemctl disable $app @@ -124,7 +124,7 @@ ynh_add_nginx_config () { # Substitute in a nginx config file only if the variable is not empty if test -n "${path_url:-}"; then # path_url_slash_less is path_url, or a blank value if path_url is only '/' - path_url_slash_less=${path_url%/} + local path_url_slash_less=${path_url%/} ynh_replace_string "__PATH__/" "$path_url_slash_less/" "$finalnginxconf" ynh_replace_string "__PATH__" "$path_url" "$finalnginxconf" fi diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 6fb073e06..6361d278e 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -180,12 +180,12 @@ ynh_restore_file () { local ARCHIVE_PATH="$YNH_CWD${ORIGIN_PATH}" # Default value for DEST_PATH = /$ORIGIN_PATH local DEST_PATH="${2:-$ORIGIN_PATH}" - + # If ARCHIVE_PATH doesn't exist, search for a corresponding path in CSV if [ ! -d "$ARCHIVE_PATH" ] && [ ! -f "$ARCHIVE_PATH" ] && [ ! -L "$ARCHIVE_PATH" ]; then ARCHIVE_PATH="$YNH_BACKUP_DIR/$(_get_archive_path \"$ORIGIN_PATH\")" fi - + # Restore ORIGIN_PATH into DEST_PATH mkdir -p $(dirname "$DEST_PATH") @@ -258,7 +258,7 @@ ynh_backup_if_checksum_is_different () { then # Proceed only if a value was stored into the app settings if ! echo "$checksum_value $file" | sudo md5sum -c --status then # If the checksum is now different - backup_file="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" + local backup_file="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" sudo mkdir -p "$(dirname "$backup_file")" sudo cp -a "$file" "$backup_file" # Backup the current file echo "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file" >&2 @@ -272,8 +272,8 @@ ynh_backup_if_checksum_is_different () { # usage: ynh_secure_remove path_to_remove # | arg: path_to_remove - File or directory to remove ynh_secure_remove () { - path_to_remove=$1 - forbidden_path=" \ + local path_to_remove=$1 + local forbidden_path=" \ /var/www \ /home/yunohost.app" diff --git a/data/helpers.d/ip b/data/helpers.d/ip index cb507b35a..874675c9d 100644 --- a/data/helpers.d/ip +++ b/data/helpers.d/ip @@ -8,12 +8,12 @@ ynh_validate_ip() { # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 - - IP_ADDRESS_FAMILY=$1 - IP_ADDRESS=$2 - + + local IP_ADDRESS_FAMILY=$1 + local IP_ADDRESS=$2 + [ "$IP_ADDRESS_FAMILY" == "4" ] || [ "$IP_ADDRESS_FAMILY" == "6" ] || return 1 - + python /dev/stdin << EOF import socket import sys diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 42c204f95..56741ec0e 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -40,9 +40,9 @@ ynh_mysql_execute_file_as_root() { # | arg: user - the user to grant privilegies # | arg: pwd - the password to identify user by ynh_mysql_create_db() { - db=$1 + local db=$1 - sql="CREATE DATABASE ${db};" + local sql="CREATE DATABASE ${db};" # grant all privilegies to user if [[ $# -gt 1 ]]; then @@ -159,6 +159,6 @@ ynh_mysql_remove_db () { # | arg: name - name to correct/sanitize # | ret: the corrected name ynh_sanitize_dbid () { - dbid=${1//[-.]/_} # We should avoid having - and . in the name of databases. They are replaced by _ + local dbid=${1//[-.]/_} # We should avoid having - and . in the name of databases. They are replaced by _ echo $dbid } diff --git a/data/helpers.d/network b/data/helpers.d/network index c6764c1f5..f9e37e6cc 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -11,7 +11,7 @@ # usage: ynh_normalize_url_path path_to_normalize # | arg: url_path_to_normalize - URL path to normalize before using it ynh_normalize_url_path () { - path_url=$1 + local path_url=$1 test -n "$path_url" || ynh_die "ynh_normalize_url_path expect a URL path as first argument and received nothing." if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a / path_url="/$path_url" # Add / at begin of path variable @@ -29,7 +29,7 @@ ynh_normalize_url_path () { # usage: ynh_find_port begin_port # | arg: begin_port - port to start to search ynh_find_port () { - port=$1 + local port=$1 test -n "$port" || ynh_die "The argument of ynh_find_port must be a valid port." while netcat -z 127.0.0.1 $port # Check if the port is free do diff --git a/data/helpers.d/package b/data/helpers.d/package index f28691579..ccb0c44d0 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -80,15 +80,15 @@ ynh_package_autopurge() { # usage: ynh_package_install_from_equivs controlfile # | arg: controlfile - path of the equivs control file ynh_package_install_from_equivs () { - controlfile=$1 + local controlfile=$1 # Check if the equivs package is installed. Or install it. ynh_package_is_installed 'equivs' \ || ynh_package_install equivs # retrieve package information - pkgname=$(grep '^Package: ' $controlfile | cut -d' ' -f 2) # Retrieve the name of the debian package - pkgversion=$(grep '^Version: ' $controlfile | cut -d' ' -f 2) # And its version number + local pkgname=$(grep '^Package: ' $controlfile | cut -d' ' -f 2) # Retrieve the name of the debian package + local pkgversion=$(grep '^Version: ' $controlfile | cut -d' ' -f 2) # And its version number [[ -z "$pkgname" || -z "$pkgversion" ]] \ && echo "Invalid control file" && exit 1 # Check if this 2 variables aren't empty. @@ -96,7 +96,7 @@ ynh_package_install_from_equivs () { ynh_package_update # Build and install the package - TMPDIR=$(mktemp -d) + local TMPDIR=$(mktemp -d) # Note that the cd executes into a sub shell # Create a fake deb package with equivs-build and the given control file # Install the fake package without its dependencies with dpkg @@ -118,13 +118,13 @@ ynh_package_install_from_equivs () { # usage: ynh_install_app_dependencies dep [dep [...]] # | arg: dep - the package name to install in dependence ynh_install_app_dependencies () { - dependencies=$@ - manifest_path="../manifest.json" + local dependencies=$@ + local manifest_path="../manifest.json" if [ ! -e "$manifest_path" ]; then manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place fi - version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file. - dep_app=${app//_/-} # Replace all '_' by '-' + local version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file. + local dep_app=${app//_/-} # Replace all '_' by '-' cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build Section: misc @@ -148,6 +148,6 @@ EOF # # usage: ynh_remove_app_dependencies ynh_remove_app_dependencies () { - dep_app=${app//_/-} # Replace all '_' by '-' + local dep_app=${app//_/-} # Replace all '_' by '-' ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used. } diff --git a/data/helpers.d/print b/data/helpers.d/print index 36f4a120e..d9c8f1ec4 100644 --- a/data/helpers.d/print +++ b/data/helpers.d/print @@ -10,10 +10,10 @@ ynh_die() { # Simply duplicate the log, execute the yunohost command and replace the log without the result of this command # It's a very badly hack... ynh_no_log() { - ynh_cli_log=/var/log/yunohost/yunohost-cli.log + local ynh_cli_log=/var/log/yunohost/yunohost-cli.log sudo cp -a ${ynh_cli_log} ${ynh_cli_log}-move eval $@ - exit_code=$? + local exit_code=$? sudo mv ${ynh_cli_log}-move ${ynh_cli_log} return $? } diff --git a/data/helpers.d/string b/data/helpers.d/string index fbf598738..80fae8cf7 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -26,13 +26,13 @@ ynh_replace_string () { local replace_string=$2 local workfile=$3 - # Escape any backslash to preserve them as simple backslash. - match_string=${match_string//\\/"\\\\"} - replace_string=${replace_string//\\/"\\\\"} + # Escape any backslash to preserve them as simple backslash. + match_string=${match_string//\\/"\\\\"} + replace_string=${replace_string//\\/"\\\\"} - # Escape the & character, who has a special function in sed. - match_string=${match_string//&/"\&"} - replace_string=${replace_string//&/"\&"} + # Escape the & character, who has a special function in sed. + match_string=${match_string//&/"\&"} + replace_string=${replace_string//&/"\&"} # Escape the delimiter if it's in the string. match_string=${match_string//${delimit}/"\\${delimit}"} diff --git a/data/helpers.d/system b/data/helpers.d/system index 5f2ad385b..4bb941b7d 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -14,7 +14,7 @@ # Usage: ynh_exit_properly is used only by the helper ynh_abort_if_errors. # You must not use it directly. ynh_exit_properly () { - exit_code=$? + local exit_code=$? if [ "$exit_code" -eq 0 ]; then exit 0 # Exit without error if the script ended correctly fi diff --git a/data/helpers.d/user b/data/helpers.d/user index 0bb0736af..8e214691c 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -48,9 +48,9 @@ ynh_system_user_create () { if ! ynh_system_user_exists "$1" # Check if the user exists on the system then # If the user doesn't exist if [ $# -ge 2 ]; then # If a home dir is mentioned - user_home_dir="-d $2" + local user_home_dir="-d $2" else - user_home_dir="--no-create-home" + local user_home_dir="--no-create-home" fi sudo useradd $user_home_dir --system --user-group $1 --shell /usr/sbin/nologin || ynh_die "Unable to create $1 system account" fi diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 2cb18c5c0..030fddcde 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -5,9 +5,9 @@ # usage: ynh_get_plain_key key [subkey [subsubkey ...]] # | ret: string - the key's value ynh_get_plain_key() { - prefix="#" - founded=0 - key=$1 + local prefix="#" + local founded=0 + local key=$1 shift while read line; do if [[ "$founded" == "1" ]] ; then @@ -36,7 +36,7 @@ ynh_get_plain_key() { # ynh_restore_upgradebackup () { echo "Upgrade failed." >&2 - app_bck=${app//_/-} # Replace all '_' by '-' + local app_bck=${app//_/-} # Replace all '_' by '-' # Check if an existing backup can be found before removing and restoring the application. if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number @@ -64,9 +64,9 @@ ynh_backup_before_upgrade () { echo "This app doesn't have any backup script." >&2 return fi - backup_number=1 - old_backup_number=2 - app_bck=${app//_/-} # Replace all '_' by '-' + local backup_number=1 + local old_backup_number=2 + local app_bck=${app//_/-} # Replace all '_' by '-' # Check if a backup already exists with the prefix 1 if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1 @@ -94,7 +94,7 @@ ynh_backup_before_upgrade () { # Download, check integrity, uncompress and patch the source from app.src # # The file conf/app.src need to contains: -# +# # SOURCE_URL=Address to download the app archive # SOURCE_SUM=Control sum # # (Optional) Program to check the integrity (sha256sum, md5sum...) @@ -113,9 +113,9 @@ ynh_backup_before_upgrade () { # Details: # This helper downloads sources from SOURCE_URL if there is no local source # archive in /opt/yunohost-apps-src/APP_ID/SOURCE_FILENAME -# +# # Next, it checks the integrity with "SOURCE_SUM_PRG -c --status" command. -# +# # If it's ok, the source archive will be uncompressed in $dest_dir. If the # SOURCE_IN_SUBDIR is true, the first level directory of the archive will be # removed. @@ -130,7 +130,7 @@ ynh_backup_before_upgrade () { ynh_setup_source () { local dest_dir=$1 local src_id=${2:-app} # If the argument is not given, source_id equals "app" - + # Load value from configuration file (see above for a small doc about this file # format) local src_url=$(grep 'SOURCE_URL=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) @@ -139,7 +139,7 @@ ynh_setup_source () { local src_format=$(grep 'SOURCE_FORMAT=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) local src_filename=$(grep 'SOURCE_FILENAME=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) - + # Default value src_sumprg=${src_sumprg:-sha256sum} src_in_subdir=${src_in_subdir:-true} @@ -216,10 +216,11 @@ ynh_setup_source () { # | arg: ... - (Optionnal) More POST keys and values ynh_local_curl () { # Define url of page to curl - full_page_url=https://localhost$path_url$1 + local full_page_url=https://localhost$path_url$1 # Concatenate all other arguments with '&' to prepare POST data - POST_data="" + local POST_data="" + local arg="" for arg in "${@:2}" do POST_data="${POST_data}${arg}&" From e696caa31fc2c6f3613ab11568fc0d2392a047af Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 29 Dec 2017 16:00:29 +0100 Subject: [PATCH 021/132] [Fix] Nginx headers --- data/templates/nginx/server.tpl.conf | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 685ae01b8..90a258433 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -42,7 +42,12 @@ server { # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; - add_header Strict-Transport-Security "max-age=31536000;"; + add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload"; + add_header 'Referrer-Policy' 'no-referrer'; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Permitted-Cross-Domain-Policies none; + add_header X-Frame-Options "SAMEORIGIN"; access_by_lua_file /usr/share/ssowat/access.lua; From 9e19e5316ccbeeb1c6d3dd16dc70278eee18c648 Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 29 Dec 2017 16:07:15 +0100 Subject: [PATCH 022/132] [Fix] Nginx headers --- data/templates/nginx/server.tpl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 90a258433..dee667939 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -42,7 +42,7 @@ server { # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; - add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; add_header 'Referrer-Policy' 'no-referrer'; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; From 5cd30e584d4225a3c82ca54709189aa4f9bc9ba9 Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 29 Dec 2017 16:16:37 +0100 Subject: [PATCH 023/132] [Fix] Nginx headers in Admin conf --- data/templates/nginx/plain/yunohost_admin.conf | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index a9d26d151..349be9177 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -36,8 +36,14 @@ server { # Uncomment the following directive after DH generation # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; - - add_header Strict-Transport-Security "max-age=31536000;"; + + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + add_header 'Referrer-Policy' 'no-referrer'; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + add_header X-Frame-Options "SAMEORIGIN"; location / { return 302 https://$http_host/yunohost/admin; From 95835118bd8e0c308dec2c3659719760cd17570f Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 29 Dec 2017 17:59:12 +0100 Subject: [PATCH 024/132] [Fix] CSP Standart. --- data/templates/nginx/server.tpl.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index dee667939..6f49d68c3 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -43,9 +43,11 @@ server { #ssl_dhparam /etc/ssl/private/dh2048.pem; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - add_header 'Referrer-Policy' 'no-referrer'; + add_header 'Referrer-Policy' 'origin-when-cross-origin'; + add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval';report-uri /csp-violation-report-endpoint/"; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; + add_header X-Download-Options noopen; add_header X-Permitted-Cross-Domain-Policies none; add_header X-Frame-Options "SAMEORIGIN"; From 804d0b29c35aa450608487ae12ad4deefb1e8537 Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 29 Dec 2017 18:25:17 +0100 Subject: [PATCH 025/132] [Fix] Add CSP in Admin conf --- data/templates/nginx/plain/yunohost_admin.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 349be9177..9bdbb0f26 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -38,7 +38,8 @@ server { #ssl_dhparam /etc/ssl/private/dh2048.pem; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - add_header 'Referrer-Policy' 'no-referrer'; + add_header 'Referrer-Policy' 'origin-when-cross-origin'; + add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval';report-uri /csp-violation-report-endpoint/"; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header X-Download-Options noopen; From b655229cbd8c00cf57838f848b1988ff8d02d1d2 Mon Sep 17 00:00:00 2001 From: frju365 Date: Sat, 30 Dec 2017 11:18:17 +0100 Subject: [PATCH 026/132] [Fix] Referrer --- data/templates/nginx/server.tpl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 6f49d68c3..11f503c98 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -43,7 +43,7 @@ server { #ssl_dhparam /etc/ssl/private/dh2048.pem; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - add_header 'Referrer-Policy' 'origin-when-cross-origin'; + add_header 'Referrer-Policy' 'same-origin'; add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval';report-uri /csp-violation-report-endpoint/"; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; From bd2f459e8663c401506faa2ac5503e74aa8e50b8 Mon Sep 17 00:00:00 2001 From: frju365 Date: Sat, 30 Dec 2017 11:18:58 +0100 Subject: [PATCH 027/132] [Fix] Referrer --- data/templates/nginx/plain/yunohost_admin.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 9bdbb0f26..eedbd61b3 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -38,7 +38,7 @@ server { #ssl_dhparam /etc/ssl/private/dh2048.pem; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - add_header 'Referrer-Policy' 'origin-when-cross-origin'; + add_header 'Referrer-Policy' 'same-origin'; add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval';report-uri /csp-violation-report-endpoint/"; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; From 044b2406d3c1a0f11e246dc1f2827a91e8a77212 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 18:45:18 +0100 Subject: [PATCH 028/132] [enh] better logging during key migration --- locales/en.json | 4 ++++ src/yunohost/dyndns.py | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index 80ff22655..421a04f56 100644 --- a/locales/en.json +++ b/locales/en.json @@ -207,6 +207,10 @@ "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", "maindomain_change_failed": "Unable to change the main domain", "maindomain_changed": "The main domain has been changed", + "migrate_tsig_end": "Migration to hmac-sha512 finished", + "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", + "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", + "migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account...", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalide number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index ccbfdaffb..459a1e04e 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -278,6 +278,7 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): + logger.warning(m18n.n('migrate_tsig_start', domain=domain)) public_key_path = private_key_path.rsplit(".private", 1)[0] + ".key" public_key_md5 = open(public_key_path).read().strip().split(' ')[-1] @@ -299,19 +300,17 @@ def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) if r.status_code != 201: - print r.text try: error = json.loads(r.text)['error'] - print "ERROR:", error except Exception as e: import traceback traceback.print_exc() print e error = r.text - # raise MoulinetteError(errno.EPERM, - # m18n.n('dyndns_registration_failed', error=error)) - # XXX print warning + logger.warning(m18n.n('migrate_tsig_failed', domain=domain, + error_code=str(r.status_code), error=error)) + os.system("mv /etc/yunohost/dyndns/*+165* /tmp") return public_key_path @@ -319,8 +318,10 @@ def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): os.system("mv /etc/yunohost/dyndns/*+157* /tmp") # sleep to wait for dyndns cache invalidation + logger.warning(m18n.n('migrate_tsig_wait')) time.sleep(180) + logger.warning(m18n.n('migrate_tsig_end')) return new_key_path.rsplit(".key", 1)[0] + ".private" From 299cba623b8ac9d4cfc5edbb360826e052681ec7 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 18:52:01 +0100 Subject: [PATCH 029/132] [fix] avoid stupid mistake --- src/yunohost/dyndns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 459a1e04e..36f30bc03 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -97,7 +97,8 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None if key is None: if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0: - os.makedirs('/etc/yunohost/dyndns') + if not os.path.exists('/etc/yunohost/dyndns'): + os.makedirs('/etc/yunohost/dyndns') logger.info(m18n.n('dyndns_key_generating')) From 544ffb273e4977a421660a8b4b0d282da48087c4 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 19:00:16 +0100 Subject: [PATCH 030/132] [mod] improve waiting time UX --- locales/en.json | 3 +++ src/yunohost/dyndns.py | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 421a04f56..3a8c2b13c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -211,6 +211,9 @@ "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", "migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account...", + "migrate_tsig_wait_2": "2min...", + "migrate_tsig_wait_3": "1min...", + "migrate_tsig_wait_3": "30 secondes...", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalide number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 36f30bc03..d4313dc8b 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -320,7 +320,13 @@ def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): # sleep to wait for dyndns cache invalidation logger.warning(m18n.n('migrate_tsig_wait')) - time.sleep(180) + time.sleep(60) + logger.warning(m18n.n('migrate_tsig_wait_2')) + time.sleep(60) + logger.warning(m18n.n('migrate_tsig_wait_3')) + time.sleep(30) + logger.warning(m18n.n('migrate_tsig_wait_4')) + time.sleep(30) logger.warning(m18n.n('migrate_tsig_end')) return new_key_path.rsplit(".key", 1)[0] + ".private" From 89358173387ddfd88e458f8e803c90e0f16cbbc1 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 19:21:53 +0100 Subject: [PATCH 031/132] [fix] add timeout on requests --- src/yunohost/dyndns.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index d4313dc8b..aabc191a1 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -90,7 +90,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None # Verify if domain is available try: - if requests.get('https://%s/test/%s' % (subscribe_host, domain)).status_code != 200: + if requests.get('https://%s/test/%s' % (subscribe_host, domain), timeout=30).status_code != 200: raise MoulinetteError(errno.EEXIST, m18n.n('dyndns_unavailable')) except requests.ConnectionError: raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) @@ -112,7 +112,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None # Send subscription try: - r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}) + r = requests.post('https://%s/key/%s' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30) except requests.ConnectionError: raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) if r.status_code != 201: @@ -296,7 +296,7 @@ def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): data={ 'public_key_md5': base64.b64encode(public_key_md5), 'public_key_sha512': base64.b64encode(public_key_sha512), - }) + }, timeout=30) except requests.ConnectionError: raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) From 28b44401f5f97425df42c5d1a7ab7daf0aaccdb8 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 19:22:10 +0100 Subject: [PATCH 032/132] [mod] remove debug print --- src/yunohost/dyndns.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index aabc191a1..d2137f91a 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -219,7 +219,6 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, # this mean that hmac-md5 is used if "+157" in key: - print "detecting md5 key" key = _migrate_from_md5_tsig_to_sha512_tsig(key, domain, dyn_host) host = domain.split('.')[1:] From 57665b6f11968dc60a65fa74a891004e135d3eb9 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 19:23:47 +0100 Subject: [PATCH 033/132] [mod] uses builtin function to show traceback --- src/yunohost/dyndns.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index d2137f91a..d65f5f7ed 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -302,14 +302,15 @@ def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): if r.status_code != 201: try: error = json.loads(r.text)['error'] - except Exception as e: - import traceback - traceback.print_exc() - print e + show_traceback = 0 + except Exception: + # failed to decode json error = r.text + show_traceback = 1 logger.warning(m18n.n('migrate_tsig_failed', domain=domain, - error_code=str(r.status_code), error=error)) + error_code=str(r.status_code), error=error), + exc_info=show_traceback) os.system("mv /etc/yunohost/dyndns/*+165* /tmp") return public_key_path From a9eea61a26e2ed78a9e2496b20150f1d8c5bcb27 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 20:07:32 +0100 Subject: [PATCH 034/132] [fix] overwritting key --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index b2d7c847a..a2df29a54 100644 --- a/locales/en.json +++ b/locales/en.json @@ -216,7 +216,7 @@ "migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account...", "migrate_tsig_wait_2": "2min...", "migrate_tsig_wait_3": "1min...", - "migrate_tsig_wait_3": "30 secondes...", + "migrate_tsig_wait_4": "30 secondes...", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalide number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", From 7c63c0a34de6013b9e528437d7c705902e6ce322 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 20:22:46 +0100 Subject: [PATCH 035/132] [enh] subscribe has hmac-sha512 --- src/yunohost/dyndns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index c11d0a067..6ff73d1e2 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -153,7 +153,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None # Send subscription try: - r = requests.post('https://%s/key/%s' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30) + r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30) except requests.ConnectionError: raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) if r.status_code != 201: From a079e3f5eef856845e3485312e989bf13eabac38 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 4 Jan 2018 18:02:58 +0100 Subject: [PATCH 036/132] [enh] display which app is being upgraded --- locales/en.json | 1 + src/yunohost/app.py | 1 + 2 files changed, 2 insertions(+) diff --git a/locales/en.json b/locales/en.json index 8dac6e799..e3bc763bb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -34,6 +34,7 @@ "app_sources_fetch_failed": "Unable to fetch sources files", "app_unknown": "Unknown app", "app_unsupported_remote_type": "Unsupported remote type used for the app", + "app_upgrade_app_name": "Upgrading app {app}...", "app_upgrade_failed": "Unable to upgrade {app:s}", "app_upgrade_some_app_failed": "Unable to upgrade some applications", "app_upgraded": "{app:s} has been upgraded", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 403e76cc4..e9278855e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -555,6 +555,7 @@ def app_upgrade(auth, app=[], url=None, file=None): logger.info("Upgrading apps %s", ", ".join(app)) for app_instance_name in apps: + logger.warning(m18n.n('app_upgrade_app_name', app=app_instance_name)) installed = _is_installed(app_instance_name) if not installed: raise MoulinetteError(errno.ENOPKG, From b97675516ee1f9dcc54327a82e87f083f1cc94e5 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 4 Jan 2018 18:15:10 +0100 Subject: [PATCH 037/132] [enh] display app name everytime possible on requirements testing to improve UX --- locales/en.json | 10 +++++----- src/yunohost/app.py | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/locales/en.json b/locales/en.json index e3bc763bb..11598a473 100644 --- a/locales/en.json +++ b/locales/en.json @@ -16,7 +16,7 @@ "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", "app_extraction_failed": "Unable to extract installation files", "app_id_invalid": "Invalid app id", - "app_incompatible": "The app is incompatible with your YunoHost version", + "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "Invalid installation files", "app_location_already_used": "An app is already installed in this location", "app_location_install_failed": "Unable to install the app in this location", @@ -26,11 +26,11 @@ "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "{app:s} is not installed", "app_not_properly_removed": "{app:s} has not been properly removed", - "app_package_need_update": "The app package needs to be updated to follow YunoHost changes", + "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", "app_removed": "{app:s} has been removed", - "app_requirements_checking": "Checking required packages...", - "app_requirements_failed": "Unable to meet requirements: {error}", - "app_requirements_unmeet": "Requirements are not met, the package {pkgname} ({version}) must be {spec}", + "app_requirements_checking": "Checking required packages for {app}...", + "app_requirements_failed": "Unable to meet requirements for {app}: {error}", + "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_sources_fetch_failed": "Unable to fetch sources files", "app_unknown": "Unknown app", "app_unsupported_remote_type": "Unsupported remote type used for the app", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e9278855e..3802874b6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -581,7 +581,7 @@ def app_upgrade(auth, app=[], url=None, file=None): continue # Check requirements - _check_manifest_requirements(manifest) + _check_manifest_requirements(manifest, app_instance_name=app_instance_name) app_setting_path = APPS_SETTING_PATH + '/' + app_instance_name @@ -684,7 +684,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False): app_id = manifest['id'] # Check requirements - _check_manifest_requirements(manifest) + _check_manifest_requirements(manifest, app_id) # Check if app can be forked instance_number = _installed_instance_number(app_id, last=True) + 1 @@ -1690,7 +1690,7 @@ def _encode_string(value): return value -def _check_manifest_requirements(manifest): +def _check_manifest_requirements(manifest, app_instance_name): """Check if required packages are met from the manifest""" requirements = manifest.get('requirements', dict()) @@ -1708,12 +1708,12 @@ def _check_manifest_requirements(manifest): if (not yunohost_req or not packages.SpecifierSet(yunohost_req) & '>= 2.3.6'): raise MoulinetteError(errno.EINVAL, '{0}{1}'.format( - m18n.g('colon', m18n.n('app_incompatible')), - m18n.n('app_package_need_update'))) + m18n.g('colon', m18n.n('app_incompatible'), app=app_instance_name), + m18n.n('app_package_need_update', app=app_instance_name))) elif not requirements: return - logger.info(m18n.n('app_requirements_checking')) + logger.info(m18n.n('app_requirements_checking', app=app_instance_name)) # Retrieve versions of each required package try: @@ -1722,7 +1722,7 @@ def _check_manifest_requirements(manifest): except packages.PackageException as e: raise MoulinetteError(errno.EINVAL, m18n.n('app_requirements_failed', - error=str(e))) + error=str(e), app=app_instance_name)) # Iterate over requirements for pkgname, spec in requirements.items(): @@ -1731,7 +1731,7 @@ def _check_manifest_requirements(manifest): raise MoulinetteError( errno.EINVAL, m18n.n('app_requirements_unmeet', pkgname=pkgname, version=version, - spec=spec)) + spec=spec, app=app_instance_name)) def _parse_args_from_manifest(manifest, action, args={}, auth=None): From a194e1fa1cc655e9076dc99b6caee6d6d9c4b68d Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 4 Jan 2018 18:44:13 +0100 Subject: [PATCH 038/132] [enh] Maintain ssh client connexion --- data/templates/ssh/sshd_config | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index 695ea0d36..8c5a7fb95 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -66,6 +66,9 @@ PrintLastLog yes TCPKeepAlive yes #UseLogin no +# keep ssh sessions fresh +ClientAliveInterval 60 + #MaxStartups 10:30:60 Banner /etc/issue.net From 13aca112fa74b79f9c8097521f3f19abe24a72e2 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 4 Jan 2018 20:31:58 +0100 Subject: [PATCH 039/132] [enh] make change_url possibility available on the API for the admin --- src/yunohost/app.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 403e76cc4..7ceb795d7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -330,6 +330,9 @@ def app_info(app, show_status=False, raw=False): if not _is_installed(app): raise MoulinetteError(errno.EINVAL, m18n.n('app_not_installed', app=app)) + + app_setting_path = APPS_SETTING_PATH + app + if raw: ret = app_list(filter=app, raw=True)[app] ret['settings'] = _get_app_settings(app) @@ -345,11 +348,10 @@ def app_info(app, show_status=False, raw=False): upgradable = "no" ret['upgradable'] = upgradable + ret['change_url'] = os.path.exists(os.path.join(app_setting_path, "scripts", "change_url")) return ret - app_setting_path = APPS_SETTING_PATH + app - # Retrieve manifest and status with open(app_setting_path + '/manifest.json') as f: manifest = json.loads(str(f.read())) From ab282e88a75d80bafd4eaa1c3b0d9b566177aaa0 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 4 Jan 2018 22:04:15 +0100 Subject: [PATCH 040/132] [enh] add commands to allow user to have access in ssh --- data/actionsmap/yunohost.yml | 24 ++++++++++++++++ src/yunohost/user.py | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 966de21df..c4e95e748 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -203,6 +203,30 @@ user: extra: pattern: *pattern_mailbox_quota + ### ssh_user_enable_ssh() + allow-ssh: + action_help: Allow the user to uses ssh + api: POST /ssh/user/enable-ssh + configuration: + authenticate: all + arguments: + username: + help: Username of the user + extra: + pattern: *pattern_username + + ### ssh_user_disable_ssh() + disallow-ssh: + action_help: Disallow the user to uses ssh + api: POST /ssh/user/disable-ssh + configuration: + authenticate: all + arguments: + username: + help: Username of the user + extra: + pattern: *pattern_username + ### user_info() info: action_help: Get user information diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 11f61d807..123438da3 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -25,6 +25,7 @@ """ import os import re +import pwd import json import errno import crypt @@ -435,6 +436,36 @@ def user_info(auth, username): raise MoulinetteError(167, m18n.n('user_info_failed')) +def user_allow_ssh(auth, username): + """ + Allow YunoHost user connect as ssh. + + Keyword argument: + username -- User username + """ + # TODO it would be good to support different kind of shells + + if not _get_user_for_ssh(auth, username): + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username)) + + auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/bash'}) + + +def user_disallow_ssh(auth, username): + """ + Disallow YunoHost user connect as ssh. + + Keyword argument: + username -- User username + """ + # TODO it would be good to support different kind of shells + + if not _get_user_for_ssh(auth, username) : + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username)) + + auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/false'}) + + def _convertSize(num, suffix=''): for unit in ['K', 'M', 'G', 'T', 'P', 'E', 'Z']: if abs(num) < 1024.0: @@ -470,3 +501,28 @@ def _hash_user_password(password): salt = '$6$' + salt + '$' return '{CRYPT}' + crypt.crypt(str(password), salt) + + +def _get_user_for_ssh(auth, username, attrs=None): + if username == "admin": + admin_unix = pwd.getpwnam("admin") + return { + 'username': 'admin', + 'fullname': '', + 'mail': '', + 'ssh_allowed': admin_unix.pw_shell.strip() != "/bin/false", + 'shell': admin_unix.pw_shell, + 'home_path': admin_unix.pw_dir, + } + + # TODO escape input using https://www.python-ldap.org/doc/html/ldap-filter.html + user = auth.search('ou=users,dc=yunohost,dc=org', + '(&(objectclass=person)(uid=%s))' % username, + attrs) + + assert len(user) in (0, 1) + + if not user: + return None + + return user[0] From 3deb11cf8a57cec5cbf5131de603c135698d354e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 4 Jan 2018 22:10:36 +0100 Subject: [PATCH 041/132] [enh] add ssh information in userlist for admin UI --- src/yunohost/user.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 123438da3..3cb848582 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -57,6 +57,7 @@ def user_list(auth, fields=None): 'cn': 'fullname', 'mail': 'mail', 'maildrop': 'mail-forward', + 'loginShell': 'shell', 'mailuserquota': 'mailbox-quota' } @@ -72,7 +73,7 @@ def user_list(auth, fields=None): raise MoulinetteError(errno.EINVAL, m18n.n('field_invalid', attr)) else: - attrs = ['uid', 'cn', 'mail', 'mailuserquota'] + attrs = ['uid', 'cn', 'mail', 'mailuserquota', 'loginShell'] result = auth.search('ou=users,dc=yunohost,dc=org', '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', @@ -82,6 +83,12 @@ def user_list(auth, fields=None): entry = {} for attr, values in user.items(): if values: + if attr == "loginShell": + if values[0].strip() == "/bin/false": + entry["ssh_allowed"] = False + else: + entry["ssh_allowed"] = True + entry[user_attrs[attr]] = values[0] uid = entry[user_attrs['uid']] From c55b8cec16ded23162c1cf34bbddaa7fb5b70942 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 4 Jan 2018 22:31:13 +0100 Subject: [PATCH 042/132] [enh] add commands to manage authorized-keys of users --- data/actionsmap/yunohost.yml | 68 +++++++++++++++++++++++ src/yunohost/ssh.py | 102 +++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/yunohost/ssh.py diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index c4e95e748..e47d0b04a 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1336,6 +1336,74 @@ dyndns: api: DELETE /dyndns/cron +############################# +# SSH # +############################# +ssh: + category_help: Manage ssh keys and access + actions: {} + subcategories: + authorized-keys: + subcategory_help: Manage user's authorized ssh keys + + actions: + ### ssh_authorized_keys_list() + list: + action_help: Show user's authorized ssh keys + api: GET /ssh/authorized-keys + configuration: + authenticate: all + arguments: + username: + help: Username of the user + extra: + pattern: *pattern_username + + ### ssh_authorized_keys_add() + add: + action_help: Add a new authorized ssh key for this user + api: POST /ssh/authorized-keys + configuration: + authenticate: all + arguments: + username: + help: Username of the user + extra: + pattern: *pattern_username + -u: + full: --public + help: Public key + extra: + required: True + -i: + full: --private + help: Private key + extra: + required: True + -n: + full: --name + help: Key name + extra: + required: True + + ### ssh_authorized_keys_remove() + remove: + action_help: Remove an authorized ssh key for this user + api: DELETE /ssh/authorized-keys + configuration: + authenticate: all + arguments: + username: + help: Username of the user + extra: + pattern: *pattern_username + -k: + full: --key + help: Key as a string + extra: + required: True + + ############################# # Tools # ############################# diff --git a/src/yunohost/ssh.py b/src/yunohost/ssh.py new file mode 100644 index 000000000..5f1f33b55 --- /dev/null +++ b/src/yunohost/ssh.py @@ -0,0 +1,102 @@ +# encoding: utf-8 + +import os + +from moulinette.utils.filesystem import read_file, write_to_file, chown, chmod, mkdir + +from yunohost.user import _get_user_for_ssh + + +def ssh_authorized_keys_list(auth, username): + user = _get_user_for_ssh(auth, username, ["homeDirectory"]) + if not user: + raise Exception("User with username '%s' doesn't exists" % username) + + authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys") + + if not os.path.exists(authorized_keys_file): + return [] + + keys = [] + last_comment = "" + for line in read_file(authorized_keys_file).split("\n"): + # empty line + if not line.strip(): + continue + + if line.lstrip().startswith("#"): + last_comment = line.lstrip().lstrip("#").strip() + continue + + # assuming a key per non empty line + key = line.strip() + keys.append({ + "key": key, + "name": last_comment, + }) + + last_comment = "" + + return {"keys": keys} + + +def ssh_authorized_keys_add(auth, username, key, comment): + user = _get_user_for_ssh(auth, username, ["homeDirectory", "uid"]) + if not user: + raise Exception("User with username '%s' doesn't exists" % username) + + authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys") + + if not os.path.exists(authorized_keys_file): + # ensure ".ssh" exists + mkdir(os.path.join(user["homeDirectory"][0], ".ssh"), + force=True, parents=True, uid=user["uid"][0]) + + # create empty file to set good permissions + write_to_file(authorized_keys_file, "") + chown(authorized_keys_file, uid=user["uid"][0]) + chmod(authorized_keys_file, 0600) + + authorized_keys_content = read_file(authorized_keys_file) + + authorized_keys_content += "\n" + authorized_keys_content += "\n" + + if comment and comment.strip(): + if not comment.lstrip().startswith("#"): + comment = "# " + comment + authorized_keys_content += comment.replace("\n", " ").strip() + authorized_keys_content += "\n" + + authorized_keys_content += key.strip() + authorized_keys_content += "\n" + + write_to_file(authorized_keys_file, authorized_keys_content) + + +def ssh_authorized_keys_remove(auth, username, key): + user = _get_user(auth, username, ["homeDirectory", "uid"]) + if not user: + raise Exception("User with username '%s' doesn't exists" % username) + + authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys") + + if not os.path.exists(authorized_keys_file): + raise Exception("this key doesn't exists ({} dosesn't exists)".format(authorized_keys_file)) + + authorized_keys_content = read_file(authorized_keys_file) + + if key not in authorized_keys_content: + raise Exception("Key '{}' is not present in authorized_keys".format(key)) + + # don't delete the previous comment because we can't verify if it's legit + + # this regex approach failed for some reasons and I don't know why :( + # authorized_keys_content = re.sub("{} *\n?".format(key), + # "", + # authorized_keys_content, + # flags=re.MULTILINE) + + authorized_keys_content = authorized_keys_content.replace(key, "") + + write_to_file(authorized_keys_file, authorized_keys_content) From 1e5323eb08c6e268feffc4a107ff4a86e69b96a4 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 5 Jan 2018 00:13:26 +0100 Subject: [PATCH 043/132] [enh] handle root user for being allowed to work on his authorized keys --- src/yunohost/user.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 3cb848582..793ccaf7a 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -36,10 +36,13 @@ import subprocess from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_file from yunohost.service import service_status logger = getActionLogger('yunohost.user') +SSHD_CONFIG_PATH = "/etc/ssh/sshd_config" + def user_list(auth, fields=None): """ @@ -58,6 +61,7 @@ def user_list(auth, fields=None): 'mail': 'mail', 'maildrop': 'mail-forward', 'loginShell': 'shell', + 'homeDirectory': 'home_path', 'mailuserquota': 'mailbox-quota' } @@ -511,6 +515,34 @@ def _hash_user_password(password): def _get_user_for_ssh(auth, username, attrs=None): + def ssh_root_login_status(auth): + # XXX temporary placed here for when the ssh_root commands are integrated + # extracted from https://github.com/YunoHost/yunohost/pull/345 + # XXX should we support all the options? + # this is the content of "man sshd_config" + # PermitRootLogin + # Specifies whether root can log in using ssh(1). The argument must be + # “yes”, “without-password”, “forced-commands-only”, or “no”. The + # default is “yes”. + sshd_config_content = read_file(SSHD_CONFIG_PATH) + + if re.search("^ *PermitRootLogin +(no|forced-commands-only) *$", + sshd_config_content, re.MULTILINE): + return {"PermitRootLogin": False} + + return {"PermitRootLogin": True} + + if username == "root": + root_unix = pwd.getpwnam("root") + return { + 'username': 'root', + 'fullname': '', + 'mail': '', + 'ssh_allowed': ssh_root_login_status(auth)["PermitRootLogin"], + 'shell': root_unix.pw_shell, + 'home_path': root_unix.pw_dir, + } + if username == "admin": admin_unix = pwd.getpwnam("admin") return { From beb432bc6fca34ad11141083a3bef115197b6c9c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Jan 2018 18:05:01 +0100 Subject: [PATCH 044/132] Add a draft migration for tsig_sha256 based on existing stuff in dyndns.py --- .../0002_migrate_to_tsig_sha256.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py diff --git a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py new file mode 100644 index 000000000..df27a1f9f --- /dev/null +++ b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py @@ -0,0 +1,79 @@ +import glob + +from yunohost.tools import Migration +from moulinette.utils.log import getActionLogger +logger = getActionLogger('yunohost.migration') + + +class MigrateToTsigSha512(Migration): + "Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG": + + + def backward(self): + # Not possible because that's a non-reversible operation ? + pass + + + def forward(self): + + dyn_host="dyndns.yunohost.org" + + try: + (domain, private_key_path) = _guess_current_dyndns_domain(dyn_host) + except MoulinetteError: + logger.warning("migrate_tsig_not_needed") + + logger.warning(m18n.n('migrate_tsig_start', domain=domain)) + public_key_path = private_key_path.rsplit(".private", 1)[0] + ".key" + public_key_md5 = open(public_key_path).read().strip().split(' ')[-1] + + os.system('cd /etc/yunohost/dyndns && ' + 'dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER %s' % domain) + os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private') + + # +165 means that this file store a hmac-sha512 key + new_key_path = glob.glob('/etc/yunohost/dyndns/*+165*.key')[0] + public_key_sha512 = open(new_key_path).read().strip().split(' ', 6)[-1] + + try: + r = requests.put('https://%s/migrate_key_to_sha512/' % (dyn_host), + data={ + 'public_key_md5': base64.b64encode(public_key_md5), + 'public_key_sha512': base64.b64encode(public_key_sha512), + }, timeout=30) + except requests.ConnectionError: + raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) + + if r.status_code != 201: + try: + error = json.loads(r.text)['error'] + show_traceback = 0 + except Exception: + # failed to decode json + error = r.text + show_traceback = 1 + + logger.warning(m18n.n('migrate_tsig_failed', domain=domain, + error_code=str(r.status_code), error=error), + exc_info=show_traceback) + + os.system("mv /etc/yunohost/dyndns/*+165* /tmp") + return public_key_path + + # remove old certificates + os.system("mv /etc/yunohost/dyndns/*+157* /tmp") + + # sleep to wait for dyndns cache invalidation + logger.warning(m18n.n('migrate_tsig_wait')) + time.sleep(60) + logger.warning(m18n.n('migrate_tsig_wait_2')) + time.sleep(60) + logger.warning(m18n.n('migrate_tsig_wait_3')) + time.sleep(30) + logger.warning(m18n.n('migrate_tsig_wait_4')) + time.sleep(30) + + logger.warning(m18n.n('migrate_tsig_end')) + return new_key_path.rsplit(".key", 1)[0] + ".private" + + From aea13bc3e8816375b37ec33624d2fdcc6c5d1807 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 6 Jan 2018 18:36:29 +0100 Subject: [PATCH 045/132] [enh] save the conf/ directory of app during installation and upgrade --- src/yunohost/app.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 403e76cc4..87178464c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -621,10 +621,13 @@ def app_upgrade(auth, app=[], url=None, file=None): with open(app_setting_path + '/status.json', 'w+') as f: json.dump(status, f) - # Replace scripts and manifest - os.system('rm -rf "%s/scripts" "%s/manifest.json"' % (app_setting_path, app_setting_path)) + # Replace scripts and manifest and conf (if exists) + os.system('rm -rf "%s/scripts" "%s/manifest.json %/conf"' % (app_setting_path, app_setting_path, app_setting_path)) os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "conf")): + os.system('cp -R %s/conf %s' % (extracted_app_folder, app_setting_path)) + # So much win upgraded_apps.append(app_instance_name) logger.success(m18n.n('app_upgraded', app=app_instance_name)) @@ -733,6 +736,9 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False): os.system('cp %s/manifest.json %s' % (extracted_app_folder, app_setting_path)) os.system('cp -R %s/scripts %s' % (extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "conf")): + os.system('cp -R %s/conf %s' % (extracted_app_folder, app_setting_path)) + # Execute the app install script install_retcode = 1 try: From 7d9307c4c60edd2e7001100c86db95882b22ccca Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 6 Jan 2018 18:44:31 +0100 Subject: [PATCH 046/132] [doc$ add PULL_REQUEST_TEMPLATE.md --- src/yunohost/.github/PULL_REQUEST_TEMPLATE.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/yunohost/.github/PULL_REQUEST_TEMPLATE.md diff --git a/src/yunohost/.github/PULL_REQUEST_TEMPLATE.md b/src/yunohost/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..065c2dd90 --- /dev/null +++ b/src/yunohost/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## The problem + +... + +## Solution + +... + +## PR Status + +... + + +## Validation + +- [ ] Principle agreement 0/2 : +- [ ] Quick review 0/1 : +- [ ] Simple test 0/1 : +- [ ] Deep review 0/1 : From 6f8912f0d431df79ccfe1ed0811e1d80911815e5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Jan 2018 18:50:30 +0100 Subject: [PATCH 047/132] Fix a few things following tests --- .../0002_migrate_to_tsig_sha256.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py index df27a1f9f..26ea3a8b8 100644 --- a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py +++ b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py @@ -1,13 +1,21 @@ import glob +import os +import requests +import base64 +import time + +from moulinette import m18n +from moulinette.core import MoulinetteError +from moulinette.utils.log import getActionLogger from yunohost.tools import Migration -from moulinette.utils.log import getActionLogger +from yunohost.dyndns import _guess_current_dyndns_domain + logger = getActionLogger('yunohost.migration') -class MigrateToTsigSha512(Migration): - "Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG": - +class MyMigration(Migration): + "Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG" def backward(self): # Not possible because that's a non-reversible operation ? @@ -22,6 +30,7 @@ class MigrateToTsigSha512(Migration): (domain, private_key_path) = _guess_current_dyndns_domain(dyn_host) except MoulinetteError: logger.warning("migrate_tsig_not_needed") + return logger.warning(m18n.n('migrate_tsig_start', domain=domain)) public_key_path = private_key_path.rsplit(".private", 1)[0] + ".key" From 465aff4581cac68bd8776ab6a59c9a7fd868bf39 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Jan 2018 20:51:20 +0100 Subject: [PATCH 048/132] Be able to fetch a single migration by its name --- src/yunohost/tools.py | 63 ++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 042671125..024ae0da4 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -717,30 +717,10 @@ def tools_migrations_migrate(target=None, skip=False): # loading all migrations for migration in tools_migrations_list()["migrations"]: - logger.debug(m18n.n('migrations_loading_migration', - number=migration["number"], - name=migration["name"], - )) - - try: - # this is python builtin method to import a module using a name, we - # use that to import the migration as a python object so we'll be - # able to run it in the next loop - module = import_module("yunohost.data_migrations.{file_name}".format(**migration)) - except Exception: - import traceback - traceback.print_exc() - - raise MoulinetteError(errno.EINVAL, m18n.n('migrations_error_failed_to_load_migration', - number=migration["number"], - name=migration["name"], - )) - break - migrations.append({ "number": migration["number"], "name": migration["name"], - "module": module, + "module": _get_migration_module(migration), }) migrations = sorted(migrations, key=lambda x: x["number"]) @@ -877,6 +857,47 @@ def _get_migrations_list(): return sorted(migrations) +def _get_migration_by_name(migration_name, with_module=True): + """ + Low-level / "private" function to find a migration by its name + """ + + migrations = tools_migrations_list()["migrations"] + + matches = [ m for m in migrations if m["name"] == migration_name ] + + assert len(matches) == 1, "Unable to find migration with name %s" % migration_name + + migration = matches[0] + + if with_module: + migration["module"] = _get_migration_module(migration) + + return migration + + +def _get_migration_module(migration): + + logger.debug(m18n.n('migrations_loading_migration', + number=migration["number"], + name=migration["name"], + )) + + try: + # this is python builtin method to import a module using a name, we + # use that to import the migration as a python object so we'll be + # able to run it in the next loop + return import_module("yunohost.data_migrations.{file_name}".format(**migration)) + except Exception: + import traceback + traceback.print_exc() + + raise MoulinetteError(errno.EINVAL, m18n.n('migrations_error_failed_to_load_migration', + number=migration["number"], + name=migration["name"], + )) + + class Migration(object): def migrate(self): From 7e02e355d51dc819b183d697d9acc0e8c731248d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Jan 2018 21:01:10 +0100 Subject: [PATCH 049/132] Call the new migration from dyndns.py when MD5 is detected --- .../0002_migrate_to_tsig_sha256.py | 16 ++--- src/yunohost/dyndns.py | 68 +++---------------- 2 files changed, 19 insertions(+), 65 deletions(-) diff --git a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py index 26ea3a8b8..257f3525a 100644 --- a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py +++ b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py @@ -22,15 +22,15 @@ class MyMigration(Migration): pass - def forward(self): + def forward(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None): - dyn_host="dyndns.yunohost.org" - - try: - (domain, private_key_path) = _guess_current_dyndns_domain(dyn_host) - except MoulinetteError: - logger.warning("migrate_tsig_not_needed") - return + if domain in None or private_key_path is None: + try: + (domain, private_key_path) = _guess_current_dyndns_domain(dyn_host) + assert "+157" in private_key_path + except MoulinetteError: + logger.warning("migrate_tsig_not_needed") + return logger.warning(m18n.n('migrate_tsig_start', domain=domain)) public_key_path = private_key_path.rsplit(".private", 1)[0] + ".key" diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 6ff73d1e2..851d04f45 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -223,9 +223,18 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, key = keys[0] - # this mean that hmac-md5 is used + # This mean that hmac-md5 is used + # (Re?)Trigger the migration to sha256 and return immediately. + # The actual update will be done in next run. if "+157" in key: - key = _migrate_from_md5_tsig_to_sha512_tsig(key, domain, dyn_host) + from yunohost.tools import _get_migration_by_name + migration = _get_migration_by_name("migrate_to_tsig_sha256") + try: + migration["module"].MyMigration().migrate(dyn_host, domain, key) + except Exception as e: + logger.error(m18n.n('migrations_migration_has_failed', exception=e, **migration), exc_info=1) + + return # Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me' host = domain.split('.')[1:] @@ -292,61 +301,6 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, write_to_file(OLD_IPV6_FILE, ipv6) -def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host): - logger.warning(m18n.n('migrate_tsig_start', domain=domain)) - public_key_path = private_key_path.rsplit(".private", 1)[0] + ".key" - public_key_md5 = open(public_key_path).read().strip().split(' ')[-1] - - os.system('cd /etc/yunohost/dyndns && ' - 'dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER %s' % domain) - os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private') - - # +165 means that this file store a hmac-sha512 key - new_key_path = glob.glob('/etc/yunohost/dyndns/*+165*.key')[0] - public_key_sha512 = open(new_key_path).read().strip().split(' ', 6)[-1] - - try: - r = requests.put('https://%s/migrate_key_to_sha512/' % (dyn_host), - data={ - 'public_key_md5': base64.b64encode(public_key_md5), - 'public_key_sha512': base64.b64encode(public_key_sha512), - }, timeout=30) - except requests.ConnectionError: - raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) - - if r.status_code != 201: - try: - error = json.loads(r.text)['error'] - show_traceback = 0 - except Exception: - # failed to decode json - error = r.text - show_traceback = 1 - - logger.warning(m18n.n('migrate_tsig_failed', domain=domain, - error_code=str(r.status_code), error=error), - exc_info=show_traceback) - - os.system("mv /etc/yunohost/dyndns/*+165* /tmp") - return public_key_path - - # remove old certificates - os.system("mv /etc/yunohost/dyndns/*+157* /tmp") - - # sleep to wait for dyndns cache invalidation - logger.warning(m18n.n('migrate_tsig_wait')) - time.sleep(60) - logger.warning(m18n.n('migrate_tsig_wait_2')) - time.sleep(60) - logger.warning(m18n.n('migrate_tsig_wait_3')) - time.sleep(30) - logger.warning(m18n.n('migrate_tsig_wait_4')) - time.sleep(30) - - logger.warning(m18n.n('migrate_tsig_end')) - return new_key_path.rsplit(".key", 1)[0] + ".private" - - def dyndns_installcron(): """ Install IP update cron From 6bf80877af7ae0b0d3ad2530ea66e617ae8e2c72 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Jan 2018 21:42:47 +0100 Subject: [PATCH 050/132] Fixing a few stuff after tests.. --- .../0002_migrate_to_tsig_sha256.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py index 257f3525a..98c591716 100644 --- a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py +++ b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py @@ -3,6 +3,7 @@ import os import requests import base64 import time +import json from moulinette import m18n from moulinette.core import MoulinetteError @@ -21,14 +22,13 @@ class MyMigration(Migration): # Not possible because that's a non-reversible operation ? pass + def migrate(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None): - def forward(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None): - - if domain in None or private_key_path is None: + if domain is None or private_key_path is None: try: (domain, private_key_path) = _guess_current_dyndns_domain(dyn_host) - assert "+157" in private_key_path - except MoulinetteError: + #assert "+157" in private_key_path + except (MoulinetteError, AssertionError): logger.warning("migrate_tsig_not_needed") return @@ -56,18 +56,21 @@ class MyMigration(Migration): if r.status_code != 201: try: error = json.loads(r.text)['error'] - show_traceback = 0 except Exception: # failed to decode json error = r.text - show_traceback = 1 - logger.warning(m18n.n('migrate_tsig_failed', domain=domain, - error_code=str(r.status_code), error=error), - exc_info=show_traceback) + import traceback + from StringIO import StringIO + stack = StringIO() + traceback.print_exc(file=stack) + logger.error(stack.getvalue()) + # Migration didn't succeed, so we rollback and raise an exception os.system("mv /etc/yunohost/dyndns/*+165* /tmp") - return public_key_path + + raise MoulinetteError(m18n.n('migrate_tsig_failed', domain=domain, + error_code=str(r.status_code), error=error)) # remove old certificates os.system("mv /etc/yunohost/dyndns/*+157* /tmp") @@ -83,6 +86,5 @@ class MyMigration(Migration): time.sleep(30) logger.warning(m18n.n('migrate_tsig_end')) - return new_key_path.rsplit(".key", 1)[0] + ".private" - + return From 08caf2e07ff380f0613668c1e93e85d5025116b9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Jan 2018 22:42:37 +0100 Subject: [PATCH 051/132] Fixing a few stuff after tests.. --- src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py index 98c591716..15f092b75 100644 --- a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py +++ b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py @@ -27,7 +27,7 @@ class MyMigration(Migration): if domain is None or private_key_path is None: try: (domain, private_key_path) = _guess_current_dyndns_domain(dyn_host) - #assert "+157" in private_key_path + assert "+157" in private_key_path except (MoulinetteError, AssertionError): logger.warning("migrate_tsig_not_needed") return @@ -63,7 +63,7 @@ class MyMigration(Migration): import traceback from StringIO import StringIO stack = StringIO() - traceback.print_exc(file=stack) + traceback.print_stack(file=stack) logger.error(stack.getvalue()) # Migration didn't succeed, so we rollback and raise an exception From f20ef340dc9aa648b5d9d94629076717a7a7b36e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 7 Jan 2018 02:04:18 +0100 Subject: [PATCH 052/132] [fix] .github was not in the right folder --- {src/yunohost/.github => .github}/PULL_REQUEST_TEMPLATE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {src/yunohost/.github => .github}/PULL_REQUEST_TEMPLATE.md (100%) diff --git a/src/yunohost/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from src/yunohost/.github/PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md From 297026e6548ef1763f6c5d51bef1c940f02e532d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 7 Jan 2018 16:43:10 +0100 Subject: [PATCH 053/132] [fix] improve UX, previous message was unclear for users qsd --- locales/en.json | 2 +- src/yunohost/app.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 8dac6e799..6e6ed6daa 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,7 +19,7 @@ "app_incompatible": "The app is incompatible with your YunoHost version", "app_install_files_invalid": "Invalid installation files", "app_location_already_used": "An app is already installed in this location", - "app_location_install_failed": "Unable to install the app in this location", + "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", "app_location_unavailable": "This url is not available or conflicts with an already installed app", "app_manifest_invalid": "Invalid app manifest: {error}", "app_no_upgrade": "No app to upgrade", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 403e76cc4..5599c0d53 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1169,10 +1169,12 @@ def app_checkurl(auth, url, app=None): continue if path == p: raise MoulinetteError(errno.EINVAL, - m18n.n('app_location_already_used')) + m18n.n('app_location_already_used', + app=a["id"], path=path)) elif path.startswith(p) or p.startswith(path): raise MoulinetteError(errno.EPERM, - m18n.n('app_location_install_failed')) + m18n.n('app_location_install_failed', + other_path=p, other_app=a['id'])) if app is not None and not installed: app_setting(app, 'domain', value=domain) From 9cd760defd74e815408ece4a7960631692681a0d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 7 Jan 2018 17:04:48 +0100 Subject: [PATCH 054/132] [enh] make exceptions messages more obivous --- locales/en.json | 3 ++- src/yunohost/app.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6e6ed6daa..1edf2609c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -18,7 +18,8 @@ "app_id_invalid": "Invalid app id", "app_incompatible": "The app is incompatible with your YunoHost version", "app_install_files_invalid": "Invalid installation files", - "app_location_already_used": "An app is already installed in this location", + "app_location_already_used": "The app '{app}' is already installed on that location ({path})", + "app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain {domain} is already used by the other app '{other_app}'", "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", "app_location_unavailable": "This url is not available or conflicts with an already installed app", "app_manifest_invalid": "Invalid app manifest: {error}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5599c0d53..851fcdaa6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1016,7 +1016,9 @@ def app_makedefault(auth, app, domain=None): if '/' in app_map(raw=True)[domain]: raise MoulinetteError(errno.EEXIST, - m18n.n('app_location_already_used')) + m18n.n('app_make_default_location_already_used', + app=app, domain=app_domain, + other_app=app_map(raw=True)[domain]["/"]["id"])) try: with open('/etc/ssowat/conf.json.persistent') as json_conf: From e8196cbc9855ab460ec9cbcb6c3505e70eb46be1 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 7 Jan 2018 17:23:02 +0100 Subject: [PATCH 055/132] [doc] add comment to speak intent --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 851fcdaa6..d9ca5977f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1173,6 +1173,7 @@ def app_checkurl(auth, url, app=None): raise MoulinetteError(errno.EINVAL, m18n.n('app_location_already_used', app=a["id"], path=path)) + # can't install "/a/b/" if "/a/" exists elif path.startswith(p) or p.startswith(path): raise MoulinetteError(errno.EPERM, m18n.n('app_location_install_failed', From 0922568d64686f996aedb5d87dfdb41522757c2d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 7 Jan 2018 17:34:27 +0100 Subject: [PATCH 056/132] [fix] was replacing the dictionnary with a string and thus breaking everything --- src/yunohost/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 042671125..318c08ce6 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -570,8 +570,8 @@ def tools_diagnosis(auth, private=False): else: diagnosis['system']['disks'] = {} for disk in disks: - if isinstance(disk, str): - diagnosis['system']['disks'] = disk + if isinstance(disks[disk], str): + diagnosis['system']['disks'][disk] = disks[disk] else: diagnosis['system']['disks'][disk] = 'Mounted on %s, %s (%s free)' % ( disks[disk]['mnt_point'], From 97fea34124ffe2fb222bf2a401ea76a72facc525 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 7 Jan 2018 20:23:00 +0100 Subject: [PATCH 057/132] [enh] display regen-conf in private diagnosis --- src/yunohost/tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 042671125..97664ba45 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -624,6 +624,8 @@ def tools_diagnosis(auth, private=False): # Domains diagnosis['private']['domains'] = domain_list(auth)['domains'] + diagnosis['private']['regen_conf'] = service_regen_conf(with_diff=True, dry_run=True) + return diagnosis From 70e08ed40f32875fc1389a39c2ee0628ffecdf1e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 8 Jan 2018 02:47:36 +0100 Subject: [PATCH 058/132] [fix] pkg is None, can't continue loop --- src/yunohost/utils/packages.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 9242d22d1..bc822da1f 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -414,6 +414,8 @@ def get_installed_version(*pkgnames, **kwargs): if strict: raise UnknownPackage(pkgname) logger.warning(m18n.n('package_unknown', pkgname=pkgname)) + continue + try: version = pkg.installed.version except AttributeError: From e0edbeca357f7aecae159a1e7c71aef006d9462d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 8 Jan 2018 03:10:16 +0100 Subject: [PATCH 059/132] [enh] --version now display stable/testing/unstable information --- src/yunohost/utils/packages.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index bc822da1f..18c4f2a3d 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -422,7 +422,21 @@ def get_installed_version(*pkgnames, **kwargs): if strict: raise UninstalledPackage(pkgname) version = None - versions[pkgname] = version + + try: + # stable, testing, unstable + repo = pkg.installed.origins[0].component + except AttributeError: + if strict: + raise UninstalledPackage(pkgname) + repo = "" + + versions[pkgname] = { + "version": version, + # when we don't have component it's because it's from a local + # install or from an image (like in vagrant) + "repo": repo if repo else "local", + } if len(pkgnames) == 1 and not as_dict: return versions[pkgnames[0]] @@ -438,6 +452,9 @@ def meets_version_specifier(pkgname, specifier): # YunoHost related methods --------------------------------------------------- def ynh_packages_version(*args, **kwargs): + # from cli the received arguments are: + # (Namespace(_callbacks=deque([]), _tid='_global', _to_return={}), []) {} + # they don't seems to serve any purpose """Return the version of each YunoHost package""" return get_installed_version( 'yunohost', 'yunohost-admin', 'moulinette', 'ssowat', From d7967f6d5fde81d4537bff8d2de1f723d1fceec4 Mon Sep 17 00:00:00 2001 From: JocelynDelalande Date: Mon, 8 Jan 2018 17:59:53 +0100 Subject: [PATCH 060/132] Fix comment lines in DNS zone example (using ";") If copy-pasted into a registrar zone file, the provided DNS zone sample for a given domain will fail, because comments lines start with "#" However, comments character in DNS zone files is ";" not "#" https://en.wikipedia.org/wiki/Zone_file --- src/yunohost/domain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index f828b0973..727a63df3 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -188,17 +188,17 @@ def domain_dns_conf(domain, ttl=None): result = "" - result += "# Basic ipv4/ipv6 records" + result += "; Basic ipv4/ipv6 records" for record in dns_conf["basic"]: result += "\n{name} {ttl} IN {type} {value}".format(**record) result += "\n\n" - result += "# XMPP" + result += "; XMPP" for record in dns_conf["xmpp"]: result += "\n{name} {ttl} IN {type} {value}".format(**record) result += "\n\n" - result += "# Mail" + result += "; Mail" for record in dns_conf["mail"]: result += "\n{name} {ttl} IN {type} {value}".format(**record) From 436c6d74bb8e960513e8589f3a2e00341519b13b Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 9 Jan 2018 12:07:47 +0100 Subject: [PATCH 061/132] [fix] Add ability to symlink the archives dir --- src/yunohost/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index def7fb27b..4700c63d3 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2300,7 +2300,7 @@ def backup_delete(name): def _create_archive_dir(): """ Create the YunoHost archives directory if doesn't exist """ - if not os.path.isdir(ARCHIVES_PATH): + if not os.path.isdir(ARCHIVES_PATH) and not os.path.islink(ARCHIVES_PATH): os.mkdir(ARCHIVES_PATH, 0750) From 8d2848a4264412600731fce371532d6f7007392b Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 9 Jan 2018 13:00:40 +0100 Subject: [PATCH 062/132] [enh] Warn users their symlink archives directory is broken --- locales/en.json | 1 + src/yunohost/backup.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 0aa8798fd..e59b71621 100644 --- a/locales/en.json +++ b/locales/en.json @@ -101,6 +101,7 @@ "backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", "backup_output_directory_not_empty": "The output directory is not empty", "backup_output_directory_required": "You must provide an output directory for the backup", + "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}', may be you have a specific setup, to backup your data on an other filesystem, in this case you probably miss to remount or plug your hard dirve or usb key.", "backup_running_app_script": "Running backup script of app '{app:s}'...", "backup_running_hooks": "Running backup hooks...", "backup_system_part_failed": "Unable to backup the '{part:s}' system part", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 4700c63d3..0c957db7e 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2300,7 +2300,12 @@ def backup_delete(name): def _create_archive_dir(): """ Create the YunoHost archives directory if doesn't exist """ - if not os.path.isdir(ARCHIVES_PATH) and not os.path.islink(ARCHIVES_PATH): + if not os.path.isdir(ARCHIVES_PATH): + if os.path.lexists(ARCHIVES_PATH): + raise MoulinetteError(errno.EINVAL, + m18n.n('backup_output_symlink_dir_broken', + path=ARCHIVES_PATH)) + os.mkdir(ARCHIVES_PATH, 0750) From efb841683946270e56e85f2f1bbf1fecbd410db5 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 9 Jan 2018 13:12:46 +0100 Subject: [PATCH 063/132] [fix] English sentences --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index e59b71621..5f5eb957f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -101,7 +101,7 @@ "backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", "backup_output_directory_not_empty": "The output directory is not empty", "backup_output_directory_required": "You must provide an output directory for the backup", - "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}', may be you have a specific setup, to backup your data on an other filesystem, in this case you probably miss to remount or plug your hard dirve or usb key.", + "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably miss to remount or plug your hard dirve or usb key.", "backup_running_app_script": "Running backup script of app '{app:s}'...", "backup_running_hooks": "Running backup hooks...", "backup_system_part_failed": "Unable to backup the '{part:s}' system part", From 4f679367eb8905aee52a76dcefac4a2190a9a382 Mon Sep 17 00:00:00 2001 From: JimboJoe Date: Tue, 9 Jan 2018 20:30:43 +0100 Subject: [PATCH 064/132] Typo --- src/yunohost/utils/packages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 18c4f2a3d..7df311406 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -454,7 +454,7 @@ def meets_version_specifier(pkgname, specifier): def ynh_packages_version(*args, **kwargs): # from cli the received arguments are: # (Namespace(_callbacks=deque([]), _tid='_global', _to_return={}), []) {} - # they don't seems to serve any purpose + # they don't seem to serve any purpose """Return the version of each YunoHost package""" return get_installed_version( 'yunohost', 'yunohost-admin', 'moulinette', 'ssowat', From 20f0e9a40c67fc9b373cd4ae33a982628c816552 Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Tue, 9 Jan 2018 23:01:19 +0100 Subject: [PATCH 065/132] Fix conf dir clean-up --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 87178464c..7b6195e60 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -622,7 +622,7 @@ def app_upgrade(auth, app=[], url=None, file=None): json.dump(status, f) # Replace scripts and manifest and conf (if exists) - os.system('rm -rf "%s/scripts" "%s/manifest.json %/conf"' % (app_setting_path, app_setting_path, app_setting_path)) + os.system('rm -rf "%s/scripts" "%s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path)) os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) if os.path.exists(os.path.join(extracted_app_folder, "conf")): From 9c44878d9203bf174bfc7c9ae164e57d781a5b5e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 9 Jan 2018 23:21:33 +0100 Subject: [PATCH 066/132] [mod] add section in PR template to inform on how to test a PR --- .github/PULL_REQUEST_TEMPLATE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 065c2dd90..9642e92f6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,6 +10,9 @@ ... +## How to test + +... ## Validation From 28450ce8c30249b28132257a5396875104ac5a1d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 10 Jan 2018 16:30:20 +0100 Subject: [PATCH 067/132] [enh] new command 'app change-label' --- data/actionsmap/yunohost.yml | 13 +++++++++++++ src/yunohost/app.py | 11 +++++++++++ 2 files changed, 24 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 966de21df..101ac45e1 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -647,6 +647,19 @@ app: authenticate: all authenticator: ldap-anonymous + ### app_change_label() + change-label: + action_help: Change an application label + api: PUT /apps//label + configuration: + authenticate: all + authenticator: ldap-anonymous + arguments: + app: + help: the application id + new_label: + help: the new application label + ### app_addaccess() TODO: Write help addaccess: action_help: Grant access right to users (everyone by default) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 15855753c..9ccc0886d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1320,6 +1320,17 @@ def app_ssowatconf(auth): logger.success(m18n.n('ssowat_conf_generated')) +def app_change_label(auth, app, new_label): + installed = _is_installed(app) + if not installed: + raise MoulinetteError(errno.ENOPKG, + m18n.n('app_not_installed', app=app)) + + app_setting(app, "label", value=new_label) + + app_ssowatconf(auth) + + def _get_app_settings(app_id): """ Get settings of an installed app From 2b2676a9c1d6f487cbbda00a17a6e3e8ff4a40cc Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 7 Jan 2018 23:03:22 +0100 Subject: [PATCH 068/132] [enh] add nginx -t output to diagnosis --- src/yunohost/tools.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 042671125..8262e6682 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -42,6 +42,7 @@ import apt.progress from moulinette import msettings, msignals, m18n from moulinette.core import MoulinetteError, init_authenticator from moulinette.utils.log import getActionLogger +from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_json, write_to_json from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain @@ -589,6 +590,9 @@ def tools_diagnosis(auth, private=False): 'swap': '%s (%s free)' % (system['memory']['swap']['total'], system['memory']['swap']['free']), } + # nginx -t + diagnosis['nginx'] = check_output("nginx -t").strip().split("\n") + # Services status services = service_status() diagnosis['services'] = {} From 07849175736bbe0372f6e0867da731184ea4e7af Mon Sep 17 00:00:00 2001 From: taziden Date: Mon, 2 Oct 2017 13:44:41 +0200 Subject: [PATCH 069/132] Create milter_headers.conf This file define a header : X-Spam which will be added to mail considered as spam by rspamd. This configuration is defined here : https://rspamd.com/doc/modules/milter_headers.html and as of the time I'm creating this PR, this documentation page is not up-to-date because X-Spam was supposed to be added when extended_spam_headers is set to True and it's not the case anymore since 1.6.2. This header is being used by rspamd.sieve which we push into dovecot in order to put spammy e-mails in Junk folder automatically. --- data/templates/rspamd/milter_headers.conf | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 data/templates/rspamd/milter_headers.conf diff --git a/data/templates/rspamd/milter_headers.conf b/data/templates/rspamd/milter_headers.conf new file mode 100644 index 000000000..d57aa6958 --- /dev/null +++ b/data/templates/rspamd/milter_headers.conf @@ -0,0 +1,9 @@ +use = ["spam-header"]; + +routines { + spam-header { + header = "X-Spam"; + value = "Yes"; + remove = 1; + } +} From 3ed6a2a7eff79c813ffbecec961e7938905767f5 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 11 Jan 2018 15:15:22 +0100 Subject: [PATCH 070/132] Revert "Create milter_headers.conf" This reverts commit 0122c26492460f618beed86d34c5beee1e8cf25c. --- data/templates/rspamd/milter_headers.conf | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 data/templates/rspamd/milter_headers.conf diff --git a/data/templates/rspamd/milter_headers.conf b/data/templates/rspamd/milter_headers.conf deleted file mode 100644 index d57aa6958..000000000 --- a/data/templates/rspamd/milter_headers.conf +++ /dev/null @@ -1,9 +0,0 @@ -use = ["spam-header"]; - -routines { - spam-header { - header = "X-Spam"; - value = "Yes"; - remove = 1; - } -} From 935f6091368aecbfcf04eb05aae07254604ed691 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 7 Jan 2018 21:46:38 +0100 Subject: [PATCH 071/132] [enh] display backports .deb in diagnosis --- src/yunohost/tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 70a757d03..1c7b8b87a 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -562,6 +562,8 @@ def tools_diagnosis(auth, private=False): # Packages version diagnosis['packages'] = ynh_packages_version() + diagnosis["backports"] = check_output("dpkg -l |awk '/^ii/ && $3 ~ /bpo[6-8]/ {print $2}'").split() + # Server basic monitoring diagnosis['system'] = OrderedDict() try: From e59e2da0b31bb2af8727ab1e87021650bf2818d4 Mon Sep 17 00:00:00 2001 From: JimboJoe Date: Fri, 12 Jan 2018 09:18:31 +0100 Subject: [PATCH 072/132] Harmonize help strings --- data/actionsmap/yunohost.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 101ac45e1..076e5599c 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -649,16 +649,16 @@ app: ### app_change_label() change-label: - action_help: Change an application label + action_help: Change app label api: PUT /apps//label configuration: authenticate: all authenticator: ldap-anonymous arguments: app: - help: the application id + help: App ID new_label: - help: the new application label + help: New app label ### app_addaccess() TODO: Write help addaccess: From b60d8ca822d08c8e3fdf8a17505ff3e285b28164 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 5 Jan 2018 12:54:11 +0100 Subject: [PATCH 073/132] [enh] add new api entry point to check for meltdown vulnerability --- data/actionsmap/yunohost.yml | 5 +++++ src/yunohost/tools.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 966de21df..9e8022964 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1460,6 +1460,11 @@ tools: full: --force action: store_true + ### tools_reboot() + meltdown-spectre-check: + action_help: Check if the server is vulnerable to meltdown/spectre + api: GET /meltdown-spectre-check + subcategories: migrations: diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index cf52ad38f..13f3ea5fd 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -836,6 +836,14 @@ def tools_migrations_state(): return read_json(MIGRATIONS_STATE_PATH) +def tools_meltdown_spectre_check(): + """ + Check if the installation is vulnerable to meltdown/spectre. + """ + # source https://askubuntu.com/questions/992137/how-to-check-that-kpti-is-enabled-on-my-ubuntu + return {"safe": "cpu_insecure" in open("/proc/cpuinfo")} + + def tools_shell(auth, command=None): """ Launch an (i)python shell in the YunoHost context. From a934b3fd19403859cf6a46713b82945524f9ac28 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 5 Jan 2018 16:30:33 +0100 Subject: [PATCH 074/132] [mod] move spectre-meltdown check to diagnosis function --- data/actionsmap/yunohost.yml | 5 ----- src/yunohost/tools.py | 17 +++++++++-------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 9e8022964..966de21df 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1460,11 +1460,6 @@ tools: full: --force action: store_true - ### tools_reboot() - meltdown-spectre-check: - action_help: Check if the server is vulnerable to meltdown/spectre - api: GET /meltdown-spectre-check - subcategories: migrations: diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 13f3ea5fd..46bcd06d5 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -42,8 +42,12 @@ import apt.progress from moulinette import msettings, msignals, m18n from moulinette.core import MoulinetteError, init_authenticator from moulinette.utils.log import getActionLogger +<<<<<<< b60d8ca822d08c8e3fdf8a17505ff3e285b28164 from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_json, write_to_json +======= +from moulinette.utils.filesystem import read_json, write_to_json, read_file +>>>>>>> [mod] move spectre-meltdown check to diagnosis function from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain from yunohost.dyndns import _dyndns_available, _dyndns_provides @@ -632,6 +636,11 @@ def tools_diagnosis(auth, private=False): diagnosis['private']['regen_conf'] = service_regen_conf(with_diff=True, dry_run=True) + diagnosis['security'] = { + # source https://askubuntu.com/questions/992137/how-to-check-that-kpti-is-enabled-on-my-ubuntu + "spectre-meltdown": "cpu_insecure" not in read_file("/proc/cpuinfo") + } + return diagnosis @@ -836,14 +845,6 @@ def tools_migrations_state(): return read_json(MIGRATIONS_STATE_PATH) -def tools_meltdown_spectre_check(): - """ - Check if the installation is vulnerable to meltdown/spectre. - """ - # source https://askubuntu.com/questions/992137/how-to-check-that-kpti-is-enabled-on-my-ubuntu - return {"safe": "cpu_insecure" in open("/proc/cpuinfo")} - - def tools_shell(auth, command=None): """ Launch an (i)python shell in the YunoHost context. From f46351c7c50297ce4e5b39cbc61a1c5cc466ddb2 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 13 Jan 2018 05:19:39 +0100 Subject: [PATCH 075/132] [enh] uses speed47 script to check for meltdown vulnerability --- data/other/spectre-meltdown-checker.sh | 980 +++++++++++++++++++++++++ src/yunohost/tools.py | 25 +- 2 files changed, 999 insertions(+), 6 deletions(-) create mode 100644 data/other/spectre-meltdown-checker.sh diff --git a/data/other/spectre-meltdown-checker.sh b/data/other/spectre-meltdown-checker.sh new file mode 100644 index 000000000..5658cf0a8 --- /dev/null +++ b/data/other/spectre-meltdown-checker.sh @@ -0,0 +1,980 @@ +#! /bin/sh + +# Message from YunoHost team: +# this is this version https://github.com/speed47/spectre-meltdown-checker/blob/dce917bfbb4fa5e135b7ed4c84881086766be802/spectre-meltdown-checker.sh +# that is included here +# You can find the licence (GPLv2))and author name in this script + +# Spectre & Meltdown checker +# +# Check for the latest version at: +# https://github.com/speed47/spectre-meltdown-checker +# git clone https://github.com/speed47/spectre-meltdown-checker.git +# or wget https://raw.githubusercontent.com/speed47/spectre-meltdown-checker/master/spectre-meltdown-checker.sh +# +# Stephane Lesimple + +VERSION=0.28 + +# Script configuration +show_usage() +{ + cat <] [--config ] [--map ] + + Modes: + Two modes are available. + + First mode is the "live" mode (default), it does its best to find information about the currently running kernel. + To run under this mode, just start the script without any option (you can also use --live explicitly) + + Second mode is the "offline" mode, where you can inspect a non-running kernel. + You'll need to specify the location of the vmlinux file, and if possible, the corresponding config and System.map files: + + --kernel vmlinux_file Specify a (possibly compressed) vmlinux file + --config kernel_config Specify a kernel config file + --map kernel_map_file Specify a kernel System.map file + + Options: + --no-color Don't use color codes + --verbose, -v Increase verbosity level + --no-sysfs Don't use the /sys interface even if present + --batch text Produce machine readable output, this is the default if --batch is specified alone + --batch json Produce JSON output formatted for Puppet, Ansible, Chef... + --batch nrpe Produce machine readable output formatted for NRPE + --variant [1,2,3] Specify which variant you'd like to check, by default all variants are checked + Can be specified multiple times (e.g. --variant 2 --variant 3) + + + IMPORTANT: + A false sense of security is worse than no security at all. + Please use the --disclaimer option to understand exactly what this script does. + +EOF +} + +show_disclaimer() +{ + cat <&2 +} + +_info() +{ + _echo 1 "$@" +} + +_info_nol() +{ + _echo_nol 1 "$@" +} + +_verbose() +{ + _echo 2 "$@" +} + +_debug() +{ + _echo 3 "\033[34m(debug) $@\033[0m" +} + +is_cpu_vulnerable() +{ + # param: 1, 2 or 3 (variant) + # returns 1 if vulnerable, 0 if not vulnerable, 255 on error + # by default, everything is vulnerable, we work in a "whitelist" logic here. + # usage: is_cpu_vulnerable 2 && do something if vulnerable + variant1=0 + variant2=0 + variant3=0 + if grep -q AMD /proc/cpuinfo; then + variant1=0 + variant2=1 + variant3=1 + elif grep -qi 'CPU implementer\s*:\s*0x41' /proc/cpuinfo; then + # ARM + # reference: https://developer.arm.com/support/security-update + cpupart=$(awk '/CPU part/ {print $4;exit}' /proc/cpuinfo) + cpuarch=$(awk '/CPU architecture/ {print $3;exit}' /proc/cpuinfo) + if [ -n "$cpupart" -a -n "$cpuarch" ]; then + # Cortex-R7 and Cortex-R8 are real-time and only used in medical devices or such + # I can't find their CPU part number, but it's probably not that useful anyway + # model R7 R8 A9 A15 A17 A57 A72 A73 A75 + # part ? ? 0xc09 0xc0f 0xc0e 0xd07 0xd08 0xd09 0xd0a + # arch 7? 7? 7 7 7 8 8 8 8 + if [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -Eq '^0x(c09|c0f|c0e)$'; then + # armv7 vulnerable chips + variant1=0 + variant2=0 + elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -Eq '^0x(d07|d08|d09|d0a)$'; then + # armv8 vulnerable chips + variant1=0 + variant2=0 + else + variant1=1 + variant2=1 + fi + # for variant3, only A75 is vulnerable + if [ "$cpuarch" = 8 -a "$cpupart" = 0xd0a ]; then + variant3=0 + else + variant3=1 + fi + fi + fi + [ "$1" = 1 ] && return $variant1 + [ "$1" = 2 ] && return $variant2 + [ "$1" = 3 ] && return $variant3 + return 255 +} + +show_header() +{ + _info "\033[1;34mSpectre and Meltdown mitigation detection tool v$VERSION\033[0m" + _info +} + +parse_opt_file() +{ + # parse_opt_file option_name option_value + option_name="$1" + option_value="$2" + if [ -z "$option_value" ]; then + show_header + show_usage + echo "$0: error: --$option_name expects one parameter (a file)" >&2 + exit 1 + elif [ ! -e "$option_value" ]; then + show_header + echo "$0: error: couldn't find file $option_value" >&2 + exit 1 + elif [ ! -f "$option_value" ]; then + show_header + echo "$0: error: $option_value is not a file" >&2 + exit 1 + elif [ ! -r "$option_value" ]; then + show_header + echo "$0: error: couldn't read $option_value (are you root?)" >&2 + exit 1 + fi + echo "$option_value" + exit 0 +} + +while [ -n "$1" ]; do + if [ "$1" = "--kernel" ]; then + opt_kernel=$(parse_opt_file kernel "$2") + [ $? -ne 0 ] && exit $? + shift 2 + opt_live=0 + elif [ "$1" = "--config" ]; then + opt_config=$(parse_opt_file config "$2") + [ $? -ne 0 ] && exit $? + shift 2 + opt_live=0 + elif [ "$1" = "--map" ]; then + opt_map=$(parse_opt_file map "$2") + [ $? -ne 0 ] && exit $? + shift 2 + opt_live=0 + elif [ "$1" = "--live" ]; then + opt_live_explicit=1 + shift + elif [ "$1" = "--no-color" ]; then + opt_no_color=1 + shift + elif [ "$1" = "--no-sysfs" ]; then + opt_no_sysfs=1 + shift + elif [ "$1" = "--batch" ]; then + opt_batch=1 + opt_verbose=0 + shift + case "$1" in + text|nrpe|json) opt_batch_format="$1"; shift;; + --*) ;; # allow subsequent flags + '') ;; # allow nothing at all + *) + echo "$0: error: unknown batch format '$1'" + echo "$0: error: --batch expects a format from: text, nrpe, json" + exit 1 >&2 + ;; + esac + elif [ "$1" = "-v" -o "$1" = "--verbose" ]; then + opt_verbose=$(expr $opt_verbose + 1) + shift + elif [ "$1" = "--variant" ]; then + if [ -z "$2" ]; then + echo "$0: error: option --variant expects a parameter (1, 2 or 3)" >&2 + exit 1 + fi + case "$2" in + 1) opt_variant1=1; opt_allvariants=0;; + 2) opt_variant2=1; opt_allvariants=0;; + 3) opt_variant3=1; opt_allvariants=0;; + *) + echo "$0: error: invalid parameter '$2' for --variant, expected either 1, 2 or 3" >&2; + exit 1;; + esac + shift 2 + elif [ "$1" = "-h" -o "$1" = "--help" ]; then + show_header + show_usage + exit 0 + elif [ "$1" = "--version" ]; then + opt_no_color=1 + show_header + exit 1 + elif [ "$1" = "--disclaimer" ]; then + show_header + show_disclaimer + exit 0 + else + show_header + show_usage + echo "$0: error: unknown option '$1'" + exit 1 + fi +done + +show_header + +# print status function +pstatus() +{ + if [ "$opt_no_color" = 1 ]; then + _info_nol "$2" + else + case "$1" in + red) col="\033[101m\033[30m";; + green) col="\033[102m\033[30m";; + yellow) col="\033[103m\033[30m";; + blue) col="\033[104m\033[30m";; + *) col="";; + esac + _info_nol "$col $2 \033[0m" + fi + [ -n "$3" ] && _info_nol " ($3)" + _info +} + +# Print the final status of a vulnerability (incl. batch mode) +# Arguments are: CVE UNK/OK/VULN description +pvulnstatus() +{ + if [ "$opt_batch" = 1 ]; then + case "$opt_batch_format" in + text) _echo 0 "$1: $2 ($3)";; + nrpe) + case "$2" in + UKN) nrpe_unknown="1";; + VULN) nrpe_critical="1"; nrpe_vuln="$nrpe_vuln $1";; + esac + ;; + json) + case "$1" in + CVE-2017-5753) aka="SPECTRE VARIANT 1";; + CVE-2017-5715) aka="SPECTRE VARIANT 2";; + CVE-2017-5754) aka="MELTDOWN";; + esac + case "$2" in + UKN) is_vuln="unknown";; + VULN) is_vuln="true";; + OK) is_vuln="false";; + esac + json_output="${json_output:-[}{\"NAME\":\""$aka"\",\"CVE\":\""$1"\",\"VULNERABLE\":$is_vuln,\"INFOS\":\""$3"\"}," + ;; + esac + fi + + _info_nol "> \033[46m\033[30mSTATUS:\033[0m " + vulnstatus="$2" + shift 2 + case "$vulnstatus" in + UNK) pstatus yellow UNKNOWN "$@";; + VULN) pstatus red 'VULNERABLE' "$@";; + OK) pstatus green 'NOT VULNERABLE' "$@";; + esac +} + + +# The 3 below functions are taken from the extract-linux script, available here: +# https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux +# The functions have been modified for better integration to this script +# The original header of the file has been retained below + +# ---------------------------------------------------------------------- +# extract-vmlinux - Extract uncompressed vmlinux from a kernel image +# +# Inspired from extract-ikconfig +# (c) 2009,2010 Dick Streefland +# +# (c) 2011 Corentin Chary +# +# Licensed under the GNU General Public License, version 2 (GPLv2). +# ---------------------------------------------------------------------- + +vmlinux='' +vmlinux_err='' +check_vmlinux() +{ + readelf -h "$1" > /dev/null 2>&1 || return 1 + return 0 +} + +try_decompress() +{ + # The obscure use of the "tr" filter is to work around older versions of + # "grep" that report the byte offset of the line instead of the pattern. + + # Try to find the header ($1) and decompress from here + for pos in `tr "$1\n$2" "\n$2=" < "$6" | grep -abo "^$2"` + do + _debug "try_decompress: magic for $3 found at offset $pos" + if ! which "$3" >/dev/null 2>&1; then + vmlinux_err="missing '$3' tool, please install it, usually it's in the '$5' package" + return 0 + fi + pos=${pos%%:*} + tail -c+$pos "$6" 2>/dev/null | $3 $4 > $vmlinuxtmp 2>/dev/null + if check_vmlinux "$vmlinuxtmp"; then + vmlinux="$vmlinuxtmp" + _debug "try_decompress: decompressed with $3 successfully!" + return 0 + else + _debug "try_decompress: decompression with $3 did not work" + fi + done + return 1 +} + +extract_vmlinux() +{ + [ -n "$1" ] || return 1 + # Prepare temp files: + vmlinuxtmp="$(mktemp /tmp/vmlinux-XXXXXX)" + trap "rm -f $vmlinuxtmp" EXIT + + # Initial attempt for uncompressed images or objects: + if check_vmlinux "$1"; then + cat "$1" > "$vmlinuxtmp" + vmlinux=$vmlinuxtmp + return 0 + fi + + # That didn't work, so retry after decompression. + try_decompress '\037\213\010' xy gunzip '' gunzip "$1" && return 0 + try_decompress '\3757zXZ\000' abcde unxz '' xz-utils "$1" && return 0 + try_decompress 'BZh' xy bunzip2 '' bzip2 "$1" && return 0 + try_decompress '\135\0\0\0' xxx unlzma '' xz-utils "$1" && return 0 + try_decompress '\211\114\132' xy 'lzop' '-d' lzop "$1" && return 0 + try_decompress '\002\041\114\030' xyy 'lz4' '-d -l' liblz4-tool "$1" && return 0 + return 1 +} + +# end of extract-vmlinux functions + +# check for mode selection inconsistency +if [ "$opt_live_explicit" = 1 ]; then + if [ -n "$opt_kernel" -o -n "$opt_config" -o -n "$opt_map" ]; then + show_usage + echo "$0: error: incompatible modes specified, use either --live or --kernel/--config/--map" + exit 1 + fi +fi + +# root check (only for live mode, for offline mode, we already checked if we could read the files) + +if [ "$opt_live" = 1 ]; then + if [ "$(id -u)" -ne 0 ]; then + _warn "Note that you should launch this script with root privileges to get accurate information." + _warn "We'll proceed but you might see permission denied errors." + _warn "To run it as root, you can try the following command: sudo $0" + _warn + fi + _info "Checking for vulnerabilities against running kernel \033[35m"$(uname -s) $(uname -r) $(uname -v) $(uname -m)"\033[0m" + _info "CPU is\033[35m"$(grep '^model name' /proc/cpuinfo | cut -d: -f2 | head -1)"\033[0m" + + # try to find the image of the current running kernel + # first, look for the BOOT_IMAGE hint in the kernel cmdline + if [ -r /proc/cmdline ] && grep -q 'BOOT_IMAGE=' /proc/cmdline; then + opt_kernel=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2) + _debug "found opt_kernel=$opt_kernel in /proc/cmdline" + # if we have a dedicated /boot partition, our bootloader might have just called it / + # so try to prepend /boot and see if we find anything + [ -e "/boot/$opt_kernel" ] && opt_kernel="/boot/$opt_kernel" + _debug "opt_kernel is now $opt_kernel" + # else, the full path is already there (most probably /boot/something) + fi + # if we didn't find a kernel, default to guessing + if [ ! -e "$opt_kernel" ]; then + [ -e /boot/vmlinuz-linux ] && opt_kernel=/boot/vmlinuz-linux + [ -e /boot/vmlinuz-linux-libre ] && opt_kernel=/boot/vmlinuz-linux-libre + [ -e /boot/vmlinuz-$(uname -r) ] && opt_kernel=/boot/vmlinuz-$(uname -r) + [ -e /boot/kernel-$( uname -r) ] && opt_kernel=/boot/kernel-$( uname -r) + [ -e /boot/bzImage-$(uname -r) ] && opt_kernel=/boot/bzImage-$(uname -r) + [ -e /boot/kernel-genkernel-$(uname -m)-$(uname -r) ] && opt_kernel=/boot/kernel-genkernel-$(uname -m)-$(uname -r) + fi + + # system.map + if [ -e /proc/kallsyms ] ; then + opt_map="/proc/kallsyms" + elif [ -e /boot/System.map-$(uname -r) ] ; then + opt_map=/boot/System.map-$(uname -r) + fi + + # config + if [ -e /proc/config.gz ] ; then + dumped_config="$(mktemp /tmp/config-XXXXXX)" + gunzip -c /proc/config.gz > $dumped_config + # dumped_config will be deleted at the end of the script + opt_config=$dumped_config + elif [ -e /boot/config-$(uname -r) ]; then + opt_config=/boot/config-$(uname -r) + fi +else + _info "Checking for vulnerabilities against specified kernel" +fi +if [ -n "$opt_kernel" ]; then + _verbose "Will use vmlinux image \033[35m$opt_kernel\033[0m" +else + _verbose "Will use no vmlinux image (accuracy might be reduced)" +fi +if [ -n "$dumped_config" ]; then + _verbose "Will use kconfig \033[35m/proc/config.gz\033[0m" +elif [ -n "$opt_config" ]; then + _verbose "Will use kconfig \033[35m$opt_config\033[0m" +else + _verbose "Will use no kconfig (accuracy might be reduced)" +fi +if [ -n "$opt_map" ]; then + _verbose "Will use System.map file \033[35m$opt_map\033[0m" +else + _verbose "Will use no System.map file (accuracy might be reduced)" +fi + +if [ -e "$opt_kernel" ]; then + if ! which readelf >/dev/null 2>&1; then + vmlinux_err="missing 'readelf' tool, please install it, usually it's in the 'binutils' package" + else + extract_vmlinux "$opt_kernel" + fi +else + vmlinux_err="couldn't find your kernel image in /boot, if you used netboot, this is normal" +fi +if [ -z "$vmlinux" -o ! -r "$vmlinux" ]; then + [ -z "$vmlinux_err" ] && vmlinux_err="couldn't extract your kernel from $opt_kernel" +fi + +_info + +# end of header stuff + +# now we define some util functions and the check_*() funcs, as +# the user can choose to execute only some of those + +mount_debugfs() +{ + if [ ! -e /sys/kernel/debug/sched_features ]; then + # try to mount the debugfs hierarchy ourselves and remember it to umount afterwards + mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null && mounted_debugfs=1 + fi +} + +umount_debugfs() +{ + if [ "$mounted_debugfs" = 1 ]; then + # umount debugfs if we did mount it ourselves + umount /sys/kernel/debug + fi +} + +sys_interface_check() +{ + [ "$opt_live" = 1 -a "$opt_no_sysfs" = 0 -a -r "$1" ] || return 1 + _info_nol "* Checking whether we're safe according to the /sys interface: " + if grep -qi '^not affected' "$1"; then + # Not affected + status=OK + pstatus green YES "kernel confirms that your CPU is unaffected" + elif grep -qi '^mitigation' "$1"; then + # Mitigation: PTI + status=OK + pstatus green YES "kernel confirms that the mitigation is active" + elif grep -qi '^vulnerable' "$1"; then + # Vulnerable + status=VULN + pstatus red NO "kernel confirms your system is vulnerable" + else + status=UNK + pstatus yellow UNKNOWN "unknown value reported by kernel" + fi + msg=$(cat "$1") + _debug "sys_interface_check: $1=$msg" + return 0 +} + +################### +# SPECTRE VARIANT 1 +check_variant1() +{ + _info "\033[1;34mCVE-2017-5753 [bounds check bypass] aka 'Spectre Variant 1'\033[0m" + + status=UNK + sys_interface_available=0 + msg='' + if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/spectre_v1"; then + # this kernel has the /sys interface, trust it over everything + sys_interface_available=1 + else + # no /sys interface (or offline mode), fallback to our own ways + _info_nol "* Checking count of LFENCE opcodes in kernel: " + if [ -n "$vmlinux_err" ]; then + msg="couldn't check ($vmlinux_err)" + status=UNK + pstatus yellow UNKNOWN + else + if ! which objdump >/dev/null 2>&1; then + msg="missing 'objdump' tool, please install it, usually it's in the binutils package" + status=UNK + pstatus yellow UNKNOWN + else + # here we disassemble the kernel and count the number of occurences of the LFENCE opcode + # in non-patched kernels, this has been empirically determined as being around 40-50 + # in patched kernels, this is more around 70-80, sometimes way higher (100+) + # v0.13: 68 found in a 3.10.23-xxxx-std-ipv6-64 (with lots of modules compiled-in directly), which doesn't have the LFENCE patches, + # so let's push the threshold to 70. + nb_lfence=$(objdump -d "$vmlinux" | grep -wc lfence) + if [ "$nb_lfence" -lt 70 ]; then + msg="only $nb_lfence opcodes found, should be >= 70, heuristic to be improved when official patches become available" + status=VULN + pstatus red NO + else + msg="$nb_lfence opcodes found, which is >= 70, heuristic to be improved when official patches become available" + status=OK + pstatus green YES + fi + fi + fi + fi + + # if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it + if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 1; then + # override status & msg in case CPU is not vulnerable after all + msg="your CPU vendor reported your CPU model as not vulnerable" + status=OK + fi + + # report status + pvulnstatus CVE-2017-5753 "$status" "$msg" +} + +################### +# SPECTRE VARIANT 2 +check_variant2() +{ + _info "\033[1;34mCVE-2017-5715 [branch target injection] aka 'Spectre Variant 2'\033[0m" + + status=UNK + sys_interface_available=0 + msg='' + if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/spectre_v2"; then + # this kernel has the /sys interface, trust it over everything + sys_interface_available=1 + else + _info "* Mitigation 1" + _info_nol "* Hardware (CPU microcode) support for mitigation: " + if [ ! -e /dev/cpu/0/msr ]; then + # try to load the module ourselves (and remember it so we can rmmod it afterwards) + modprobe msr 2>/dev/null && insmod_msr=1 + _debug "attempted to load module msr, ret=$insmod_msr" + fi + if [ ! -e /dev/cpu/0/msr ]; then + pstatus yellow UNKNOWN "couldn't read /dev/cpu/0/msr, is msr support enabled in your kernel?" + else + # the new MSR 'SPEC_CTRL' is at offset 0x48 + # here we use dd, it's the same as using 'rdmsr 0x48' but without needing the rdmsr tool + # if we get a read error, the MSR is not there + dd if=/dev/cpu/0/msr of=/dev/null bs=8 count=1 skip=9 2>/dev/null + if [ $? -eq 0 ]; then + pstatus green YES + else + pstatus red NO + fi + fi + + if [ "$insmod_msr" = 1 ]; then + # if we used modprobe ourselves, rmmod the module + rmmod msr 2>/dev/null + _debug "attempted to unload module msr, ret=$?" + fi + + _info_nol "* Kernel support for IBRS: " + if [ "$opt_live" = 1 ]; then + mount_debugfs + for ibrs_file in \ + /sys/kernel/debug/ibrs_enabled \ + /sys/kernel/debug/x86/ibrs_enabled \ + /proc/sys/kernel/ibrs_enabled; do + if [ -e "$ibrs_file" ]; then + # if the file is there, we have IBRS compiled-in + # /sys/kernel/debug/ibrs_enabled: vanilla + # /sys/kernel/debug/x86/ibrs_enabled: RedHat (see https://access.redhat.com/articles/3311301) + # /proc/sys/kernel/ibrs_enabled: OpenSUSE tumbleweed + pstatus green YES + ibrs_supported=1 + ibrs_enabled=$(cat "$ibrs_file" 2>/dev/null) + _debug "ibrs: found $ibrs_file=$ibrs_enabled" + break + else + _debug "ibrs: file $ibrs_file doesn't exist" + fi + done + fi + if [ "$ibrs_supported" != 1 -a -n "$opt_map" ]; then + if grep -q spec_ctrl "$opt_map"; then + pstatus green YES + ibrs_supported=1 + _debug "ibrs: found '*spec_ctrl*' symbol in $opt_map" + fi + fi + if [ "$ibrs_supported" != 1 ]; then + pstatus red NO + fi + + _info_nol "* IBRS enabled for Kernel space: " + if [ "$opt_live" = 1 ]; then + # 0 means disabled + # 1 is enabled only for kernel space + # 2 is enabled for kernel and user space + case "$ibrs_enabled" in + "") [ "$ibrs_supported" = 1 ] && pstatus yellow UNKNOWN || pstatus red NO;; + 0) pstatus red NO;; + 1 | 2) pstatus green YES;; + *) pstatus yellow UNKNOWN;; + esac + else + pstatus blue N/A "not testable in offline mode" + fi + + _info_nol "* IBRS enabled for User space: " + if [ "$opt_live" = 1 ]; then + case "$ibrs_enabled" in + "") [ "$ibrs_supported" = 1 ] && pstatus yellow UNKNOWN || pstatus red NO;; + 0 | 1) pstatus red NO;; + 2) pstatus green YES;; + *) pstatus yellow UNKNOWN;; + esac + else + pstatus blue N/A "not testable in offline mode" + fi + + _info "* Mitigation 2" + _info_nol "* Kernel compiled with retpoline option: " + # We check the RETPOLINE kernel options + if [ -r "$opt_config" ]; then + if grep -q '^CONFIG_RETPOLINE=y' "$opt_config"; then + pstatus green YES + retpoline=1 + _debug "retpoline: found "$(grep '^CONFIG_RETPOLINE' "$opt_config")" in $opt_config" + else + pstatus red NO + fi + else + pstatus yellow UNKNOWN "couldn't read your kernel configuration" + fi + + _info_nol "* Kernel compiled with a retpoline-aware compiler: " + # Now check if the compiler used to compile the kernel knows how to insert retpolines in generated asm + # For gcc, this is -mindirect-branch=thunk-extern (detected by the kernel makefiles) + # See gcc commit https://github.com/hjl-tools/gcc/commit/23b517d4a67c02d3ef80b6109218f2aadad7bd79 + # In latest retpoline LKML patches, the noretpoline_setup symbol exists only if CONFIG_RETPOLINE is set + # *AND* if the compiler is retpoline-compliant, so look for that symbol + if [ -n "$opt_map" ]; then + # look for the symbol + if grep -qw noretpoline_setup "$opt_map"; then + retpoline_compiler=1 + pstatus green YES "noretpoline_setup symbol found in System.map" + else + pstatus red NO + fi + elif [ -n "$vmlinux" ]; then + # look for the symbol + if which nm >/dev/null 2>&1; then + # the proper way: use nm and look for the symbol + if nm "$vmlinux" 2>/dev/null | grep -qw 'noretpoline_setup'; then + retpoline_compiler=1 + pstatus green YES "noretpoline_setup found in vmlinux symbols" + else + pstatus red NO + fi + elif grep -q noretpoline_setup "$vmlinux"; then + # if we don't have nm, nevermind, the symbol name is long enough to not have + # any false positive using good old grep directly on the binary + retpoline_compiler=1 + pstatus green YES "noretpoline_setup found in vmlinux" + else + pstatus red NO + fi + else + pstatus yellow UNKNOWN "couldn't find your kernel image or System.map" + fi + fi + + # if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it + if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 2; then + # override status & msg in case CPU is not vulnerable after all + pvulnstatus CVE-2017-5715 OK "your CPU vendor reported your CPU model as not vulnerable" + elif [ -z "$msg" ]; then + # if msg is empty, sysfs check didn't fill it, rely on our own test + if [ "$retpoline" = 1 -a "$retpoline_compiler" = 1 ]; then + pvulnstatus CVE-2017-5715 OK "retpoline mitigate the vulnerability" + elif [ "$opt_live" = 1 ]; then + if [ "$ibrs_enabled" = 1 -o "$ibrs_enabled" = 2 ]; then + pvulnstatus CVE-2017-5715 OK "IBRS mitigates the vulnerability" + else + pvulnstatus CVE-2017-5715 VULN "IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability" + fi + else + if [ "$ibrs_supported" = 1 ]; then + pvulnstatus CVE-2017-5715 OK "offline mode: IBRS will mitigate the vulnerability if enabled at runtime" + else + pvulnstatus CVE-2017-5715 VULN "IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability" + fi + fi + else + pvulnstatus CVE-2017-5715 "$status" "$msg" + fi +} + +######################## +# MELTDOWN aka VARIANT 3 +check_variant3() +{ + _info "\033[1;34mCVE-2017-5754 [rogue data cache load] aka 'Meltdown' aka 'Variant 3'\033[0m" + + status=UNK + sys_interface_available=0 + msg='' + if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/meltdown"; then + # this kernel has the /sys interface, trust it over everything + sys_interface_available=1 + else + _info_nol "* Kernel supports Page Table Isolation (PTI): " + kpti_support=0 + kpti_can_tell=0 + if [ -n "$opt_config" ]; then + kpti_can_tell=1 + if grep -Eq '^(CONFIG_PAGE_TABLE_ISOLATION|CONFIG_KAISER)=y' "$opt_config"; then + _debug "kpti_support: found option "$(grep -E '^(CONFIG_PAGE_TABLE_ISOLATION|CONFIG_KAISER)=y' "$opt_config")" in $opt_config" + kpti_support=1 + fi + fi + if [ "$kpti_support" = 0 -a -n "$opt_map" ]; then + # it's not an elif: some backports don't have the PTI config but still include the patch + # so we try to find an exported symbol that is part of the PTI patch in System.map + kpti_can_tell=1 + if grep -qw kpti_force_enabled "$opt_map"; then + _debug "kpti_support: found kpti_force_enabled in $opt_map" + kpti_support=1 + fi + fi + if [ "$kpti_support" = 0 -a -n "$vmlinux" ]; then + # same as above but in case we don't have System.map and only vmlinux, look for the + # nopti option that is part of the patch (kernel command line option) + kpti_can_tell=1 + if ! which strings >/dev/null 2>&1; then + pstatus yellow UNKNOWN "missing 'strings' tool, please install it, usually it's in the binutils package" + else + if strings "$vmlinux" | grep -qw nopti; then + _debug "kpti_support: found nopti string in $vmlinux" + kpti_support=1 + fi + fi + fi + + if [ "$kpti_support" = 1 ]; then + pstatus green YES + elif [ "$kpti_can_tell" = 1 ]; then + pstatus red NO + else + pstatus yellow UNKNOWN "couldn't read your kernel configuration nor System.map file" + fi + + mount_debugfs + _info_nol "* PTI enabled and active: " + if [ "$opt_live" = 1 ]; then + dmesg_grep="Kernel/User page tables isolation: enabled" + dmesg_grep="$dmesg_grep|Kernel page table isolation enabled" + dmesg_grep="$dmesg_grep|x86/pti: Unmapping kernel while in userspace" + if grep ^flags /proc/cpuinfo | grep -qw pti; then + # vanilla PTI patch sets the 'pti' flag in cpuinfo + _debug "kpti_enabled: found 'pti' flag in /proc/cpuinfo" + kpti_enabled=1 + elif grep ^flags /proc/cpuinfo | grep -qw kaiser; then + # kernel line 4.9 sets the 'kaiser' flag in cpuinfo + _debug "kpti_enabled: found 'kaiser' flag in /proc/cpuinfo" + kpti_enabled=1 + elif [ -e /sys/kernel/debug/x86/pti_enabled ]; then + # RedHat Backport creates a dedicated file, see https://access.redhat.com/articles/3311301 + kpti_enabled=$(cat /sys/kernel/debug/x86/pti_enabled 2>/dev/null) + _debug "kpti_enabled: file /sys/kernel/debug/x86/pti_enabled exists and says: $kpti_enabled" + elif dmesg | grep -Eq "$dmesg_grep"; then + # if we can't find the flag, grep dmesg output + _debug "kpti_enabled: found hint in dmesg: "$(dmesg | grep -E "$dmesg_grep") + kpti_enabled=1 + elif [ -r /var/log/dmesg ] && grep -Eq "$dmesg_grep" /var/log/dmesg; then + # if we can't find the flag in dmesg output, grep in /var/log/dmesg when readable + _debug "kpti_enabled: found hint in /var/log/dmesg: "$(grep -E "$dmesg_grep" /var/log/dmesg) + kpti_enabled=1 + else + _debug "kpti_enabled: couldn't find any hint that PTI is enabled" + kpti_enabled=0 + fi + if [ "$kpti_enabled" = 1 ]; then + pstatus green YES + else + pstatus red NO + fi + else + pstatus blue N/A "can't verify if PTI is enabled in offline mode" + fi + fi + + # if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it + cve='CVE-2017-5754' + if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 3; then + # override status & msg in case CPU is not vulnerable after all + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + elif [ -z "$msg" ]; then + # if msg is empty, sysfs check didn't fill it, rely on our own test + if [ "$opt_live" = 1 ]; then + if [ "$kpti_enabled" = 1 ]; then + pvulnstatus $cve OK "PTI mitigates the vulnerability" + else + pvulnstatus $cve VULN "PTI is needed to mitigate the vulnerability" + fi + else + if [ "$kpti_support" = 1 ]; then + pvulnstatus $cve OK "offline mode: PTI will mitigate the vulnerability if enabled at runtime" + else + pvulnstatus $cve VULN "PTI is needed to mitigate the vulnerability" + fi + fi + else + pvulnstatus $cve "$status" "$msg" + fi +} + +# now run the checks the user asked for +if [ "$opt_variant1" = 1 -o "$opt_allvariants" = 1 ]; then + check_variant1 + _info +fi +if [ "$opt_variant2" = 1 -o "$opt_allvariants" = 1 ]; then + check_variant2 + _info +fi +if [ "$opt_variant3" = 1 -o "$opt_allvariants" = 1 ]; then + check_variant3 + _info +fi + +_info "A false sense of security is worse than no security at all, see --disclaimer" + +# this'll umount only if we mounted debugfs ourselves +umount_debugfs + +# cleanup the temp decompressed config +[ -n "$dumped_config" ] && rm -f "$dumped_config" + +if [ "$opt_batch" = 1 -a "$opt_batch_format" = "nrpe" ]; then + if [ ! -z "$nrpe_vuln" ]; then + echo "Vulnerable:$nrpe_vuln" + else + echo "OK" + fi + [ "$nrpe_critical" = 1 ] && exit 2 # critical + [ "$nrpe_unknown" = 1 ] && exit 3 # unknown + exit 0 # ok +fi + +if [ "$opt_batch" = 1 -a "$opt_batch_format" = "json" ]; then + _echo 0 ${json_output%?}] +fi diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 46bcd06d5..e1f0a51df 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -42,12 +42,8 @@ import apt.progress from moulinette import msettings, msignals, m18n from moulinette.core import MoulinetteError, init_authenticator from moulinette.utils.log import getActionLogger -<<<<<<< b60d8ca822d08c8e3fdf8a17505ff3e285b28164 from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_json, write_to_json -======= -from moulinette.utils.filesystem import read_json, write_to_json, read_file ->>>>>>> [mod] move spectre-meltdown check to diagnosis function from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain from yunohost.dyndns import _dyndns_available, _dyndns_provides @@ -637,13 +633,30 @@ def tools_diagnosis(auth, private=False): diagnosis['private']['regen_conf'] = service_regen_conf(with_diff=True, dry_run=True) diagnosis['security'] = { - # source https://askubuntu.com/questions/992137/how-to-check-that-kpti-is-enabled-on-my-ubuntu - "spectre-meltdown": "cpu_insecure" not in read_file("/proc/cpuinfo") + "CVE-2017-5754": { + "name": "meltdown", + "vulnerable": _check_if_vulnerable_to_meltdown(), + } } return diagnosis +def _check_if_vulnerable_to_meltdown(): + # script taken from https://github.com/speed47/spectre-meltdown-checker + # script commit id is store directly in the script + SCRIPT_PATH = "/usr/share/yunohost/yunohost-config/moulinette/spectre-meltdown-checker.sh" + + # example output from the script: + # [{"NAME":"SPECTRE VARIANT 1","CVE":"CVE-2017-5753","VULNERABLE":true,"INFOS":"only 23 opcodes found, should be >= 70, heuristic to be improved when official patches become available"},{"NAME":"SPECTRE VARIANT 2","CVE":"CVE-2017-5715","VULNERABLE":true,"INFOS":"IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability"},{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}] + for CVE in json.loads(check_output("bash %s --batch json" % SCRIPT_PATH)): + # meltdown https://security-tracker.debian.org/tracker/CVE-2017-5754 + if CVE["CVE"] == "CVE-2017-5754": + return CVE["VULNERABLE"] + + raise Exception("We should never get there") + + def tools_port_available(port): """ Check availability of a local port From 3026035e41281e24fcd40cfa579570b7f9a68ac3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jan 2018 20:32:44 +0100 Subject: [PATCH 076/132] Use --variant 3 to directly check Meltdown only --- src/yunohost/tools.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e1f0a51df..001769108 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -643,18 +643,23 @@ def tools_diagnosis(auth, private=False): def _check_if_vulnerable_to_meltdown(): + # meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754 + # script taken from https://github.com/speed47/spectre-meltdown-checker # script commit id is store directly in the script SCRIPT_PATH = "/usr/share/yunohost/yunohost-config/moulinette/spectre-meltdown-checker.sh" + # '--variant 3' corresponds to Meltdown # example output from the script: - # [{"NAME":"SPECTRE VARIANT 1","CVE":"CVE-2017-5753","VULNERABLE":true,"INFOS":"only 23 opcodes found, should be >= 70, heuristic to be improved when official patches become available"},{"NAME":"SPECTRE VARIANT 2","CVE":"CVE-2017-5715","VULNERABLE":true,"INFOS":"IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability"},{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}] - for CVE in json.loads(check_output("bash %s --batch json" % SCRIPT_PATH)): - # meltdown https://security-tracker.debian.org/tracker/CVE-2017-5754 - if CVE["CVE"] == "CVE-2017-5754": - return CVE["VULNERABLE"] + # [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}] + try: + CVEs = json.loads(check_output("bash %s --batch json --variant 3" % SCRIPT_PATH)) + assert len(CVEs) == 1 + assert CVEs[0]["NAME"] == "MELTDOWN" + except: + raise Exception("Something wrong happened when trying to diagnose Meltdown vunerability.") - raise Exception("We should never get there") + return CVEs[0]["VULNERABLE"] def tools_port_available(port): From de4d4fdd378ca06e0549d4ae180e83ca88eabb4b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jan 2018 20:53:10 +0100 Subject: [PATCH 077/132] Moving spectre/meltdown checker script to vendor folder --- data/other/spectre-meltdown-checker.sh | 980 ------------------------- src/yunohost/tools.py | 2 +- 2 files changed, 1 insertion(+), 981 deletions(-) delete mode 100644 data/other/spectre-meltdown-checker.sh diff --git a/data/other/spectre-meltdown-checker.sh b/data/other/spectre-meltdown-checker.sh deleted file mode 100644 index 5658cf0a8..000000000 --- a/data/other/spectre-meltdown-checker.sh +++ /dev/null @@ -1,980 +0,0 @@ -#! /bin/sh - -# Message from YunoHost team: -# this is this version https://github.com/speed47/spectre-meltdown-checker/blob/dce917bfbb4fa5e135b7ed4c84881086766be802/spectre-meltdown-checker.sh -# that is included here -# You can find the licence (GPLv2))and author name in this script - -# Spectre & Meltdown checker -# -# Check for the latest version at: -# https://github.com/speed47/spectre-meltdown-checker -# git clone https://github.com/speed47/spectre-meltdown-checker.git -# or wget https://raw.githubusercontent.com/speed47/spectre-meltdown-checker/master/spectre-meltdown-checker.sh -# -# Stephane Lesimple - -VERSION=0.28 - -# Script configuration -show_usage() -{ - cat <] [--config ] [--map ] - - Modes: - Two modes are available. - - First mode is the "live" mode (default), it does its best to find information about the currently running kernel. - To run under this mode, just start the script without any option (you can also use --live explicitly) - - Second mode is the "offline" mode, where you can inspect a non-running kernel. - You'll need to specify the location of the vmlinux file, and if possible, the corresponding config and System.map files: - - --kernel vmlinux_file Specify a (possibly compressed) vmlinux file - --config kernel_config Specify a kernel config file - --map kernel_map_file Specify a kernel System.map file - - Options: - --no-color Don't use color codes - --verbose, -v Increase verbosity level - --no-sysfs Don't use the /sys interface even if present - --batch text Produce machine readable output, this is the default if --batch is specified alone - --batch json Produce JSON output formatted for Puppet, Ansible, Chef... - --batch nrpe Produce machine readable output formatted for NRPE - --variant [1,2,3] Specify which variant you'd like to check, by default all variants are checked - Can be specified multiple times (e.g. --variant 2 --variant 3) - - - IMPORTANT: - A false sense of security is worse than no security at all. - Please use the --disclaimer option to understand exactly what this script does. - -EOF -} - -show_disclaimer() -{ - cat <&2 -} - -_info() -{ - _echo 1 "$@" -} - -_info_nol() -{ - _echo_nol 1 "$@" -} - -_verbose() -{ - _echo 2 "$@" -} - -_debug() -{ - _echo 3 "\033[34m(debug) $@\033[0m" -} - -is_cpu_vulnerable() -{ - # param: 1, 2 or 3 (variant) - # returns 1 if vulnerable, 0 if not vulnerable, 255 on error - # by default, everything is vulnerable, we work in a "whitelist" logic here. - # usage: is_cpu_vulnerable 2 && do something if vulnerable - variant1=0 - variant2=0 - variant3=0 - if grep -q AMD /proc/cpuinfo; then - variant1=0 - variant2=1 - variant3=1 - elif grep -qi 'CPU implementer\s*:\s*0x41' /proc/cpuinfo; then - # ARM - # reference: https://developer.arm.com/support/security-update - cpupart=$(awk '/CPU part/ {print $4;exit}' /proc/cpuinfo) - cpuarch=$(awk '/CPU architecture/ {print $3;exit}' /proc/cpuinfo) - if [ -n "$cpupart" -a -n "$cpuarch" ]; then - # Cortex-R7 and Cortex-R8 are real-time and only used in medical devices or such - # I can't find their CPU part number, but it's probably not that useful anyway - # model R7 R8 A9 A15 A17 A57 A72 A73 A75 - # part ? ? 0xc09 0xc0f 0xc0e 0xd07 0xd08 0xd09 0xd0a - # arch 7? 7? 7 7 7 8 8 8 8 - if [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -Eq '^0x(c09|c0f|c0e)$'; then - # armv7 vulnerable chips - variant1=0 - variant2=0 - elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -Eq '^0x(d07|d08|d09|d0a)$'; then - # armv8 vulnerable chips - variant1=0 - variant2=0 - else - variant1=1 - variant2=1 - fi - # for variant3, only A75 is vulnerable - if [ "$cpuarch" = 8 -a "$cpupart" = 0xd0a ]; then - variant3=0 - else - variant3=1 - fi - fi - fi - [ "$1" = 1 ] && return $variant1 - [ "$1" = 2 ] && return $variant2 - [ "$1" = 3 ] && return $variant3 - return 255 -} - -show_header() -{ - _info "\033[1;34mSpectre and Meltdown mitigation detection tool v$VERSION\033[0m" - _info -} - -parse_opt_file() -{ - # parse_opt_file option_name option_value - option_name="$1" - option_value="$2" - if [ -z "$option_value" ]; then - show_header - show_usage - echo "$0: error: --$option_name expects one parameter (a file)" >&2 - exit 1 - elif [ ! -e "$option_value" ]; then - show_header - echo "$0: error: couldn't find file $option_value" >&2 - exit 1 - elif [ ! -f "$option_value" ]; then - show_header - echo "$0: error: $option_value is not a file" >&2 - exit 1 - elif [ ! -r "$option_value" ]; then - show_header - echo "$0: error: couldn't read $option_value (are you root?)" >&2 - exit 1 - fi - echo "$option_value" - exit 0 -} - -while [ -n "$1" ]; do - if [ "$1" = "--kernel" ]; then - opt_kernel=$(parse_opt_file kernel "$2") - [ $? -ne 0 ] && exit $? - shift 2 - opt_live=0 - elif [ "$1" = "--config" ]; then - opt_config=$(parse_opt_file config "$2") - [ $? -ne 0 ] && exit $? - shift 2 - opt_live=0 - elif [ "$1" = "--map" ]; then - opt_map=$(parse_opt_file map "$2") - [ $? -ne 0 ] && exit $? - shift 2 - opt_live=0 - elif [ "$1" = "--live" ]; then - opt_live_explicit=1 - shift - elif [ "$1" = "--no-color" ]; then - opt_no_color=1 - shift - elif [ "$1" = "--no-sysfs" ]; then - opt_no_sysfs=1 - shift - elif [ "$1" = "--batch" ]; then - opt_batch=1 - opt_verbose=0 - shift - case "$1" in - text|nrpe|json) opt_batch_format="$1"; shift;; - --*) ;; # allow subsequent flags - '') ;; # allow nothing at all - *) - echo "$0: error: unknown batch format '$1'" - echo "$0: error: --batch expects a format from: text, nrpe, json" - exit 1 >&2 - ;; - esac - elif [ "$1" = "-v" -o "$1" = "--verbose" ]; then - opt_verbose=$(expr $opt_verbose + 1) - shift - elif [ "$1" = "--variant" ]; then - if [ -z "$2" ]; then - echo "$0: error: option --variant expects a parameter (1, 2 or 3)" >&2 - exit 1 - fi - case "$2" in - 1) opt_variant1=1; opt_allvariants=0;; - 2) opt_variant2=1; opt_allvariants=0;; - 3) opt_variant3=1; opt_allvariants=0;; - *) - echo "$0: error: invalid parameter '$2' for --variant, expected either 1, 2 or 3" >&2; - exit 1;; - esac - shift 2 - elif [ "$1" = "-h" -o "$1" = "--help" ]; then - show_header - show_usage - exit 0 - elif [ "$1" = "--version" ]; then - opt_no_color=1 - show_header - exit 1 - elif [ "$1" = "--disclaimer" ]; then - show_header - show_disclaimer - exit 0 - else - show_header - show_usage - echo "$0: error: unknown option '$1'" - exit 1 - fi -done - -show_header - -# print status function -pstatus() -{ - if [ "$opt_no_color" = 1 ]; then - _info_nol "$2" - else - case "$1" in - red) col="\033[101m\033[30m";; - green) col="\033[102m\033[30m";; - yellow) col="\033[103m\033[30m";; - blue) col="\033[104m\033[30m";; - *) col="";; - esac - _info_nol "$col $2 \033[0m" - fi - [ -n "$3" ] && _info_nol " ($3)" - _info -} - -# Print the final status of a vulnerability (incl. batch mode) -# Arguments are: CVE UNK/OK/VULN description -pvulnstatus() -{ - if [ "$opt_batch" = 1 ]; then - case "$opt_batch_format" in - text) _echo 0 "$1: $2 ($3)";; - nrpe) - case "$2" in - UKN) nrpe_unknown="1";; - VULN) nrpe_critical="1"; nrpe_vuln="$nrpe_vuln $1";; - esac - ;; - json) - case "$1" in - CVE-2017-5753) aka="SPECTRE VARIANT 1";; - CVE-2017-5715) aka="SPECTRE VARIANT 2";; - CVE-2017-5754) aka="MELTDOWN";; - esac - case "$2" in - UKN) is_vuln="unknown";; - VULN) is_vuln="true";; - OK) is_vuln="false";; - esac - json_output="${json_output:-[}{\"NAME\":\""$aka"\",\"CVE\":\""$1"\",\"VULNERABLE\":$is_vuln,\"INFOS\":\""$3"\"}," - ;; - esac - fi - - _info_nol "> \033[46m\033[30mSTATUS:\033[0m " - vulnstatus="$2" - shift 2 - case "$vulnstatus" in - UNK) pstatus yellow UNKNOWN "$@";; - VULN) pstatus red 'VULNERABLE' "$@";; - OK) pstatus green 'NOT VULNERABLE' "$@";; - esac -} - - -# The 3 below functions are taken from the extract-linux script, available here: -# https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux -# The functions have been modified for better integration to this script -# The original header of the file has been retained below - -# ---------------------------------------------------------------------- -# extract-vmlinux - Extract uncompressed vmlinux from a kernel image -# -# Inspired from extract-ikconfig -# (c) 2009,2010 Dick Streefland -# -# (c) 2011 Corentin Chary -# -# Licensed under the GNU General Public License, version 2 (GPLv2). -# ---------------------------------------------------------------------- - -vmlinux='' -vmlinux_err='' -check_vmlinux() -{ - readelf -h "$1" > /dev/null 2>&1 || return 1 - return 0 -} - -try_decompress() -{ - # The obscure use of the "tr" filter is to work around older versions of - # "grep" that report the byte offset of the line instead of the pattern. - - # Try to find the header ($1) and decompress from here - for pos in `tr "$1\n$2" "\n$2=" < "$6" | grep -abo "^$2"` - do - _debug "try_decompress: magic for $3 found at offset $pos" - if ! which "$3" >/dev/null 2>&1; then - vmlinux_err="missing '$3' tool, please install it, usually it's in the '$5' package" - return 0 - fi - pos=${pos%%:*} - tail -c+$pos "$6" 2>/dev/null | $3 $4 > $vmlinuxtmp 2>/dev/null - if check_vmlinux "$vmlinuxtmp"; then - vmlinux="$vmlinuxtmp" - _debug "try_decompress: decompressed with $3 successfully!" - return 0 - else - _debug "try_decompress: decompression with $3 did not work" - fi - done - return 1 -} - -extract_vmlinux() -{ - [ -n "$1" ] || return 1 - # Prepare temp files: - vmlinuxtmp="$(mktemp /tmp/vmlinux-XXXXXX)" - trap "rm -f $vmlinuxtmp" EXIT - - # Initial attempt for uncompressed images or objects: - if check_vmlinux "$1"; then - cat "$1" > "$vmlinuxtmp" - vmlinux=$vmlinuxtmp - return 0 - fi - - # That didn't work, so retry after decompression. - try_decompress '\037\213\010' xy gunzip '' gunzip "$1" && return 0 - try_decompress '\3757zXZ\000' abcde unxz '' xz-utils "$1" && return 0 - try_decompress 'BZh' xy bunzip2 '' bzip2 "$1" && return 0 - try_decompress '\135\0\0\0' xxx unlzma '' xz-utils "$1" && return 0 - try_decompress '\211\114\132' xy 'lzop' '-d' lzop "$1" && return 0 - try_decompress '\002\041\114\030' xyy 'lz4' '-d -l' liblz4-tool "$1" && return 0 - return 1 -} - -# end of extract-vmlinux functions - -# check for mode selection inconsistency -if [ "$opt_live_explicit" = 1 ]; then - if [ -n "$opt_kernel" -o -n "$opt_config" -o -n "$opt_map" ]; then - show_usage - echo "$0: error: incompatible modes specified, use either --live or --kernel/--config/--map" - exit 1 - fi -fi - -# root check (only for live mode, for offline mode, we already checked if we could read the files) - -if [ "$opt_live" = 1 ]; then - if [ "$(id -u)" -ne 0 ]; then - _warn "Note that you should launch this script with root privileges to get accurate information." - _warn "We'll proceed but you might see permission denied errors." - _warn "To run it as root, you can try the following command: sudo $0" - _warn - fi - _info "Checking for vulnerabilities against running kernel \033[35m"$(uname -s) $(uname -r) $(uname -v) $(uname -m)"\033[0m" - _info "CPU is\033[35m"$(grep '^model name' /proc/cpuinfo | cut -d: -f2 | head -1)"\033[0m" - - # try to find the image of the current running kernel - # first, look for the BOOT_IMAGE hint in the kernel cmdline - if [ -r /proc/cmdline ] && grep -q 'BOOT_IMAGE=' /proc/cmdline; then - opt_kernel=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2) - _debug "found opt_kernel=$opt_kernel in /proc/cmdline" - # if we have a dedicated /boot partition, our bootloader might have just called it / - # so try to prepend /boot and see if we find anything - [ -e "/boot/$opt_kernel" ] && opt_kernel="/boot/$opt_kernel" - _debug "opt_kernel is now $opt_kernel" - # else, the full path is already there (most probably /boot/something) - fi - # if we didn't find a kernel, default to guessing - if [ ! -e "$opt_kernel" ]; then - [ -e /boot/vmlinuz-linux ] && opt_kernel=/boot/vmlinuz-linux - [ -e /boot/vmlinuz-linux-libre ] && opt_kernel=/boot/vmlinuz-linux-libre - [ -e /boot/vmlinuz-$(uname -r) ] && opt_kernel=/boot/vmlinuz-$(uname -r) - [ -e /boot/kernel-$( uname -r) ] && opt_kernel=/boot/kernel-$( uname -r) - [ -e /boot/bzImage-$(uname -r) ] && opt_kernel=/boot/bzImage-$(uname -r) - [ -e /boot/kernel-genkernel-$(uname -m)-$(uname -r) ] && opt_kernel=/boot/kernel-genkernel-$(uname -m)-$(uname -r) - fi - - # system.map - if [ -e /proc/kallsyms ] ; then - opt_map="/proc/kallsyms" - elif [ -e /boot/System.map-$(uname -r) ] ; then - opt_map=/boot/System.map-$(uname -r) - fi - - # config - if [ -e /proc/config.gz ] ; then - dumped_config="$(mktemp /tmp/config-XXXXXX)" - gunzip -c /proc/config.gz > $dumped_config - # dumped_config will be deleted at the end of the script - opt_config=$dumped_config - elif [ -e /boot/config-$(uname -r) ]; then - opt_config=/boot/config-$(uname -r) - fi -else - _info "Checking for vulnerabilities against specified kernel" -fi -if [ -n "$opt_kernel" ]; then - _verbose "Will use vmlinux image \033[35m$opt_kernel\033[0m" -else - _verbose "Will use no vmlinux image (accuracy might be reduced)" -fi -if [ -n "$dumped_config" ]; then - _verbose "Will use kconfig \033[35m/proc/config.gz\033[0m" -elif [ -n "$opt_config" ]; then - _verbose "Will use kconfig \033[35m$opt_config\033[0m" -else - _verbose "Will use no kconfig (accuracy might be reduced)" -fi -if [ -n "$opt_map" ]; then - _verbose "Will use System.map file \033[35m$opt_map\033[0m" -else - _verbose "Will use no System.map file (accuracy might be reduced)" -fi - -if [ -e "$opt_kernel" ]; then - if ! which readelf >/dev/null 2>&1; then - vmlinux_err="missing 'readelf' tool, please install it, usually it's in the 'binutils' package" - else - extract_vmlinux "$opt_kernel" - fi -else - vmlinux_err="couldn't find your kernel image in /boot, if you used netboot, this is normal" -fi -if [ -z "$vmlinux" -o ! -r "$vmlinux" ]; then - [ -z "$vmlinux_err" ] && vmlinux_err="couldn't extract your kernel from $opt_kernel" -fi - -_info - -# end of header stuff - -# now we define some util functions and the check_*() funcs, as -# the user can choose to execute only some of those - -mount_debugfs() -{ - if [ ! -e /sys/kernel/debug/sched_features ]; then - # try to mount the debugfs hierarchy ourselves and remember it to umount afterwards - mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null && mounted_debugfs=1 - fi -} - -umount_debugfs() -{ - if [ "$mounted_debugfs" = 1 ]; then - # umount debugfs if we did mount it ourselves - umount /sys/kernel/debug - fi -} - -sys_interface_check() -{ - [ "$opt_live" = 1 -a "$opt_no_sysfs" = 0 -a -r "$1" ] || return 1 - _info_nol "* Checking whether we're safe according to the /sys interface: " - if grep -qi '^not affected' "$1"; then - # Not affected - status=OK - pstatus green YES "kernel confirms that your CPU is unaffected" - elif grep -qi '^mitigation' "$1"; then - # Mitigation: PTI - status=OK - pstatus green YES "kernel confirms that the mitigation is active" - elif grep -qi '^vulnerable' "$1"; then - # Vulnerable - status=VULN - pstatus red NO "kernel confirms your system is vulnerable" - else - status=UNK - pstatus yellow UNKNOWN "unknown value reported by kernel" - fi - msg=$(cat "$1") - _debug "sys_interface_check: $1=$msg" - return 0 -} - -################### -# SPECTRE VARIANT 1 -check_variant1() -{ - _info "\033[1;34mCVE-2017-5753 [bounds check bypass] aka 'Spectre Variant 1'\033[0m" - - status=UNK - sys_interface_available=0 - msg='' - if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/spectre_v1"; then - # this kernel has the /sys interface, trust it over everything - sys_interface_available=1 - else - # no /sys interface (or offline mode), fallback to our own ways - _info_nol "* Checking count of LFENCE opcodes in kernel: " - if [ -n "$vmlinux_err" ]; then - msg="couldn't check ($vmlinux_err)" - status=UNK - pstatus yellow UNKNOWN - else - if ! which objdump >/dev/null 2>&1; then - msg="missing 'objdump' tool, please install it, usually it's in the binutils package" - status=UNK - pstatus yellow UNKNOWN - else - # here we disassemble the kernel and count the number of occurences of the LFENCE opcode - # in non-patched kernels, this has been empirically determined as being around 40-50 - # in patched kernels, this is more around 70-80, sometimes way higher (100+) - # v0.13: 68 found in a 3.10.23-xxxx-std-ipv6-64 (with lots of modules compiled-in directly), which doesn't have the LFENCE patches, - # so let's push the threshold to 70. - nb_lfence=$(objdump -d "$vmlinux" | grep -wc lfence) - if [ "$nb_lfence" -lt 70 ]; then - msg="only $nb_lfence opcodes found, should be >= 70, heuristic to be improved when official patches become available" - status=VULN - pstatus red NO - else - msg="$nb_lfence opcodes found, which is >= 70, heuristic to be improved when official patches become available" - status=OK - pstatus green YES - fi - fi - fi - fi - - # if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it - if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 1; then - # override status & msg in case CPU is not vulnerable after all - msg="your CPU vendor reported your CPU model as not vulnerable" - status=OK - fi - - # report status - pvulnstatus CVE-2017-5753 "$status" "$msg" -} - -################### -# SPECTRE VARIANT 2 -check_variant2() -{ - _info "\033[1;34mCVE-2017-5715 [branch target injection] aka 'Spectre Variant 2'\033[0m" - - status=UNK - sys_interface_available=0 - msg='' - if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/spectre_v2"; then - # this kernel has the /sys interface, trust it over everything - sys_interface_available=1 - else - _info "* Mitigation 1" - _info_nol "* Hardware (CPU microcode) support for mitigation: " - if [ ! -e /dev/cpu/0/msr ]; then - # try to load the module ourselves (and remember it so we can rmmod it afterwards) - modprobe msr 2>/dev/null && insmod_msr=1 - _debug "attempted to load module msr, ret=$insmod_msr" - fi - if [ ! -e /dev/cpu/0/msr ]; then - pstatus yellow UNKNOWN "couldn't read /dev/cpu/0/msr, is msr support enabled in your kernel?" - else - # the new MSR 'SPEC_CTRL' is at offset 0x48 - # here we use dd, it's the same as using 'rdmsr 0x48' but without needing the rdmsr tool - # if we get a read error, the MSR is not there - dd if=/dev/cpu/0/msr of=/dev/null bs=8 count=1 skip=9 2>/dev/null - if [ $? -eq 0 ]; then - pstatus green YES - else - pstatus red NO - fi - fi - - if [ "$insmod_msr" = 1 ]; then - # if we used modprobe ourselves, rmmod the module - rmmod msr 2>/dev/null - _debug "attempted to unload module msr, ret=$?" - fi - - _info_nol "* Kernel support for IBRS: " - if [ "$opt_live" = 1 ]; then - mount_debugfs - for ibrs_file in \ - /sys/kernel/debug/ibrs_enabled \ - /sys/kernel/debug/x86/ibrs_enabled \ - /proc/sys/kernel/ibrs_enabled; do - if [ -e "$ibrs_file" ]; then - # if the file is there, we have IBRS compiled-in - # /sys/kernel/debug/ibrs_enabled: vanilla - # /sys/kernel/debug/x86/ibrs_enabled: RedHat (see https://access.redhat.com/articles/3311301) - # /proc/sys/kernel/ibrs_enabled: OpenSUSE tumbleweed - pstatus green YES - ibrs_supported=1 - ibrs_enabled=$(cat "$ibrs_file" 2>/dev/null) - _debug "ibrs: found $ibrs_file=$ibrs_enabled" - break - else - _debug "ibrs: file $ibrs_file doesn't exist" - fi - done - fi - if [ "$ibrs_supported" != 1 -a -n "$opt_map" ]; then - if grep -q spec_ctrl "$opt_map"; then - pstatus green YES - ibrs_supported=1 - _debug "ibrs: found '*spec_ctrl*' symbol in $opt_map" - fi - fi - if [ "$ibrs_supported" != 1 ]; then - pstatus red NO - fi - - _info_nol "* IBRS enabled for Kernel space: " - if [ "$opt_live" = 1 ]; then - # 0 means disabled - # 1 is enabled only for kernel space - # 2 is enabled for kernel and user space - case "$ibrs_enabled" in - "") [ "$ibrs_supported" = 1 ] && pstatus yellow UNKNOWN || pstatus red NO;; - 0) pstatus red NO;; - 1 | 2) pstatus green YES;; - *) pstatus yellow UNKNOWN;; - esac - else - pstatus blue N/A "not testable in offline mode" - fi - - _info_nol "* IBRS enabled for User space: " - if [ "$opt_live" = 1 ]; then - case "$ibrs_enabled" in - "") [ "$ibrs_supported" = 1 ] && pstatus yellow UNKNOWN || pstatus red NO;; - 0 | 1) pstatus red NO;; - 2) pstatus green YES;; - *) pstatus yellow UNKNOWN;; - esac - else - pstatus blue N/A "not testable in offline mode" - fi - - _info "* Mitigation 2" - _info_nol "* Kernel compiled with retpoline option: " - # We check the RETPOLINE kernel options - if [ -r "$opt_config" ]; then - if grep -q '^CONFIG_RETPOLINE=y' "$opt_config"; then - pstatus green YES - retpoline=1 - _debug "retpoline: found "$(grep '^CONFIG_RETPOLINE' "$opt_config")" in $opt_config" - else - pstatus red NO - fi - else - pstatus yellow UNKNOWN "couldn't read your kernel configuration" - fi - - _info_nol "* Kernel compiled with a retpoline-aware compiler: " - # Now check if the compiler used to compile the kernel knows how to insert retpolines in generated asm - # For gcc, this is -mindirect-branch=thunk-extern (detected by the kernel makefiles) - # See gcc commit https://github.com/hjl-tools/gcc/commit/23b517d4a67c02d3ef80b6109218f2aadad7bd79 - # In latest retpoline LKML patches, the noretpoline_setup symbol exists only if CONFIG_RETPOLINE is set - # *AND* if the compiler is retpoline-compliant, so look for that symbol - if [ -n "$opt_map" ]; then - # look for the symbol - if grep -qw noretpoline_setup "$opt_map"; then - retpoline_compiler=1 - pstatus green YES "noretpoline_setup symbol found in System.map" - else - pstatus red NO - fi - elif [ -n "$vmlinux" ]; then - # look for the symbol - if which nm >/dev/null 2>&1; then - # the proper way: use nm and look for the symbol - if nm "$vmlinux" 2>/dev/null | grep -qw 'noretpoline_setup'; then - retpoline_compiler=1 - pstatus green YES "noretpoline_setup found in vmlinux symbols" - else - pstatus red NO - fi - elif grep -q noretpoline_setup "$vmlinux"; then - # if we don't have nm, nevermind, the symbol name is long enough to not have - # any false positive using good old grep directly on the binary - retpoline_compiler=1 - pstatus green YES "noretpoline_setup found in vmlinux" - else - pstatus red NO - fi - else - pstatus yellow UNKNOWN "couldn't find your kernel image or System.map" - fi - fi - - # if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it - if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 2; then - # override status & msg in case CPU is not vulnerable after all - pvulnstatus CVE-2017-5715 OK "your CPU vendor reported your CPU model as not vulnerable" - elif [ -z "$msg" ]; then - # if msg is empty, sysfs check didn't fill it, rely on our own test - if [ "$retpoline" = 1 -a "$retpoline_compiler" = 1 ]; then - pvulnstatus CVE-2017-5715 OK "retpoline mitigate the vulnerability" - elif [ "$opt_live" = 1 ]; then - if [ "$ibrs_enabled" = 1 -o "$ibrs_enabled" = 2 ]; then - pvulnstatus CVE-2017-5715 OK "IBRS mitigates the vulnerability" - else - pvulnstatus CVE-2017-5715 VULN "IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability" - fi - else - if [ "$ibrs_supported" = 1 ]; then - pvulnstatus CVE-2017-5715 OK "offline mode: IBRS will mitigate the vulnerability if enabled at runtime" - else - pvulnstatus CVE-2017-5715 VULN "IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability" - fi - fi - else - pvulnstatus CVE-2017-5715 "$status" "$msg" - fi -} - -######################## -# MELTDOWN aka VARIANT 3 -check_variant3() -{ - _info "\033[1;34mCVE-2017-5754 [rogue data cache load] aka 'Meltdown' aka 'Variant 3'\033[0m" - - status=UNK - sys_interface_available=0 - msg='' - if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/meltdown"; then - # this kernel has the /sys interface, trust it over everything - sys_interface_available=1 - else - _info_nol "* Kernel supports Page Table Isolation (PTI): " - kpti_support=0 - kpti_can_tell=0 - if [ -n "$opt_config" ]; then - kpti_can_tell=1 - if grep -Eq '^(CONFIG_PAGE_TABLE_ISOLATION|CONFIG_KAISER)=y' "$opt_config"; then - _debug "kpti_support: found option "$(grep -E '^(CONFIG_PAGE_TABLE_ISOLATION|CONFIG_KAISER)=y' "$opt_config")" in $opt_config" - kpti_support=1 - fi - fi - if [ "$kpti_support" = 0 -a -n "$opt_map" ]; then - # it's not an elif: some backports don't have the PTI config but still include the patch - # so we try to find an exported symbol that is part of the PTI patch in System.map - kpti_can_tell=1 - if grep -qw kpti_force_enabled "$opt_map"; then - _debug "kpti_support: found kpti_force_enabled in $opt_map" - kpti_support=1 - fi - fi - if [ "$kpti_support" = 0 -a -n "$vmlinux" ]; then - # same as above but in case we don't have System.map and only vmlinux, look for the - # nopti option that is part of the patch (kernel command line option) - kpti_can_tell=1 - if ! which strings >/dev/null 2>&1; then - pstatus yellow UNKNOWN "missing 'strings' tool, please install it, usually it's in the binutils package" - else - if strings "$vmlinux" | grep -qw nopti; then - _debug "kpti_support: found nopti string in $vmlinux" - kpti_support=1 - fi - fi - fi - - if [ "$kpti_support" = 1 ]; then - pstatus green YES - elif [ "$kpti_can_tell" = 1 ]; then - pstatus red NO - else - pstatus yellow UNKNOWN "couldn't read your kernel configuration nor System.map file" - fi - - mount_debugfs - _info_nol "* PTI enabled and active: " - if [ "$opt_live" = 1 ]; then - dmesg_grep="Kernel/User page tables isolation: enabled" - dmesg_grep="$dmesg_grep|Kernel page table isolation enabled" - dmesg_grep="$dmesg_grep|x86/pti: Unmapping kernel while in userspace" - if grep ^flags /proc/cpuinfo | grep -qw pti; then - # vanilla PTI patch sets the 'pti' flag in cpuinfo - _debug "kpti_enabled: found 'pti' flag in /proc/cpuinfo" - kpti_enabled=1 - elif grep ^flags /proc/cpuinfo | grep -qw kaiser; then - # kernel line 4.9 sets the 'kaiser' flag in cpuinfo - _debug "kpti_enabled: found 'kaiser' flag in /proc/cpuinfo" - kpti_enabled=1 - elif [ -e /sys/kernel/debug/x86/pti_enabled ]; then - # RedHat Backport creates a dedicated file, see https://access.redhat.com/articles/3311301 - kpti_enabled=$(cat /sys/kernel/debug/x86/pti_enabled 2>/dev/null) - _debug "kpti_enabled: file /sys/kernel/debug/x86/pti_enabled exists and says: $kpti_enabled" - elif dmesg | grep -Eq "$dmesg_grep"; then - # if we can't find the flag, grep dmesg output - _debug "kpti_enabled: found hint in dmesg: "$(dmesg | grep -E "$dmesg_grep") - kpti_enabled=1 - elif [ -r /var/log/dmesg ] && grep -Eq "$dmesg_grep" /var/log/dmesg; then - # if we can't find the flag in dmesg output, grep in /var/log/dmesg when readable - _debug "kpti_enabled: found hint in /var/log/dmesg: "$(grep -E "$dmesg_grep" /var/log/dmesg) - kpti_enabled=1 - else - _debug "kpti_enabled: couldn't find any hint that PTI is enabled" - kpti_enabled=0 - fi - if [ "$kpti_enabled" = 1 ]; then - pstatus green YES - else - pstatus red NO - fi - else - pstatus blue N/A "can't verify if PTI is enabled in offline mode" - fi - fi - - # if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it - cve='CVE-2017-5754' - if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 3; then - # override status & msg in case CPU is not vulnerable after all - pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" - elif [ -z "$msg" ]; then - # if msg is empty, sysfs check didn't fill it, rely on our own test - if [ "$opt_live" = 1 ]; then - if [ "$kpti_enabled" = 1 ]; then - pvulnstatus $cve OK "PTI mitigates the vulnerability" - else - pvulnstatus $cve VULN "PTI is needed to mitigate the vulnerability" - fi - else - if [ "$kpti_support" = 1 ]; then - pvulnstatus $cve OK "offline mode: PTI will mitigate the vulnerability if enabled at runtime" - else - pvulnstatus $cve VULN "PTI is needed to mitigate the vulnerability" - fi - fi - else - pvulnstatus $cve "$status" "$msg" - fi -} - -# now run the checks the user asked for -if [ "$opt_variant1" = 1 -o "$opt_allvariants" = 1 ]; then - check_variant1 - _info -fi -if [ "$opt_variant2" = 1 -o "$opt_allvariants" = 1 ]; then - check_variant2 - _info -fi -if [ "$opt_variant3" = 1 -o "$opt_allvariants" = 1 ]; then - check_variant3 - _info -fi - -_info "A false sense of security is worse than no security at all, see --disclaimer" - -# this'll umount only if we mounted debugfs ourselves -umount_debugfs - -# cleanup the temp decompressed config -[ -n "$dumped_config" ] && rm -f "$dumped_config" - -if [ "$opt_batch" = 1 -a "$opt_batch_format" = "nrpe" ]; then - if [ ! -z "$nrpe_vuln" ]; then - echo "Vulnerable:$nrpe_vuln" - else - echo "OK" - fi - [ "$nrpe_critical" = 1 ] && exit 2 # critical - [ "$nrpe_unknown" = 1 ] && exit 3 # unknown - exit 0 # ok -fi - -if [ "$opt_batch" = 1 -a "$opt_batch_format" = "json" ]; then - _echo 0 ${json_output%?}] -fi diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 001769108..8b6e5cc44 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -647,7 +647,7 @@ def _check_if_vulnerable_to_meltdown(): # script taken from https://github.com/speed47/spectre-meltdown-checker # script commit id is store directly in the script - SCRIPT_PATH = "/usr/share/yunohost/yunohost-config/moulinette/spectre-meltdown-checker.sh" + SCRIPT_PATH = "./vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh" # '--variant 3' corresponds to Meltdown # example output from the script: From 80cfa3a786173976cf61a4a0202291f0c08fc8fc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jan 2018 21:22:22 +0100 Subject: [PATCH 078/132] Squashed 'src/yunohost/vendor/spectre-meltdown-checker/' content from commit 7f92717 git-subtree-dir: src/yunohost/vendor/spectre-meltdown-checker git-subtree-split: 7f92717a2c720a55785f8814a872eed7d380fdcf --- LICENSE | 674 +++++++++++++++++++++++++ README.md | 45 ++ spectre-meltdown-checker.sh | 982 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1701 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100755 spectre-meltdown-checker.sh diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 000000000..518b3ec9b --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +Spectre & Meltdown Checker +========================== + +A simple shell script to tell if your Linux installation is vulnerable against the 3 "speculative execution" CVEs that were made public early 2018. + +Without options, it'll inspect your currently running kernel. +You can also specify a kernel image on the command line, if you'd like to inspect a kernel you're not running. + +The script will do its best to detect mitigations, including backported non-vanilla patches, regardless of the advertised kernel version number. + +## Example of script output + +![checker](https://framapic.org/6O4v4AAwMenv/M6J4CFWwsB3z.png) + +## Quick summary of the CVEs + +**CVE-2017-5753** bounds check bypass (Spectre Variant 1) + + - Impact: Kernel & all software + - Mitigation: recompile software *and* kernel with a modified compiler that introduces the LFENCE opcode at the proper positions in the resulting code + - Performance impact of the mitigation: negligible + +**CVE-2017-5715** branch target injection (Spectre Variant 2) + + - Impact: Kernel + - Mitigation 1: new opcode via microcode update that should be used by up to date compilers to protect the BTB (by flushing indirect branch predictors) + - Mitigation 2: introducing "retpoline" into compilers, and recompile software/OS with it + - Performance impact of the mitigation: high for mitigation 1, medium for mitigation 2, depending on your CPU + +**CVE-2017-5754** rogue data cache load (Meltdown) + + - Impact: Kernel + - Mitigation: updated kernel (with PTI/KPTI patches), updating the kernel is enough + - Performance impact of the mitigation: low to medium + +## Disclaimer + +This tool does its best to determine whether your system is immune (or has proper mitigations in place) for the collectively named "speculative execution" vulnerabilities. It doesn't attempt to run any kind of exploit, and can't guarantee that your system is secure, but rather helps you verifying whether your system has the known correct mitigations in place. +However, some mitigations could also exist in your kernel that this script doesn't know (yet) how to detect, or it might falsely detect mitigations that in the end don't work as expected (for example, on backported or modified kernels). + +Your system exposure also depends on your CPU. As of now, AMD and ARM processors are marked as immune to some or all of these vulnerabilities (except some specific ARM models). All Intel processors manufactured since circa 1995 are thought to be vulnerable. Whatever processor one uses, one might seek more information from the manufacturer of that processor and/or of the device in which it runs. + +The nature of the discovered vulnerabilities being quite new, the landscape of vulnerable processors can be expected to change over time, which is why this script makes the assumption that all CPUs are vulnerable, except if the manufacturer explicitly stated otherwise in a verifiable public announcement. + +This tool has been released in the hope that it'll be useful, but don't use it to jump to conclusions about your security. diff --git a/spectre-meltdown-checker.sh b/spectre-meltdown-checker.sh new file mode 100755 index 000000000..f71deb5bf --- /dev/null +++ b/spectre-meltdown-checker.sh @@ -0,0 +1,982 @@ +#! /bin/sh +# Spectre & Meltdown checker +# +# Check for the latest version at: +# https://github.com/speed47/spectre-meltdown-checker +# git clone https://github.com/speed47/spectre-meltdown-checker.git +# or wget https://raw.githubusercontent.com/speed47/spectre-meltdown-checker/master/spectre-meltdown-checker.sh +# +# Stephane Lesimple +# +VERSION=0.29 + +# Script configuration +show_usage() +{ + cat <] [--config ] [--map ] + + Modes: + Two modes are available. + + First mode is the "live" mode (default), it does its best to find information about the currently running kernel. + To run under this mode, just start the script without any option (you can also use --live explicitly) + + Second mode is the "offline" mode, where you can inspect a non-running kernel. + You'll need to specify the location of the vmlinux file, and if possible, the corresponding config and System.map files: + + --kernel vmlinux_file Specify a (possibly compressed) vmlinux file + --config kernel_config Specify a kernel config file + --map kernel_map_file Specify a kernel System.map file + + Options: + --no-color Don't use color codes + --verbose, -v Increase verbosity level + --no-sysfs Don't use the /sys interface even if present + --batch text Produce machine readable output, this is the default if --batch is specified alone + --batch json Produce JSON output formatted for Puppet, Ansible, Chef... + --batch nrpe Produce machine readable output formatted for NRPE + --variant [1,2,3] Specify which variant you'd like to check, by default all variants are checked + Can be specified multiple times (e.g. --variant 2 --variant 3) + + + IMPORTANT: + A false sense of security is worse than no security at all. + Please use the --disclaimer option to understand exactly what this script does. + +EOF +} + +show_disclaimer() +{ + cat <&2 +} + +_info() +{ + _echo 1 "$@" +} + +_info_nol() +{ + _echo_nol 1 "$@" +} + +_verbose() +{ + _echo 2 "$@" +} + +_debug() +{ + _echo 3 "\033[34m(debug) $@\033[0m" +} + +is_cpu_vulnerable() +{ + # param: 1, 2 or 3 (variant) + # returns 0 if vulnerable, 1 if not vulnerable + # (note that in shell, a return of 0 is success) + # by default, everything is vulnerable, we work in a "whitelist" logic here. + # usage: is_cpu_vulnerable 2 && do something if vulnerable + variant1=0 + variant2=0 + variant3=0 + + if grep -q AMD /proc/cpuinfo; then + # AMD revised their statement about variant2 => vulnerable + # https://www.amd.com/en/corporate/speculative-execution + variant3=1 + elif grep -qi 'CPU implementer\s*:\s*0x41' /proc/cpuinfo; then + # ARM + # reference: https://developer.arm.com/support/security-update + cpupart=$(awk '/CPU part/ {print $4;exit}' /proc/cpuinfo) + cpuarch=$(awk '/CPU architecture/ {print $3;exit}' /proc/cpuinfo) + if [ -n "$cpupart" -a -n "$cpuarch" ]; then + # Cortex-R7 and Cortex-R8 are real-time and only used in medical devices or such + # I can't find their CPU part number, but it's probably not that useful anyway + # model R7 R8 A9 A15 A17 A57 A72 A73 A75 + # part ? ? 0xc09 0xc0f 0xc0e 0xd07 0xd08 0xd09 0xd0a + # arch 7? 7? 7 7 7 8 8 8 8 + if [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -Eq '^0x(c09|c0f|c0e)$'; then + # armv7 vulnerable chips + : + elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -Eq '^0x(d07|d08|d09|d0a)$'; then + # armv8 vulnerable chips + : + else + variant1=1 + variant2=1 + fi + # for variant3, only A75 is vulnerable + if ! [ "$cpuarch" = 8 -a "$cpupart" = 0xd0a ]; then + variant3=1 + fi + fi + fi + + [ "$1" = 1 ] && return $variant1 + [ "$1" = 2 ] && return $variant2 + [ "$1" = 3 ] && return $variant3 + echo "$0: error: invalid variant '$1' passed to is_cpu_vulnerable()" >&2 + exit 1 +} + +show_header() +{ + _info "\033[1;34mSpectre and Meltdown mitigation detection tool v$VERSION\033[0m" + _info +} + +parse_opt_file() +{ + # parse_opt_file option_name option_value + option_name="$1" + option_value="$2" + if [ -z "$option_value" ]; then + show_header + show_usage + echo "$0: error: --$option_name expects one parameter (a file)" >&2 + exit 1 + elif [ ! -e "$option_value" ]; then + show_header + echo "$0: error: couldn't find file $option_value" >&2 + exit 1 + elif [ ! -f "$option_value" ]; then + show_header + echo "$0: error: $option_value is not a file" >&2 + exit 1 + elif [ ! -r "$option_value" ]; then + show_header + echo "$0: error: couldn't read $option_value (are you root?)" >&2 + exit 1 + fi + echo "$option_value" + exit 0 +} + +while [ -n "$1" ]; do + if [ "$1" = "--kernel" ]; then + opt_kernel=$(parse_opt_file kernel "$2") + [ $? -ne 0 ] && exit $? + shift 2 + opt_live=0 + elif [ "$1" = "--config" ]; then + opt_config=$(parse_opt_file config "$2") + [ $? -ne 0 ] && exit $? + shift 2 + opt_live=0 + elif [ "$1" = "--map" ]; then + opt_map=$(parse_opt_file map "$2") + [ $? -ne 0 ] && exit $? + shift 2 + opt_live=0 + elif [ "$1" = "--live" ]; then + opt_live_explicit=1 + shift + elif [ "$1" = "--no-color" ]; then + opt_no_color=1 + shift + elif [ "$1" = "--no-sysfs" ]; then + opt_no_sysfs=1 + shift + elif [ "$1" = "--batch" ]; then + opt_batch=1 + opt_verbose=0 + shift + case "$1" in + text|nrpe|json) opt_batch_format="$1"; shift;; + --*) ;; # allow subsequent flags + '') ;; # allow nothing at all + *) + echo "$0: error: unknown batch format '$1'" + echo "$0: error: --batch expects a format from: text, nrpe, json" + exit 1 >&2 + ;; + esac + elif [ "$1" = "-v" -o "$1" = "--verbose" ]; then + opt_verbose=$(expr $opt_verbose + 1) + shift + elif [ "$1" = "--variant" ]; then + if [ -z "$2" ]; then + echo "$0: error: option --variant expects a parameter (1, 2 or 3)" >&2 + exit 1 + fi + case "$2" in + 1) opt_variant1=1; opt_allvariants=0;; + 2) opt_variant2=1; opt_allvariants=0;; + 3) opt_variant3=1; opt_allvariants=0;; + *) + echo "$0: error: invalid parameter '$2' for --variant, expected either 1, 2 or 3" >&2; + exit 1;; + esac + shift 2 + elif [ "$1" = "-h" -o "$1" = "--help" ]; then + show_header + show_usage + exit 0 + elif [ "$1" = "--version" ]; then + opt_no_color=1 + show_header + exit 1 + elif [ "$1" = "--disclaimer" ]; then + show_header + show_disclaimer + exit 0 + else + show_header + show_usage + echo "$0: error: unknown option '$1'" + exit 1 + fi +done + +show_header + +# print status function +pstatus() +{ + if [ "$opt_no_color" = 1 ]; then + _info_nol "$2" + else + case "$1" in + red) col="\033[101m\033[30m";; + green) col="\033[102m\033[30m";; + yellow) col="\033[103m\033[30m";; + blue) col="\033[104m\033[30m";; + *) col="";; + esac + _info_nol "$col $2 \033[0m" + fi + [ -n "$3" ] && _info_nol " ($3)" + _info +} + +# Print the final status of a vulnerability (incl. batch mode) +# Arguments are: CVE UNK/OK/VULN description +pvulnstatus() +{ + if [ "$opt_batch" = 1 ]; then + case "$opt_batch_format" in + text) _echo 0 "$1: $2 ($3)";; + nrpe) + case "$2" in + UKN) nrpe_unknown="1";; + VULN) nrpe_critical="1"; nrpe_vuln="$nrpe_vuln $1";; + esac + ;; + json) + case "$1" in + CVE-2017-5753) aka="SPECTRE VARIANT 1";; + CVE-2017-5715) aka="SPECTRE VARIANT 2";; + CVE-2017-5754) aka="MELTDOWN";; + esac + case "$2" in + UKN) is_vuln="unknown";; + VULN) is_vuln="true";; + OK) is_vuln="false";; + esac + json_output="${json_output:-[}{\"NAME\":\""$aka"\",\"CVE\":\""$1"\",\"VULNERABLE\":$is_vuln,\"INFOS\":\""$3"\"}," + ;; + esac + fi + + _info_nol "> \033[46m\033[30mSTATUS:\033[0m " + vulnstatus="$2" + shift 2 + case "$vulnstatus" in + UNK) pstatus yellow UNKNOWN "$@";; + VULN) pstatus red 'VULNERABLE' "$@";; + OK) pstatus green 'NOT VULNERABLE' "$@";; + esac +} + + +# The 3 below functions are taken from the extract-linux script, available here: +# https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux +# The functions have been modified for better integration to this script +# The original header of the file has been retained below + +# ---------------------------------------------------------------------- +# extract-vmlinux - Extract uncompressed vmlinux from a kernel image +# +# Inspired from extract-ikconfig +# (c) 2009,2010 Dick Streefland +# +# (c) 2011 Corentin Chary +# +# Licensed under the GNU General Public License, version 2 (GPLv2). +# ---------------------------------------------------------------------- + +vmlinux='' +vmlinux_err='' +check_vmlinux() +{ + readelf -h "$1" > /dev/null 2>&1 || return 1 + return 0 +} + +try_decompress() +{ + # The obscure use of the "tr" filter is to work around older versions of + # "grep" that report the byte offset of the line instead of the pattern. + + # Try to find the header ($1) and decompress from here + for pos in `tr "$1\n$2" "\n$2=" < "$6" | grep -abo "^$2"` + do + _debug "try_decompress: magic for $3 found at offset $pos" + if ! which "$3" >/dev/null 2>&1; then + vmlinux_err="missing '$3' tool, please install it, usually it's in the '$5' package" + return 0 + fi + pos=${pos%%:*} + tail -c+$pos "$6" 2>/dev/null | $3 $4 > $vmlinuxtmp 2>/dev/null + if check_vmlinux "$vmlinuxtmp"; then + vmlinux="$vmlinuxtmp" + _debug "try_decompress: decompressed with $3 successfully!" + return 0 + else + _debug "try_decompress: decompression with $3 did not work" + fi + done + return 1 +} + +extract_vmlinux() +{ + [ -n "$1" ] || return 1 + # Prepare temp files: + vmlinuxtmp="$(mktemp /tmp/vmlinux-XXXXXX)" + trap "rm -f $vmlinuxtmp" EXIT + + # Initial attempt for uncompressed images or objects: + if check_vmlinux "$1"; then + cat "$1" > "$vmlinuxtmp" + vmlinux=$vmlinuxtmp + return 0 + fi + + # That didn't work, so retry after decompression. + try_decompress '\037\213\010' xy gunzip '' gunzip "$1" && return 0 + try_decompress '\3757zXZ\000' abcde unxz '' xz-utils "$1" && return 0 + try_decompress 'BZh' xy bunzip2 '' bzip2 "$1" && return 0 + try_decompress '\135\0\0\0' xxx unlzma '' xz-utils "$1" && return 0 + try_decompress '\211\114\132' xy 'lzop' '-d' lzop "$1" && return 0 + try_decompress '\002\041\114\030' xyy 'lz4' '-d -l' liblz4-tool "$1" && return 0 + return 1 +} + +# end of extract-vmlinux functions + +# check for mode selection inconsistency +if [ "$opt_live_explicit" = 1 ]; then + if [ -n "$opt_kernel" -o -n "$opt_config" -o -n "$opt_map" ]; then + show_usage + echo "$0: error: incompatible modes specified, use either --live or --kernel/--config/--map" + exit 1 + fi +fi + +# root check (only for live mode, for offline mode, we already checked if we could read the files) + +if [ "$opt_live" = 1 ]; then + if [ "$(id -u)" -ne 0 ]; then + _warn "Note that you should launch this script with root privileges to get accurate information." + _warn "We'll proceed but you might see permission denied errors." + _warn "To run it as root, you can try the following command: sudo $0" + _warn + fi + _info "Checking for vulnerabilities against running kernel \033[35m"$(uname -s) $(uname -r) $(uname -v) $(uname -m)"\033[0m" + _info "CPU is\033[35m"$(grep '^model name' /proc/cpuinfo | cut -d: -f2 | head -1)"\033[0m" + + # try to find the image of the current running kernel + # first, look for the BOOT_IMAGE hint in the kernel cmdline + if [ -r /proc/cmdline ] && grep -q 'BOOT_IMAGE=' /proc/cmdline; then + opt_kernel=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2) + _debug "found opt_kernel=$opt_kernel in /proc/cmdline" + # if we have a dedicated /boot partition, our bootloader might have just called it / + # so try to prepend /boot and see if we find anything + [ -e "/boot/$opt_kernel" ] && opt_kernel="/boot/$opt_kernel" + _debug "opt_kernel is now $opt_kernel" + # else, the full path is already there (most probably /boot/something) + fi + # if we didn't find a kernel, default to guessing + if [ ! -e "$opt_kernel" ]; then + [ -e /boot/vmlinuz-linux ] && opt_kernel=/boot/vmlinuz-linux + [ -e /boot/vmlinuz-linux-libre ] && opt_kernel=/boot/vmlinuz-linux-libre + [ -e /boot/vmlinuz-$(uname -r) ] && opt_kernel=/boot/vmlinuz-$(uname -r) + [ -e /boot/kernel-$( uname -r) ] && opt_kernel=/boot/kernel-$( uname -r) + [ -e /boot/bzImage-$(uname -r) ] && opt_kernel=/boot/bzImage-$(uname -r) + [ -e /boot/kernel-genkernel-$(uname -m)-$(uname -r) ] && opt_kernel=/boot/kernel-genkernel-$(uname -m)-$(uname -r) + fi + + # system.map + if [ -e /proc/kallsyms ] ; then + opt_map="/proc/kallsyms" + elif [ -e /boot/System.map-$(uname -r) ] ; then + opt_map=/boot/System.map-$(uname -r) + fi + + # config + if [ -e /proc/config.gz ] ; then + dumped_config="$(mktemp /tmp/config-XXXXXX)" + gunzip -c /proc/config.gz > $dumped_config + # dumped_config will be deleted at the end of the script + opt_config=$dumped_config + elif [ -e /boot/config-$(uname -r) ]; then + opt_config=/boot/config-$(uname -r) + fi +else + _info "Checking for vulnerabilities against specified kernel" +fi + +if [ -n "$opt_kernel" ]; then + _verbose "Will use vmlinux image \033[35m$opt_kernel\033[0m" +else + _verbose "Will use no vmlinux image (accuracy might be reduced)" + bad_accuracy=1 +fi +if [ -n "$dumped_config" ]; then + _verbose "Will use kconfig \033[35m/proc/config.gz\033[0m" +elif [ -n "$opt_config" ]; then + _verbose "Will use kconfig \033[35m$opt_config\033[0m" +else + _verbose "Will use no kconfig (accuracy might be reduced)" + bad_accuracy=1 +fi +if [ -n "$opt_map" ]; then + _verbose "Will use System.map file \033[35m$opt_map\033[0m" +else + _verbose "Will use no System.map file (accuracy might be reduced)" + bad_accuracy=1 +fi + +if [ "$bad_accuracy" = 1 ]; then + _info "We're missing some kernel info (see -v), accuracy might be reduced" +fi + +if [ -e "$opt_kernel" ]; then + if ! which readelf >/dev/null 2>&1; then + vmlinux_err="missing 'readelf' tool, please install it, usually it's in the 'binutils' package" + else + extract_vmlinux "$opt_kernel" + fi +else + vmlinux_err="couldn't find your kernel image in /boot, if you used netboot, this is normal" +fi +if [ -z "$vmlinux" -o ! -r "$vmlinux" ]; then + [ -z "$vmlinux_err" ] && vmlinux_err="couldn't extract your kernel from $opt_kernel" +fi + +_info + +# end of header stuff + +# now we define some util functions and the check_*() funcs, as +# the user can choose to execute only some of those + +mount_debugfs() +{ + if [ ! -e /sys/kernel/debug/sched_features ]; then + # try to mount the debugfs hierarchy ourselves and remember it to umount afterwards + mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null && mounted_debugfs=1 + fi +} + +umount_debugfs() +{ + if [ "$mounted_debugfs" = 1 ]; then + # umount debugfs if we did mount it ourselves + umount /sys/kernel/debug + fi +} + +sys_interface_check() +{ + [ "$opt_live" = 1 -a "$opt_no_sysfs" = 0 -a -r "$1" ] || return 1 + _info_nol "* Checking whether we're safe according to the /sys interface: " + if grep -qi '^not affected' "$1"; then + # Not affected + status=OK + pstatus green YES "kernel confirms that your CPU is unaffected" + elif grep -qi '^mitigation' "$1"; then + # Mitigation: PTI + status=OK + pstatus green YES "kernel confirms that the mitigation is active" + elif grep -qi '^vulnerable' "$1"; then + # Vulnerable + status=VULN + pstatus red NO "kernel confirms your system is vulnerable" + else + status=UNK + pstatus yellow UNKNOWN "unknown value reported by kernel" + fi + msg=$(cat "$1") + _debug "sys_interface_check: $1=$msg" + return 0 +} + +################### +# SPECTRE VARIANT 1 +check_variant1() +{ + _info "\033[1;34mCVE-2017-5753 [bounds check bypass] aka 'Spectre Variant 1'\033[0m" + + status=UNK + sys_interface_available=0 + msg='' + if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/spectre_v1"; then + # this kernel has the /sys interface, trust it over everything + sys_interface_available=1 + else + # no /sys interface (or offline mode), fallback to our own ways + _info_nol "* Checking count of LFENCE opcodes in kernel: " + if [ -n "$vmlinux_err" ]; then + msg="couldn't check ($vmlinux_err)" + status=UNK + pstatus yellow UNKNOWN + else + if ! which objdump >/dev/null 2>&1; then + msg="missing 'objdump' tool, please install it, usually it's in the binutils package" + status=UNK + pstatus yellow UNKNOWN + else + # here we disassemble the kernel and count the number of occurrences of the LFENCE opcode + # in non-patched kernels, this has been empirically determined as being around 40-50 + # in patched kernels, this is more around 70-80, sometimes way higher (100+) + # v0.13: 68 found in a 3.10.23-xxxx-std-ipv6-64 (with lots of modules compiled-in directly), which doesn't have the LFENCE patches, + # so let's push the threshold to 70. + nb_lfence=$(objdump -d "$vmlinux" | grep -wc lfence) + if [ "$nb_lfence" -lt 70 ]; then + msg="only $nb_lfence opcodes found, should be >= 70, heuristic to be improved when official patches become available" + status=VULN + pstatus red NO + else + msg="$nb_lfence opcodes found, which is >= 70, heuristic to be improved when official patches become available" + status=OK + pstatus green YES + fi + fi + fi + fi + + # if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it + if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 1; then + # override status & msg in case CPU is not vulnerable after all + msg="your CPU vendor reported your CPU model as not vulnerable" + status=OK + fi + + # report status + pvulnstatus CVE-2017-5753 "$status" "$msg" +} + +################### +# SPECTRE VARIANT 2 +check_variant2() +{ + _info "\033[1;34mCVE-2017-5715 [branch target injection] aka 'Spectre Variant 2'\033[0m" + + status=UNK + sys_interface_available=0 + msg='' + if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/spectre_v2"; then + # this kernel has the /sys interface, trust it over everything + sys_interface_available=1 + else + _info "* Mitigation 1" + _info_nol "* Hardware (CPU microcode) support for mitigation: " + if [ ! -e /dev/cpu/0/msr ]; then + # try to load the module ourselves (and remember it so we can rmmod it afterwards) + modprobe msr 2>/dev/null && insmod_msr=1 + _debug "attempted to load module msr, ret=$insmod_msr" + fi + if [ ! -e /dev/cpu/0/msr ]; then + pstatus yellow UNKNOWN "couldn't read /dev/cpu/0/msr, is msr support enabled in your kernel?" + else + # the new MSR 'SPEC_CTRL' is at offset 0x48 + # here we use dd, it's the same as using 'rdmsr 0x48' but without needing the rdmsr tool + # if we get a read error, the MSR is not there + dd if=/dev/cpu/0/msr of=/dev/null bs=8 count=1 skip=9 2>/dev/null + if [ $? -eq 0 ]; then + pstatus green YES + else + pstatus red NO + fi + fi + + if [ "$insmod_msr" = 1 ]; then + # if we used modprobe ourselves, rmmod the module + rmmod msr 2>/dev/null + _debug "attempted to unload module msr, ret=$?" + fi + + _info_nol "* Kernel support for IBRS: " + if [ "$opt_live" = 1 ]; then + mount_debugfs + for ibrs_file in \ + /sys/kernel/debug/ibrs_enabled \ + /sys/kernel/debug/x86/ibrs_enabled \ + /proc/sys/kernel/ibrs_enabled; do + if [ -e "$ibrs_file" ]; then + # if the file is there, we have IBRS compiled-in + # /sys/kernel/debug/ibrs_enabled: vanilla + # /sys/kernel/debug/x86/ibrs_enabled: RedHat (see https://access.redhat.com/articles/3311301) + # /proc/sys/kernel/ibrs_enabled: OpenSUSE tumbleweed + pstatus green YES + ibrs_supported=1 + ibrs_enabled=$(cat "$ibrs_file" 2>/dev/null) + _debug "ibrs: found $ibrs_file=$ibrs_enabled" + break + else + _debug "ibrs: file $ibrs_file doesn't exist" + fi + done + fi + if [ "$ibrs_supported" != 1 -a -n "$opt_map" ]; then + if grep -q spec_ctrl "$opt_map"; then + pstatus green YES + ibrs_supported=1 + _debug "ibrs: found '*spec_ctrl*' symbol in $opt_map" + fi + fi + if [ "$ibrs_supported" != 1 ]; then + pstatus red NO + fi + + _info_nol "* IBRS enabled for Kernel space: " + if [ "$opt_live" = 1 ]; then + # 0 means disabled + # 1 is enabled only for kernel space + # 2 is enabled for kernel and user space + case "$ibrs_enabled" in + "") [ "$ibrs_supported" = 1 ] && pstatus yellow UNKNOWN || pstatus red NO;; + 0) pstatus red NO;; + 1 | 2) pstatus green YES;; + *) pstatus yellow UNKNOWN;; + esac + else + pstatus blue N/A "not testable in offline mode" + fi + + _info_nol "* IBRS enabled for User space: " + if [ "$opt_live" = 1 ]; then + case "$ibrs_enabled" in + "") [ "$ibrs_supported" = 1 ] && pstatus yellow UNKNOWN || pstatus red NO;; + 0 | 1) pstatus red NO;; + 2) pstatus green YES;; + *) pstatus yellow UNKNOWN;; + esac + else + pstatus blue N/A "not testable in offline mode" + fi + + _info "* Mitigation 2" + _info_nol "* Kernel compiled with retpoline option: " + # We check the RETPOLINE kernel options + if [ -r "$opt_config" ]; then + if grep -q '^CONFIG_RETPOLINE=y' "$opt_config"; then + pstatus green YES + retpoline=1 + _debug "retpoline: found "$(grep '^CONFIG_RETPOLINE' "$opt_config")" in $opt_config" + else + pstatus red NO + fi + else + pstatus yellow UNKNOWN "couldn't read your kernel configuration" + fi + + _info_nol "* Kernel compiled with a retpoline-aware compiler: " + # Now check if the compiler used to compile the kernel knows how to insert retpolines in generated asm + # For gcc, this is -mindirect-branch=thunk-extern (detected by the kernel makefiles) + # See gcc commit https://github.com/hjl-tools/gcc/commit/23b517d4a67c02d3ef80b6109218f2aadad7bd79 + # In latest retpoline LKML patches, the noretpoline_setup symbol exists only if CONFIG_RETPOLINE is set + # *AND* if the compiler is retpoline-compliant, so look for that symbol + if [ -n "$opt_map" ]; then + # look for the symbol + if grep -qw noretpoline_setup "$opt_map"; then + retpoline_compiler=1 + pstatus green YES "noretpoline_setup symbol found in System.map" + else + pstatus red NO + fi + elif [ -n "$vmlinux" ]; then + # look for the symbol + if which nm >/dev/null 2>&1; then + # the proper way: use nm and look for the symbol + if nm "$vmlinux" 2>/dev/null | grep -qw 'noretpoline_setup'; then + retpoline_compiler=1 + pstatus green YES "noretpoline_setup found in vmlinux symbols" + else + pstatus red NO + fi + elif grep -q noretpoline_setup "$vmlinux"; then + # if we don't have nm, nevermind, the symbol name is long enough to not have + # any false positive using good old grep directly on the binary + retpoline_compiler=1 + pstatus green YES "noretpoline_setup found in vmlinux" + else + pstatus red NO + fi + else + pstatus yellow UNKNOWN "couldn't find your kernel image or System.map" + fi + fi + + # if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it + if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 2; then + # override status & msg in case CPU is not vulnerable after all + pvulnstatus CVE-2017-5715 OK "your CPU vendor reported your CPU model as not vulnerable" + elif [ -z "$msg" ]; then + # if msg is empty, sysfs check didn't fill it, rely on our own test + if [ "$retpoline" = 1 -a "$retpoline_compiler" = 1 ]; then + pvulnstatus CVE-2017-5715 OK "retpoline mitigate the vulnerability" + elif [ "$opt_live" = 1 ]; then + if [ "$ibrs_enabled" = 1 -o "$ibrs_enabled" = 2 ]; then + pvulnstatus CVE-2017-5715 OK "IBRS mitigates the vulnerability" + else + pvulnstatus CVE-2017-5715 VULN "IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability" + fi + else + if [ "$ibrs_supported" = 1 ]; then + pvulnstatus CVE-2017-5715 OK "offline mode: IBRS will mitigate the vulnerability if enabled at runtime" + else + pvulnstatus CVE-2017-5715 VULN "IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability" + fi + fi + else + pvulnstatus CVE-2017-5715 "$status" "$msg" + fi +} + +######################## +# MELTDOWN aka VARIANT 3 +check_variant3() +{ + _info "\033[1;34mCVE-2017-5754 [rogue data cache load] aka 'Meltdown' aka 'Variant 3'\033[0m" + + status=UNK + sys_interface_available=0 + msg='' + if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/meltdown"; then + # this kernel has the /sys interface, trust it over everything + sys_interface_available=1 + else + _info_nol "* Kernel supports Page Table Isolation (PTI): " + kpti_support=0 + kpti_can_tell=0 + if [ -n "$opt_config" ]; then + kpti_can_tell=1 + if grep -Eq '^(CONFIG_PAGE_TABLE_ISOLATION|CONFIG_KAISER)=y' "$opt_config"; then + _debug "kpti_support: found option "$(grep -E '^(CONFIG_PAGE_TABLE_ISOLATION|CONFIG_KAISER)=y' "$opt_config")" in $opt_config" + kpti_support=1 + fi + fi + if [ "$kpti_support" = 0 -a -n "$opt_map" ]; then + # it's not an elif: some backports don't have the PTI config but still include the patch + # so we try to find an exported symbol that is part of the PTI patch in System.map + kpti_can_tell=1 + if grep -qw kpti_force_enabled "$opt_map"; then + _debug "kpti_support: found kpti_force_enabled in $opt_map" + kpti_support=1 + fi + fi + if [ "$kpti_support" = 0 -a -n "$vmlinux" ]; then + # same as above but in case we don't have System.map and only vmlinux, look for the + # nopti option that is part of the patch (kernel command line option) + kpti_can_tell=1 + if ! which strings >/dev/null 2>&1; then + pstatus yellow UNKNOWN "missing 'strings' tool, please install it, usually it's in the binutils package" + else + if strings "$vmlinux" | grep -qw nopti; then + _debug "kpti_support: found nopti string in $vmlinux" + kpti_support=1 + fi + fi + fi + + if [ "$kpti_support" = 1 ]; then + pstatus green YES + elif [ "$kpti_can_tell" = 1 ]; then + pstatus red NO + else + pstatus yellow UNKNOWN "couldn't read your kernel configuration nor System.map file" + fi + + mount_debugfs + _info_nol "* PTI enabled and active: " + if [ "$opt_live" = 1 ]; then + dmesg_grep="Kernel/User page tables isolation: enabled" + dmesg_grep="$dmesg_grep|Kernel page table isolation enabled" + dmesg_grep="$dmesg_grep|x86/pti: Unmapping kernel while in userspace" + if grep ^flags /proc/cpuinfo | grep -qw pti; then + # vanilla PTI patch sets the 'pti' flag in cpuinfo + _debug "kpti_enabled: found 'pti' flag in /proc/cpuinfo" + kpti_enabled=1 + elif grep ^flags /proc/cpuinfo | grep -qw kaiser; then + # kernel line 4.9 sets the 'kaiser' flag in cpuinfo + _debug "kpti_enabled: found 'kaiser' flag in /proc/cpuinfo" + kpti_enabled=1 + elif [ -e /sys/kernel/debug/x86/pti_enabled ]; then + # RedHat Backport creates a dedicated file, see https://access.redhat.com/articles/3311301 + kpti_enabled=$(cat /sys/kernel/debug/x86/pti_enabled 2>/dev/null) + _debug "kpti_enabled: file /sys/kernel/debug/x86/pti_enabled exists and says: $kpti_enabled" + elif dmesg | grep -Eq "$dmesg_grep"; then + # if we can't find the flag, grep dmesg output + _debug "kpti_enabled: found hint in dmesg: "$(dmesg | grep -E "$dmesg_grep") + kpti_enabled=1 + elif [ -r /var/log/dmesg ] && grep -Eq "$dmesg_grep" /var/log/dmesg; then + # if we can't find the flag in dmesg output, grep in /var/log/dmesg when readable + _debug "kpti_enabled: found hint in /var/log/dmesg: "$(grep -E "$dmesg_grep" /var/log/dmesg) + kpti_enabled=1 + else + _debug "kpti_enabled: couldn't find any hint that PTI is enabled" + kpti_enabled=0 + fi + if [ "$kpti_enabled" = 1 ]; then + pstatus green YES + else + pstatus red NO + fi + else + pstatus blue N/A "can't verify if PTI is enabled in offline mode" + fi + fi + + # if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it + cve='CVE-2017-5754' + if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 3; then + # override status & msg in case CPU is not vulnerable after all + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + elif [ -z "$msg" ]; then + # if msg is empty, sysfs check didn't fill it, rely on our own test + if [ "$opt_live" = 1 ]; then + if [ "$kpti_enabled" = 1 ]; then + pvulnstatus $cve OK "PTI mitigates the vulnerability" + else + pvulnstatus $cve VULN "PTI is needed to mitigate the vulnerability" + fi + else + if [ "$kpti_support" = 1 ]; then + pvulnstatus $cve OK "offline mode: PTI will mitigate the vulnerability if enabled at runtime" + else + pvulnstatus $cve VULN "PTI is needed to mitigate the vulnerability" + fi + fi + else + pvulnstatus $cve "$status" "$msg" + fi +} + +# now run the checks the user asked for +if [ "$opt_variant1" = 1 -o "$opt_allvariants" = 1 ]; then + check_variant1 + _info +fi +if [ "$opt_variant2" = 1 -o "$opt_allvariants" = 1 ]; then + check_variant2 + _info +fi +if [ "$opt_variant3" = 1 -o "$opt_allvariants" = 1 ]; then + check_variant3 + _info +fi + +_info "A false sense of security is worse than no security at all, see --disclaimer" + +# this'll umount only if we mounted debugfs ourselves +umount_debugfs + +# cleanup the temp decompressed config +[ -n "$dumped_config" ] && rm -f "$dumped_config" + +if [ "$opt_batch" = 1 -a "$opt_batch_format" = "nrpe" ]; then + if [ ! -z "$nrpe_vuln" ]; then + echo "Vulnerable:$nrpe_vuln" + else + echo "OK" + fi + [ "$nrpe_critical" = 1 ] && exit 2 # critical + [ "$nrpe_unknown" = 1 ] && exit 3 # unknown + exit 0 # ok +fi + +if [ "$opt_batch" = 1 -a "$opt_batch_format" = "json" ]; then + _echo 0 ${json_output%?}] +fi From 979123bf61a446feb9be4b55531e28cc5c60dbba Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 13 Jan 2018 23:01:29 +0100 Subject: [PATCH 079/132] [fix] other part of the code didn't expected this new datastructure --- src/yunohost/utils/packages.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 7df311406..d19e6d774 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -431,12 +431,15 @@ def get_installed_version(*pkgnames, **kwargs): raise UninstalledPackage(pkgname) repo = "" - versions[pkgname] = { - "version": version, - # when we don't have component it's because it's from a local - # install or from an image (like in vagrant) - "repo": repo if repo else "local", - } + if as_dict: + versions[pkgname] = { + "version": version, + # when we don't have component it's because it's from a local + # install or from an image (like in vagrant) + "repo": repo if repo else "local", + } + else: + versions[pkgname] = version if len(pkgnames) == 1 and not as_dict: return versions[pkgnames[0]] @@ -458,4 +461,5 @@ def ynh_packages_version(*args, **kwargs): """Return the version of each YunoHost package""" return get_installed_version( 'yunohost', 'yunohost-admin', 'moulinette', 'ssowat', + as_dict=True ) From 27262f0e4bfb6afbed6e54460a236cdd660a846c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Jan 2018 00:30:06 +0100 Subject: [PATCH 080/132] Fix english --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 5f5eb957f..8f83b709c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -101,7 +101,7 @@ "backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", "backup_output_directory_not_empty": "The output directory is not empty", "backup_output_directory_required": "You must provide an output directory for the backup", - "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably miss to remount or plug your hard dirve or usb key.", + "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", "backup_running_app_script": "Running backup script of app '{app:s}'...", "backup_running_hooks": "Running backup hooks...", "backup_system_part_failed": "Unable to backup the '{part:s}' system part", From 6ccf054d5c2b265a37b4e495ec476ed9dff301ab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Jan 2018 00:40:44 +0100 Subject: [PATCH 081/132] Forgot to import errno --- src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py index 15f092b75..9cfd64842 100644 --- a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py +++ b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py @@ -4,6 +4,7 @@ import requests import base64 import time import json +import errno from moulinette import m18n from moulinette.core import MoulinetteError From d8435eaccc6627b02a2ded47f80a755ef5dbece9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Jan 2018 00:59:48 +0100 Subject: [PATCH 082/132] Add missing translation --- locales/en.json | 1 + src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index a2df29a54..ffb3233d4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -217,6 +217,7 @@ "migrate_tsig_wait_2": "2min...", "migrate_tsig_wait_3": "1min...", "migrate_tsig_wait_4": "30 secondes...", + "migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed !", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalide number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", diff --git a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py index 9cfd64842..5d495b06c 100644 --- a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py +++ b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py @@ -30,7 +30,7 @@ class MyMigration(Migration): (domain, private_key_path) = _guess_current_dyndns_domain(dyn_host) assert "+157" in private_key_path except (MoulinetteError, AssertionError): - logger.warning("migrate_tsig_not_needed") + logger.warning(m18n.n("migrate_tsig_not_needed")) return logger.warning(m18n.n('migrate_tsig_start', domain=domain)) From 4eeeb783af42ce41bbf65949d8ed24fe9d5fa8cd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Jan 2018 02:11:11 +0100 Subject: [PATCH 083/132] Keep only css and javascript for gzip types. --- data/templates/nginx/plain/global.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/global.conf b/data/templates/nginx/plain/global.conf index 341f08620..ca8721afb 100644 --- a/data/templates/nginx/plain/global.conf +++ b/data/templates/nginx/plain/global.conf @@ -1,2 +1,2 @@ server_tokens off; -gzip_types text/plain text/css application/javascript text/xml application/xml application/xml+rss text/javascript; +gzip_types text/css text/javascript application/javascript; From 235a2ed2b71189c13f332ef11b067c59b84c6036 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 14 Jan 2018 02:59:03 +0100 Subject: [PATCH 084/132] [fix] as_dict was used for manifest comparison --- src/yunohost/utils/packages.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index d19e6d774..3917ef563 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -406,6 +406,7 @@ def get_installed_version(*pkgnames, **kwargs): # Retrieve options as_dict = kwargs.get('as_dict', False) strict = kwargs.get('strict', False) + with_repo = kwargs.get('with_repo', False) for pkgname in pkgnames: try: @@ -431,7 +432,7 @@ def get_installed_version(*pkgnames, **kwargs): raise UninstalledPackage(pkgname) repo = "" - if as_dict: + if with_repo: versions[pkgname] = { "version": version, # when we don't have component it's because it's from a local @@ -461,5 +462,5 @@ def ynh_packages_version(*args, **kwargs): """Return the version of each YunoHost package""" return get_installed_version( 'yunohost', 'yunohost-admin', 'moulinette', 'ssowat', - as_dict=True + with_repo=True ) From e9c87da02830aecc1d0860164e1e0fa5217644a3 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 15 Jan 2018 22:59:20 +0100 Subject: [PATCH 085/132] [fix] previous patch mistakely remove variable name --- data/helpers.d/string | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index 43c4aab1a..f708b31b1 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -24,7 +24,7 @@ ynh_replace_string () { local delimit=@ local match_string=$1 local replace_string=$2 - local workfile=$ + local workfile=$3 # Escape the delimiter if it's in the string. match_string=${match_string//${delimit}/"\\${delimit}"} From e1f2dd6a597e2bb342d9e34463ae18c988c37487 Mon Sep 17 00:00:00 2001 From: David B Date: Fri, 22 Dec 2017 14:16:48 +0000 Subject: [PATCH 086/132] [i18n] Translated using Weblate (Spanish) Currently translated at 79.4% (282 of 355 strings) --- locales/es.json | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/locales/es.json b/locales/es.json index f1c125d02..eba43fac2 100644 --- a/locales/es.json +++ b/locales/es.json @@ -13,7 +13,7 @@ "app_install_files_invalid": "Los archivos de instalación no son válidos", "app_location_already_used": "Una aplicación ya está instalada en esta localización", "app_location_install_failed": "No se puede instalar la aplicación en esta localización", - "app_manifest_invalid": "El manifiesto de la aplicación no es válido", + "app_manifest_invalid": "El manifiesto de la aplicación no es válido: {error}", "app_no_upgrade": "No hay aplicaciones para actualizar", "app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada", "app_not_installed": "{app:s} 9 no está instalada", @@ -29,9 +29,9 @@ "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", "app_upgrade_failed": "No se pudo actualizar la aplicación {app:s}", "app_upgraded": "{app:s} ha sido actualizada", - "appslist_fetched": "Lista de aplicaciones ha sido descargada", - "appslist_removed": "La lista de aplicaciones ha sido eliminada", - "appslist_retrieve_error": "No se pudo recuperar la lista remota de aplicaciones: {error}", + "appslist_fetched": "La lista de aplicaciones {appslist:s} ha sido descargada", + "appslist_removed": "La lista de aplicaciones {appslist:s} ha sido eliminada", + "appslist_retrieve_error": "No se pudo recuperar la lista remota de aplicaciones {appslist:s} : {error}", "appslist_unknown": "Lista de aplicaciones desconocida", "ask_current_admin_password": "Contraseña administrativa actual", "ask_email": "Dirección de correo electrónico", @@ -272,7 +272,7 @@ "certmanager_acme_not_configured_for_domain": "El certificado para el dominio {domain:s} no parece instalado correctamente. Ejecute primero cert-install para este dominio.", "certmanager_http_check_timeout": "Plazo expirado, el servidor no ha podido contactarse a si mismo a través de HTTP usando su dirección IP pública (dominio {domain:s} con ip {ip:s}). Puede ser debido a hairpinning o a una mala configuración del cortafuego/router al que está conectado su servidor.", "certmanager_couldnt_fetch_intermediate_cert": "Plazo expirado, no se ha podido descargar el certificado intermedio de Let's Encrypt. La instalación/renovación del certificado ha sido cancelada - vuelva a intentarlo más tarde.", - "appslist_retrieve_bad_format": "El archivo recuperado no es una lista de aplicaciones válida", + "appslist_retrieve_bad_format": "El archivo obtenido para la lista de aplicaciones {appslist:s} no es válido", "domain_hostname_failed": "Error al establecer nuevo nombre de host", "yunohost_ca_creation_success": "Se ha creado la autoridad de certificación local.", "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción 'app changeurl'.", @@ -283,10 +283,11 @@ "app_change_url_success": "El URL de la aplicación {app:s} ha sido cambiado correctamente a {domain:s} {path:s}", "app_location_unavailable": "Este URL no está disponible o está en conflicto con otra aplicación instalada.", "app_already_up_to_date": "La aplicación {app:s} ya está actualizada", - "appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con nombre {name:s}.", + "appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con el nombre {name:s}.", "appslist_url_already_tracked": "Ya existe una lista de aplicaciones registrada con el URL {url:s}.", "appslist_migrating": "Migrando la lista de aplicaciones {applist:s} ...", "appslist_could_not_migrate": "No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL ... El antiguo cronjob se ha mantenido en {bkp_file:s}.", "appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename: s} está dañado.", - "invalid_url_format": "Formato de URL no válido" + "invalid_url_format": "Formato de URL no válido", + "app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones" } From f132031b38fe745eadfbfa15698f63fc1c98c2a5 Mon Sep 17 00:00:00 2001 From: Lapineige Date: Sun, 7 Jan 2018 19:06:07 +0000 Subject: [PATCH 087/132] [i18n] Translated using Weblate (French) Currently translated at 100.0% (357 of 357 strings) --- locales/fr.json | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 8baccbd70..498e1f469 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -10,21 +10,21 @@ "app_argument_required": "Le paramètre « {name:s} » est requis", "app_extraction_failed": "Impossible d'extraire les fichiers d'installation", "app_id_invalid": "Id d'application incorrect", - "app_incompatible": "L'application est incompatible avec votre version de YunoHost", + "app_incompatible": "L'application {app} est incompatible avec votre version de YunoHost", "app_install_files_invalid": "Fichiers d'installation incorrects", - "app_location_already_used": "Une application est déjà installée à cet emplacement", - "app_location_install_failed": "Impossible d'installer l'application à cet emplacement", + "app_location_already_used": "L'application '{app}' est déjà installée à cet emplacement ({path})", + "app_location_install_failed": "Impossible d'installer l'application à cet emplacement pour cause de conflit avec l'app '{other_app}' déjà installée sur '{other_path}'", "app_manifest_invalid": "Manifeste d'application incorrect : {error}", "app_no_upgrade": "Aucune application à mettre à jour", "app_not_correctly_installed": "{app:s} semble être mal installé", "app_not_installed": "{app:s} n'est pas installé", "app_not_properly_removed": "{app:s} n'a pas été supprimé correctement", - "app_package_need_update": "Le paquet de l'application doit être mis à jour pour suivre les changements de YunoHost", + "app_package_need_update": "Le paquet de l'application {app} doit être mis à jour pour suivre les changements de YunoHost", "app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost", "app_removed": "{app:s} a été supprimé", - "app_requirements_checking": "Vérification des paquets requis...", - "app_requirements_failed": "Impossible de satisfaire les pré-requis : {error}", - "app_requirements_unmeet": "Les pré-requis ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", + "app_requirements_checking": "Vérification des paquets requis pour {app}...", + "app_requirements_failed": "Impossible de satisfaire les pré-requis pour {app} : {error}", + "app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources", "app_unknown": "Application inconnue", "app_unsupported_remote_type": "Le type distant utilisé par l'application n'est pas supporté", @@ -367,5 +367,7 @@ "app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications", "ask_path": "Chemin", "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", - "dyndns_domain_not_provided": "Le fournisseur Dyndns {provider:s} ne peut pas fournir le domaine {domain:s}." + "dyndns_domain_not_provided": "Le fournisseur Dyndns {provider:s} ne peut pas fournir le domaine {domain:s}.", + "app_make_default_location_already_used": "Impossible de configurer l'app '{app}' par défaut pour le domaine {domain}, déjà utilisé par l'autre app '{other_app}'", + "app_upgrade_app_name": "Mise à jour de l'application {app}..." } From eab5038edc48ab2b531bb37414407ada39e7ccb9 Mon Sep 17 00:00:00 2001 From: Lapineige Date: Sun, 7 Jan 2018 19:06:56 +0000 Subject: [PATCH 088/132] [i18n] Translated using Weblate (French) Currently translated at 100.0% (358 of 358 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 498e1f469..00b98840c 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -369,5 +369,6 @@ "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", "dyndns_domain_not_provided": "Le fournisseur Dyndns {provider:s} ne peut pas fournir le domaine {domain:s}.", "app_make_default_location_already_used": "Impossible de configurer l'app '{app}' par défaut pour le domaine {domain}, déjà utilisé par l'autre app '{other_app}'", - "app_upgrade_app_name": "Mise à jour de l'application {app}..." + "app_upgrade_app_name": "Mise à jour de l'application {app}...", + "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque / clef USB." } From b23bc434f8d0beaa1e624b502783c1596a3b8ee8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 16 Jan 2018 18:49:31 +0100 Subject: [PATCH 089/132] Bigger depreciation / more explicit depreciation warning about checkurl... --- locales/en.json | 1 + src/yunohost/app.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/locales/en.json b/locales/en.json index 8f83b709c..ca03fbb17 100644 --- a/locales/en.json +++ b/locales/en.json @@ -14,6 +14,7 @@ "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", + "app_checkurl_is_deprecated": "Packagers /!\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !", "app_extraction_failed": "Unable to extract installation files", "app_id_invalid": "Invalid app id", "app_incompatible": "The app {app} is incompatible with your YunoHost version", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 9ccc0886d..0ee0dd7d3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1149,6 +1149,9 @@ def app_checkurl(auth, url, app=None): app -- Write domain & path to app settings for further checks """ + + logger.warning(m18n.n("app_checkurl_is_deprecated")) + from yunohost.domain import domain_list if "https://" == url[:8]: From eb38100265d1758938641b07989b994c2f093ada Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 16 Jan 2018 17:40:32 -0500 Subject: [PATCH 090/132] Update changelog for 2.7.6 release --- debian/changelog | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/debian/changelog b/debian/changelog index bd7430cb7..22188585a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,31 @@ +yunohost (2.7.6) testing; urgency=low + + Major changes: + + * [enh] Add new api entry point to check for Meltdown vulnerability + * [enh] New command 'app change-label' + + Misc fixes/improvements: + + * [helpers] Fix upgrade of fake package + * [helpers] Fix ynh_use_logrotate + * [helpers] Fix broken ynh_replace_string + * [helpers] Use local variables + * [enh/fix] Save the conf/ directory of app during installation and upgrade + * [enh] Improve UX for app messages + * [enh] Keep SSH sessions alive + * [enh] --version now display stable/testing/unstable information + * [enh] Backup: add ability to symlink the archives dir + * [enh] Add regen-conf messages, nginx -t and backports .deb to diagnosis output + * [fix] Comment line syntax for DNS zone recommendation (use ';') + * [fix] Fix a bug in disk diagnosis + * [mod] Use systemctl for all service operations + * [i18n] Improved Spanish and French translations + + Thanks to all contributors (Maniack, Josue, Bram, ljf, Aleks, Jocelyn, JimboeJoe, David B, Lapineige, ...) ! <3 + + -- Alexandre Aubin Tue, 16 Jan 2018 17:17:34 -0500 + yunohost (2.7.5) stable; urgency=low (Bumping version number for stable release) From 8a2434eb44514b3fa2bf2d5e069471960cda8414 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 17 Jan 2018 17:19:06 +0100 Subject: [PATCH 091/132] [fix] Nginx traversal issue --- data/templates/nginx/plain/yunohost_admin.conf.inc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf.inc b/data/templates/nginx/plain/yunohost_admin.conf.inc index b0ab4cef6..92e1e0ccf 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf.inc +++ b/data/templates/nginx/plain/yunohost_admin.conf.inc @@ -1,4 +1,6 @@ -location /yunohost/admin { +# Fix the nginx traversal weak ( #1034 ) +rewrite ^/yunohost/admin$ /yunohost/admin/ permanent; +location /yunohost/admin/ { alias /usr/share/yunohost/admin/; default_type text/html; index index.html; From 5cf6895ba20a34268b7d51a8bb2177549bd5809b Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 17 Jan 2018 17:22:13 +0100 Subject: [PATCH 092/132] [fix] Bad issue number in a comment --- data/templates/nginx/plain/yunohost_admin.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf.inc b/data/templates/nginx/plain/yunohost_admin.conf.inc index 92e1e0ccf..f516a9d4b 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf.inc +++ b/data/templates/nginx/plain/yunohost_admin.conf.inc @@ -1,4 +1,4 @@ -# Fix the nginx traversal weak ( #1034 ) +# Fix the nginx traversal weak ( #1037 ) rewrite ^/yunohost/admin$ /yunohost/admin/ permanent; location /yunohost/admin/ { alias /usr/share/yunohost/admin/; From fcd587392730ad7e3e9b834ee332aafc74f40381 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 17 Jan 2018 18:17:38 +0100 Subject: [PATCH 093/132] [fix] Cron issue during custom backup --- src/yunohost/backup.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 0c957db7e..15c793802 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1543,9 +1543,13 @@ class BackupMethod(object): # Can create a hard link only if files are on the same fs # (i.e. we can't if it's on a different fs) if os.stat(src).st_dev == os.stat(dest_dir).st_dev: - os.link(src, dest) - # Success, go to next file to organize - continue + # Don't hardlink /etc/cron.d files to avoid cron bug + # 'NUMBER OF HARD LINKS > 1' see #1043 + cron_path = os.path.abspath('/etc/cron') + '.' + if not os.path.abspath(src).startswith(cron_path): + os.link(src, dest) + # Success, go to next file to organize + continue # If mountbind or hardlink couldnt be created, # prepare a list of files that need to be copied From 6078e42ed97863680a304a1a0800e7546afd6386 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 17 Jan 2018 18:59:34 +0100 Subject: [PATCH 094/132] [fix] wrap 'nginx -t' diagnosis call in try/catch --- src/yunohost/tools.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 8b6e5cc44..c209254b8 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -593,7 +593,12 @@ def tools_diagnosis(auth, private=False): } # nginx -t - diagnosis['nginx'] = check_output("nginx -t").strip().split("\n") + try: + diagnosis['nginx'] = check_output("nginx -t").strip().split("\n") + except Exception as e: + import traceback + traceback.print_exc() + logger.warning("Unable to check 'nginx -t', exception: %s" % e) # Services status services = service_status() From a58449ad51ff8aae85d6069033a99488894fccbb Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 17 Jan 2018 18:59:48 +0100 Subject: [PATCH 095/132] [fix] wrap 'meltdown' diagnosis call in try/catch --- src/yunohost/tools.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index c209254b8..d6d288108 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -637,12 +637,17 @@ def tools_diagnosis(auth, private=False): diagnosis['private']['regen_conf'] = service_regen_conf(with_diff=True, dry_run=True) - diagnosis['security'] = { - "CVE-2017-5754": { - "name": "meltdown", - "vulnerable": _check_if_vulnerable_to_meltdown(), + try: + diagnosis['security'] = { + "CVE-2017-5754": { + "name": "meltdown", + "vulnerable": _check_if_vulnerable_to_meltdown(), + } } - } + except Exception as e: + import traceback + traceback.print_exc() + logger.warning("Unable to check for meltdown vulnerability: %s" % e) return diagnosis From 177914ee00e1e85cdea5c8afcee191c9b6f823b0 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 17 Jan 2018 19:00:24 +0100 Subject: [PATCH 096/132] [fix] useful error messages for meltdown check --- src/yunohost/tools.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index d6d288108..dfc97222e 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -663,11 +663,22 @@ def _check_if_vulnerable_to_meltdown(): # example output from the script: # [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}] try: - CVEs = json.loads(check_output("bash %s --batch json --variant 3" % SCRIPT_PATH)) + call = subprocess.Popen("bash %s --batch json --variant 3" % + SCRIPT_PATH, shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + output, _ = call.communicate() + assert call.returncode == 0 + + CVEs = json.loads(output) assert len(CVEs) == 1 assert CVEs[0]["NAME"] == "MELTDOWN" - except: - raise Exception("Something wrong happened when trying to diagnose Meltdown vunerability.") + except Exception as e: + import traceback + traceback.print_exc() + logger.warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e) + raise Exception("Command output for failed meltdown check: '%s'" % output) return CVEs[0]["VULNERABLE"] From 54c9b8be10a9ed99e4492260fb340277cfa99c1c Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 17 Jan 2018 19:00:35 +0100 Subject: [PATCH 097/132] [fix] we need absolute path for meltdown check --- src/yunohost/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index dfc97222e..959da2df7 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -657,7 +657,8 @@ def _check_if_vulnerable_to_meltdown(): # script taken from https://github.com/speed47/spectre-meltdown-checker # script commit id is store directly in the script - SCRIPT_PATH = "./vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh" + file_dir = os.path.split(__file__)[0] + SCRIPT_PATH = os.path.join(file_dir, "./vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh") # '--variant 3' corresponds to Meltdown # example output from the script: From 6e4b2673742a796d86731ab34b4a5d780db9fbca Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 17 Jan 2018 13:12:12 -0500 Subject: [PATCH 098/132] Update changelog for 2.7.6.1 release --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index 22188585a..eb1666fe5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +yunohost (2.7.6.1) testing; urgency=low + + * [fix] Fix Meltdown diagnosis + * [fix] Improve error handling of 'nginx -t' and Metdown diagnosis + + -- Alexandre Aubin Wed, 17 Jan 2018 13:11:02 -0500 + yunohost (2.7.6) testing; urgency=low Major changes: From 6f2acb7eb6a9ebb54837bac017eecd512d49a32d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Jan 2018 21:10:08 +0100 Subject: [PATCH 099/132] Wording --- data/templates/nginx/plain/yunohost_admin.conf.inc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf.inc b/data/templates/nginx/plain/yunohost_admin.conf.inc index f516a9d4b..2ab72293d 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf.inc +++ b/data/templates/nginx/plain/yunohost_admin.conf.inc @@ -1,5 +1,6 @@ -# Fix the nginx traversal weak ( #1037 ) +# Avoid the nginx path/alias traversal weakness ( #1037 ) rewrite ^/yunohost/admin$ /yunohost/admin/ permanent; + location /yunohost/admin/ { alias /usr/share/yunohost/admin/; default_type text/html; From 324ab9494b9b759375980121e03f7ea91a75f2fb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Jan 2018 17:45:34 -0500 Subject: [PATCH 100/132] Update changelog for 2.7.7 release --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index eb1666fe5..599bf9cc8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (2.7.7) stable; urgency=low + + (Bumping version number for stable release) + + -- Alexandre Aubin Thu, 18 Jan 2018 17:45:21 -0500 + yunohost (2.7.6.1) testing; urgency=low * [fix] Fix Meltdown diagnosis From 8a51b46d4750cad047f8e98e6cd141db1959c78c Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 22 Jan 2018 19:06:37 +0100 Subject: [PATCH 101/132] [Fix] Fix ynh_restore_upgradebackup --- data/helpers.d/utils | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 030fddcde..3dc0c9bfc 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -64,7 +64,7 @@ ynh_backup_before_upgrade () { echo "This app doesn't have any backup script." >&2 return fi - local backup_number=1 + backup_number=1 local old_backup_number=2 local app_bck=${app//_/-} # Replace all '_' by '-' From 37a97cf9b5b6405a28c062774889d1976adcd679 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Wed, 24 Jan 2018 09:18:01 +0000 Subject: [PATCH 102/132] [i18n] Translated using Weblate (French) Currently translated at 100.0% (366 of 366 strings) --- locales/fr.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 00b98840c..dd9c40b34 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -370,5 +370,13 @@ "dyndns_domain_not_provided": "Le fournisseur Dyndns {provider:s} ne peut pas fournir le domaine {domain:s}.", "app_make_default_location_already_used": "Impossible de configurer l'app '{app}' par défaut pour le domaine {domain}, déjà utilisé par l'autre app '{other_app}'", "app_upgrade_app_name": "Mise à jour de l'application {app}...", - "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque / clef USB." + "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque / clef USB.", + "migrate_tsig_end": "La migration à hmac-sha512 est terminée", + "migrate_tsig_failed": "La migration du domaine dyndns {domain} à hmac-sha512 a échoué, annulation des modifications. Erreur : {error_code} - {error}", + "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine « {domain} », lancement de la migration vers hmac-sha512 qui est plus sécurisé", + "migrate_tsig_wait": "Attendons 3 minutes pour que le serveur dyndns prenne en compte la nouvelle clef…", + "migrate_tsig_wait_2": "2 minutes…", + "migrate_tsig_wait_3": "1 minute…", + "migrate_tsig_wait_4": "30 secondes…", + "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine dyndns, donc aucune migration n’est nécessaire !" } From 1c752db22b74b4bb1771e78f9159e8abd8ce909f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 24 Jan 2018 12:16:25 -0500 Subject: [PATCH 103/132] Update changelog for 2.7.8 release --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index 599bf9cc8..8adb7004a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +yunohost (2.7.8) testing; urgency=low + + * [fix] Use HMAC-SHA512 for DynDNS TSIG + * [fix] Fix ynh_restore_upgradebackup + * [i18n] Improve french translation + + Thanks to all contributors (Bram, Maniack, jibec, Aleks) ! <3 + + -- Alexandre Aubin Wed, 24 Jan 2018 12:15:12 -0500 + yunohost (2.7.7) stable; urgency=low (Bumping version number for stable release) From 4dfb1ee77703d579bc6070381b31b5fad601ece5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 26 Jan 2018 03:19:22 +0100 Subject: [PATCH 104/132] Move get_public_ip to an 'util' file --- src/yunohost/domain.py | 38 --------------------------- src/yunohost/utils/network.py | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 src/yunohost/utils/network.py diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 727a63df3..19a5e55a7 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -30,8 +30,6 @@ import yaml import errno import requests -from urllib import urlopen - from moulinette import m18n, msettings from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger @@ -260,42 +258,6 @@ def domain_url_available(auth, domain, path): return available -def get_public_ip(protocol=4): - """Retrieve the public IP address from ip.yunohost.org""" - if protocol == 4: - url = 'https://ip.yunohost.org' - elif protocol == 6: - url = 'https://ip6.yunohost.org' - else: - raise ValueError("invalid protocol version") - - try: - return urlopen(url).read().strip() - except IOError: - logger.debug('cannot retrieve public IPv%d' % protocol, exc_info=1) - raise MoulinetteError(errno.ENETUNREACH, - m18n.n('no_internet_connection')) - -def get_public_ips(): - """ - Retrieve the public IPv4 and v6 from ip. and ip6.yunohost.org - - Returns a 2-tuple (ipv4, ipv6). ipv4 or ipv6 can be None if they were not - found. - """ - - try: - ipv4 = get_public_ip() - except: - ipv4 = None - try: - ipv6 = get_public_ip(6) - except: - ipv6 = None - - return (ipv4, ipv6) - - def _get_maindomain(): with open('/etc/yunohost/current_host', 'r') as f: maindomain = f.readline().rstrip() diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py new file mode 100644 index 000000000..902d99278 --- /dev/null +++ b/src/yunohost/utils/network.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2015 YUNOHOST.ORG + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" +import logging +from urllib import urlopen + +logger = logging.getLogger('yunohost.utils.network') + +def get_public_ip(protocol=4): + """Retrieve the public IP address from ip.yunohost.org""" + + if protocol == 4: + url = 'https://ip.yunohost.org' + elif protocol == 6: + url = 'https://ip6.yunohost.org' + else: + raise ValueError("invalid protocol version") + + try: + return urlopen(url).read().strip() + except IOError: + return None + + +def get_public_ips(): + + ipv4 = get_public_ip() + ipv6 = get_public_ip(6) + + return (ipv4, ipv6) + From e80f3a5a55044346b1cdd352752c5f6d77105ca3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 26 Jan 2018 03:39:35 +0100 Subject: [PATCH 105/132] Fix imports and get_public_ip usage --- src/yunohost/certificate.py | 14 +++++--------- src/yunohost/domain.py | 12 +++--------- src/yunohost/dyndns.py | 3 ++- src/yunohost/monitor.py | 8 +++----- src/yunohost/tools.py | 14 +++++--------- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index b6fb0e275..310c5d131 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -44,6 +44,7 @@ from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger import yunohost.domain +from yunohost.utils.network import get_public_ip from moulinette import m18n from yunohost.app import app_ssowatconf @@ -809,7 +810,7 @@ def _backup_current_cert(domain): def _check_domain_is_ready_for_ACME(domain): - public_ip = yunohost.domain.get_public_ip() + public_ip = get_public_ip() # Check if IP from DNS matches public IP if not _dns_ip_match_public_ip(public_ip, domain): @@ -856,14 +857,9 @@ def _regen_dnsmasq_if_needed(): """ Update the dnsmasq conf if some IPs are not up to date... """ - try: - ipv4 = yunohost.domain.get_public_ip() - except: - ipv4 = None - try: - ipv6 = yunohost.domain.get_public_ip(6) - except: - ipv6 = None + + ipv4 = get_public_ip() + ipv6 = get_public_ip(6) do_regen = False diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 19a5e55a7..026c4da36 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -37,6 +37,7 @@ from moulinette.utils.log import getActionLogger import yunohost.certificate from yunohost.service import service_regen_conf +from yunohost.utils.network import get_public_ip logger = getActionLogger('yunohost.domain') @@ -318,15 +319,8 @@ def _build_dns_conf(domain, ttl=3600): } """ - try: - ipv4 = get_public_ip() - except: - ipv4 = None - - try: - ipv6 = get_public_ip(6) - except: - ipv6 = None + ipv4 = get_public_ip() + ipv6 = get_public_ip(6) basic = [] diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 851d04f45..8c4efd777 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -39,7 +39,8 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, write_to_file, rm from moulinette.utils.network import download_json -from yunohost.domain import get_public_ips, _get_maindomain, _build_dns_conf +from yunohost.domain import _get_maindomain, _build_dns_conf +from yunohost.utils.network import get_public_ips logger = getActionLogger('yunohost.dyndns') diff --git a/src/yunohost/monitor.py b/src/yunohost/monitor.py index d99ac1688..ed13d532d 100644 --- a/src/yunohost/monitor.py +++ b/src/yunohost/monitor.py @@ -41,7 +41,8 @@ from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from yunohost.domain import get_public_ip, _get_maindomain +from yunohost.utils.network import get_public_ip +from yunohost.domain import _get_maindomain logger = getActionLogger('yunohost.monitor') @@ -210,10 +211,7 @@ def monitor_network(units=None, human_readable=False): else: logger.debug('interface name %s was not found', iname) elif u == 'infos': - try: - p_ipv4 = get_public_ip() - except: - p_ipv4 = 'unknown' + p_ipv4 = get_public_ip() or 'unknown' l_ip = 'unknown' for name, addrs in devices.items(): diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index b997961be..381cd07e0 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -45,12 +45,13 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_json, write_to_json from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron -from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain +from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp from yunohost.service import service_status, service_regen_conf, service_log, service_start, service_enable from yunohost.monitor import monitor_disk, monitor_system from yunohost.utils.packages import ynh_packages_version +from yunohost.utils.network import get_public_ip # FIXME this is a duplicate from apps.py APPS_SETTING_PATH = '/etc/yunohost/apps/' @@ -621,16 +622,11 @@ def tools_diagnosis(auth, private=False): # Private data if private: diagnosis['private'] = OrderedDict() + # Public IP diagnosis['private']['public_ip'] = {} - try: - diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4) - except MoulinetteError as e: - pass - try: - diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6) - except MoulinetteError as e: - pass + diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4) + diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6) # Domains diagnosis['private']['domains'] = domain_list(auth)['domains'] From f5b5edb3bb598ff401a58714a48a1c8647b4ef9c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 26 Jan 2018 03:41:34 +0100 Subject: [PATCH 106/132] This get_public_ips isn't really relevant anymore --- src/yunohost/dyndns.py | 5 +++-- src/yunohost/utils/network.py | 8 -------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 8c4efd777..ec3bf88c8 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -40,7 +40,7 @@ from moulinette.utils.filesystem import read_file, write_to_file, rm from moulinette.utils.network import download_json from yunohost.domain import _get_maindomain, _build_dns_conf -from yunohost.utils.network import get_public_ips +from yunohost.utils.network import get_public_ip logger = getActionLogger('yunohost.dyndns') @@ -194,7 +194,8 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, old_ipv6 = read_file(OLD_IPV6_FILE).rstrip() # Get current IPv4 and IPv6 - (ipv4_, ipv6_) = get_public_ips() + ipv4_ = get_public_ip() + ipv6_ = get_public_ip(6) if ipv4 is None: ipv4 = ipv4_ diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 902d99278..9cdbc676c 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -38,11 +38,3 @@ def get_public_ip(protocol=4): except IOError: return None - -def get_public_ips(): - - ipv4 = get_public_ip() - ipv6 = get_public_ip(6) - - return (ipv4, ipv6) - From 9c4ddcca39d9d6d92bd5f9a23978337e48d0a4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 26 Jan 2018 21:17:18 +0100 Subject: [PATCH 107/132] Add service name as arg (optionnal) --- data/helpers.d/backend | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 8fef412cf..c04f2230b 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -64,6 +64,10 @@ ynh_remove_logrotate () { # Create a dedicated systemd config # +# usage: ynh_add_systemd_config [Service name] [Source file] +# | arg: Service name +# | arg: Systemd source file (for example appname.service) +# # This will use a template in ../conf/systemd.service # and will replace the following keywords with # global variables that should be defined before calling @@ -74,9 +78,11 @@ ynh_remove_logrotate () { # # usage: ynh_add_systemd_config ynh_add_systemd_config () { - finalsystemdconf="/etc/systemd/system/$app.service" + local service_name="${1:-$app}" + + finalsystemdconf="/etc/systemd/system/$service_name.service" ynh_backup_if_checksum_is_different "$finalsystemdconf" - sudo cp ../conf/systemd.service "$finalsystemdconf" + sudo cp ../conf/${2:-systemd.service} "$finalsystemdconf" # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty @@ -89,19 +95,25 @@ ynh_add_systemd_config () { ynh_store_file_checksum "$finalsystemdconf" sudo chown root: "$finalsystemdconf" - sudo systemctl enable $app + sudo systemctl enable $service_name sudo systemctl daemon-reload } # Remove the dedicated systemd config # +# usage: ynh_remove_systemd_config [Service name] +# | arg: Service name +# # usage: ynh_remove_systemd_config ynh_remove_systemd_config () { - local finalsystemdconf="/etc/systemd/system/$app.service" + local service_name="${1:-$app}" + + local finalsystemdconf="/etc/systemd/system/$service_name.service" if [ -e "$finalsystemdconf" ]; then - sudo systemctl stop $app - sudo systemctl disable $app + sudo systemctl stop $service_name + sudo systemctl disable $service_name ynh_secure_remove "$finalsystemdconf" + sudo systemctl daemon-reload fi } From a975e5e6843c97dbacdc7401fa38d4d1a72922e7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 26 Jan 2018 22:21:14 +0100 Subject: [PATCH 108/132] Improve comment / helper description --- data/helpers.d/backend | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index c04f2230b..33d87db2c 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -64,13 +64,13 @@ ynh_remove_logrotate () { # Create a dedicated systemd config # -# usage: ynh_add_systemd_config [Service name] [Source file] -# | arg: Service name -# | arg: Systemd source file (for example appname.service) +# usage: ynh_add_systemd_config [Service name] [Template name] +# | arg: Service name (optionnal, $app by default) +# | arg: Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) # -# This will use a template in ../conf/systemd.service -# and will replace the following keywords with -# global variables that should be defined before calling +# This will use the template ../conf/.service +# to generate a systemd config, by replacing the following keywords +# with global variables that should be defined before calling # this helper : # # __APP__ by $app @@ -102,7 +102,7 @@ ynh_add_systemd_config () { # Remove the dedicated systemd config # # usage: ynh_remove_systemd_config [Service name] -# | arg: Service name +# | arg: Service name (optionnal, $app by default) # # usage: ynh_remove_systemd_config ynh_remove_systemd_config () { From a1831ce0f8f0e18d0b1b831509c9727895f87e08 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 26 Jan 2018 22:40:33 +0100 Subject: [PATCH 109/132] Manage etckeeper.conf to make etckeeper quiet --- data/hooks/conf_regen/01-yunohost | 3 ++ data/templates/yunohost/etckeeper.conf | 43 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 data/templates/yunohost/etckeeper.conf diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index f8bef0614..e1daa7c3d 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -53,6 +53,9 @@ do_pre_regen() { else sudo cp services.yml /etc/yunohost/services.yml fi + + mkdir -p "$pending_dir"/etc/etckeeper/ + cp etckeeper.conf "$pending_dir"/etc/etckeeper/ } _update_services() { diff --git a/data/templates/yunohost/etckeeper.conf b/data/templates/yunohost/etckeeper.conf new file mode 100644 index 000000000..2d11c3dc6 --- /dev/null +++ b/data/templates/yunohost/etckeeper.conf @@ -0,0 +1,43 @@ +# The VCS to use. +#VCS="hg" +VCS="git" +#VCS="bzr" +#VCS="darcs" + +# Options passed to git commit when run by etckeeper. +GIT_COMMIT_OPTIONS="--quiet" + +# Options passed to hg commit when run by etckeeper. +HG_COMMIT_OPTIONS="" + +# Options passed to bzr commit when run by etckeeper. +BZR_COMMIT_OPTIONS="" + +# Options passed to darcs record when run by etckeeper. +DARCS_COMMIT_OPTIONS="-a" + +# Uncomment to avoid etckeeper committing existing changes +# to /etc automatically once per day. +#AVOID_DAILY_AUTOCOMMITS=1 + +# Uncomment the following to avoid special file warning +# (the option is enabled automatically by cronjob regardless). +#AVOID_SPECIAL_FILE_WARNING=1 + +# Uncomment to avoid etckeeper committing existing changes to +# /etc before installation. It will cancel the installation, +# so you can commit the changes by hand. +#AVOID_COMMIT_BEFORE_INSTALL=1 + +# The high-level package manager that's being used. +# (apt, pacman-g2, yum, zypper etc) +HIGHLEVEL_PACKAGE_MANAGER=apt + +# The low-level package manager that's being used. +# (dpkg, rpm, pacman, pacman-g2, etc) +LOWLEVEL_PACKAGE_MANAGER=dpkg + +# To push each commit to a remote, put the name of the remote here. +# (eg, "origin" for git). Space-separated lists of multiple remotes +# also work (eg, "origin gitlab github" for git). +PUSH_REMOTE="" From 9511e01f5a8f577fed65c867b5dfe7c40eddb4c2 Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Sat, 27 Jan 2018 16:16:42 +0100 Subject: [PATCH 110/132] Add access to conf folder when executing change_url script --- src/yunohost/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 9ccc0886d..58c397542 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -485,6 +485,12 @@ def app_change_url(auth, app, domain, path): shutil.copytree(os.path.join(APPS_SETTING_PATH, app, "scripts"), os.path.join(APP_TMP_FOLDER, "scripts")) + if os.path.exists(os.path.join(APP_TMP_FOLDER, "conf")): + shutil.rmtree(os.path.join(APP_TMP_FOLDER, "conf")) + + shutil.copytree(os.path.join(APPS_SETTING_PATH, app, "conf"), + os.path.join(APP_TMP_FOLDER, "conf")) + # Execute App change_url script os.system('chown -R admin: %s' % INSTALL_TMP) os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts"))) From 1056977dc6433da6ee41d7187706edbc090030de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 27 Jan 2018 20:03:42 +0100 Subject: [PATCH 111/132] Microdecision: those weren't space, wtf --- data/helpers.d/mysql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 56741ec0e..8aa81a1fe 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -8,7 +8,7 @@ MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql # usage: ynh_mysql_connect_as user pwd [db] # | arg: user - the user name to connect as # | arg: pwd - the user password -# | arg: db - the database to connect to +# | arg: db - the database to connect to ynh_mysql_connect_as() { mysql -u "$1" --password="$2" -B "${3:-}" } @@ -17,7 +17,7 @@ ynh_mysql_connect_as() { # # usage: ynh_mysql_execute_as_root sql [db] # | arg: sql - the SQL command to execute -# | arg: db - the database to connect to +# | arg: db - the database to connect to ynh_mysql_execute_as_root() { ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ "${2:-}" <<< "$1" @@ -27,7 +27,7 @@ ynh_mysql_execute_as_root() { # # usage: ynh_mysql_execute_file_as_root file [db] # | arg: file - the file containing SQL commands -# | arg: db - the database to connect to +# | arg: db - the database to connect to ynh_mysql_execute_file_as_root() { ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ "${2:-}" < "$1" From e15ccdf0b749e89ebec2e659fd6af626f80105e2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 27 Jan 2018 20:06:16 +0100 Subject: [PATCH 112/132] Microdecison: more weird spaces --- data/helpers.d/user | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index 8e214691c..47e6eb88a 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -1,4 +1,4 @@ -# Check if a YunoHost user exists +# Check if a YunoHost user exists # # example: ynh_user_exists 'toto' || exit 1 # @@ -31,7 +31,7 @@ ynh_user_list() { | awk '/^##username$/{getline; print}' } -# Check if a user exists on the system +# Check if a user exists on the system # # usage: ynh_system_user_exists username # | arg: username - the username to check From 50bd20fce9d0a716114d0165d222f9a90e8c3f0f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lescher Date: Tue, 30 Jan 2018 17:16:46 +0100 Subject: [PATCH 113/132] [Fix] Stronger match for acme-challenge nginx location If an application (for instance roundcube) installed at the root of a subdomain has the following nginx configuration: location ~ ^/(.+/|)\. { deny all; } acme-challenge matching location: location '/.well-known/acme-challenge' { default_type "text/plain"; alias /tmp/acme-challenge-public/; } will not be used. This fix prevents further matching by regular expressions. Co-authored-by: Tomo59 --- src/yunohost/certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index b6fb0e275..2394f26d5 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -463,7 +463,7 @@ def _configure_for_acme_challenge(auth, domain): nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder nginx_configuration = ''' -location '/.well-known/acme-challenge' +location ^~ '/.well-known/acme-challenge' { default_type "text/plain"; alias %s; From 61a60d0f5c6c0e95c7484cb12dbd72e13ab86db1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 30 Jan 2018 17:43:01 +0000 Subject: [PATCH 114/132] Update changelog for 2.7.9 release --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 8adb7004a..4ebe4e14c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (2.7.9) stable; urgency=low + + (Bumping version number for stable release) + + -- Alexandre Aubin Tue, 30 Jan 2018 17:42:00 +0000 + yunohost (2.7.8) testing; urgency=low * [fix] Use HMAC-SHA512 for DynDNS TSIG From 8e92bb832867abcd3ebd6d5872bb99c2f8653835 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 4 Feb 2018 19:26:49 +0100 Subject: [PATCH 115/132] [enh] --verbose for backup during upgrade --- data/helpers.d/utils | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 3dc0c9bfc..288ac393c 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -44,7 +44,7 @@ ynh_restore_upgradebackup () { # Remove the application then restore it sudo yunohost app remove $app # Restore the backup - sudo yunohost backup restore --ignore-system $app_bck-pre-upgrade$backup_number --apps $app --force + sudo yunohost backup restore --ignore-system $app_bck-pre-upgrade$backup_number --apps $app --force --verbose ynh_die "The app was restored to the way it was before the failed upgrade." fi } @@ -77,7 +77,7 @@ ynh_backup_before_upgrade () { fi # Create backup - sudo yunohost backup create --ignore-system --apps $app --name $app_bck-pre-upgrade$backup_number + sudo yunohost backup create --ignore-system --apps $app --name $app_bck-pre-upgrade$backup_number --verbose if [ "$?" -eq 0 ] then # If the backup succeeded, remove the previous backup From e5a41be5182d87c5e9ab6bcf3ddd3d34dc4426f7 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 7 Feb 2018 21:42:27 +0100 Subject: [PATCH 116/132] [fix] backslash needs to be double escaped --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 8f995c9bf..66fa93f45 100644 --- a/locales/en.json +++ b/locales/en.json @@ -14,7 +14,7 @@ "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", - "app_checkurl_is_deprecated": "Packagers /!\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !", + "app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !", "app_extraction_failed": "Unable to extract installation files", "app_id_invalid": "Invalid app id", "app_incompatible": "The app {app} is incompatible with your YunoHost version", From 52a54c5ab1ce096005f086739efe600eef545d32 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 7 Feb 2018 21:46:38 +0100 Subject: [PATCH 117/132] [mod] we are in 2017 --- src/yunohost/utils/network.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 9cdbc676c..e22d1644d 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -2,7 +2,7 @@ """ License - Copyright (C) 2015 YUNOHOST.ORG + Copyright (C) 2017 YUNOHOST.ORG This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -37,4 +37,3 @@ def get_public_ip(protocol=4): return urlopen(url).read().strip() except IOError: return None - From 89848a9b57a8ae76810132df6db28ccab7ec6f36 Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Wed, 7 Feb 2018 20:33:49 +0000 Subject: [PATCH 118/132] Added translation using Weblate (Arabic) --- locales/ar.json | 369 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 locales/ar.json diff --git a/locales/ar.json b/locales/ar.json new file mode 100644 index 000000000..8f995c9bf --- /dev/null +++ b/locales/ar.json @@ -0,0 +1,369 @@ +{ + "action_invalid": "Invalid action '{action:s}'", + "admin_password": "Administration password", + "admin_password_change_failed": "Unable to change password", + "admin_password_changed": "The administration password has been changed", + "app_already_installed": "{app:s} is already installed", + "app_already_installed_cant_change_url": "This app is already installed. The url cannot be changed just by this function. Look into `app changeurl` if it's available.", + "app_already_up_to_date": "{app:s} is already up to date", + "app_argument_choice_invalid": "Invalid choice for argument '{name:s}', it must be one of {choices:s}", + "app_argument_invalid": "Invalid value for argument '{name:s}': {error:s}", + "app_argument_required": "Argument '{name:s}' is required", + "app_change_no_change_url_script": "The application {app_name:s} doesn't support changing it's URL yet, you might need to upgrade it.", + "app_change_url_failed_nginx_reload": "Failed to reload nginx. Here is the output of 'nginx -t':\n{nginx_errors:s}", + "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", + "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", + "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", + "app_checkurl_is_deprecated": "Packagers /!\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !", + "app_extraction_failed": "Unable to extract installation files", + "app_id_invalid": "Invalid app id", + "app_incompatible": "The app {app} is incompatible with your YunoHost version", + "app_install_files_invalid": "Invalid installation files", + "app_location_already_used": "The app '{app}' is already installed on that location ({path})", + "app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain {domain} is already used by the other app '{other_app}'", + "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", + "app_location_unavailable": "This url is not available or conflicts with an already installed app", + "app_manifest_invalid": "Invalid app manifest: {error}", + "app_no_upgrade": "No app to upgrade", + "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", + "app_not_installed": "{app:s} is not installed", + "app_not_properly_removed": "{app:s} has not been properly removed", + "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", + "app_removed": "{app:s} has been removed", + "app_requirements_checking": "Checking required packages for {app}...", + "app_requirements_failed": "Unable to meet requirements for {app}: {error}", + "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", + "app_sources_fetch_failed": "Unable to fetch sources files", + "app_unknown": "Unknown app", + "app_unsupported_remote_type": "Unsupported remote type used for the app", + "app_upgrade_app_name": "Upgrading app {app}...", + "app_upgrade_failed": "Unable to upgrade {app:s}", + "app_upgrade_some_app_failed": "Unable to upgrade some applications", + "app_upgraded": "{app:s} has been upgraded", + "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", + "appslist_could_not_migrate": "Could not migrate app list {appslist:s} ! Unable to parse the url... The old cron job has been kept in {bkp_file:s}.", + "appslist_fetched": "The application list {appslist:s} has been fetched", + "appslist_migrating": "Migrating application list {appslist:s} ...", + "appslist_name_already_tracked": "There is already a registered application list with name {name:s}.", + "appslist_removed": "The application list {appslist:s} has been removed", + "appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid", + "appslist_retrieve_error": "Unable to retrieve the remote application list {appslist:s}: {error:s}", + "appslist_unknown": "Application list {appslist:s} unknown.", + "appslist_url_already_tracked": "There is already a registered application list with url {url:s}.", + "ask_current_admin_password": "Current administration password", + "ask_email": "Email address", + "ask_firstname": "First name", + "ask_lastname": "Last name", + "ask_list_to_remove": "List to remove", + "ask_main_domain": "Main domain", + "ask_new_admin_password": "New administration password", + "ask_password": "Password", + "ask_path": "Path", + "backup_abstract_method": "This backup method hasn't yet been implemented", + "backup_action_required": "You must specify something to save", + "backup_app_failed": "Unable to back up the app '{app:s}'", + "backup_applying_method_borg": "Sending all files to backup into borg-backup repository...", + "backup_applying_method_copy": "Copying all files to backup...", + "backup_applying_method_custom": "Calling the custom backup method '{method:s}'...", + "backup_applying_method_tar": "Creating the backup tar archive...", + "backup_archive_app_not_found": "App '{app:s}' not found in the backup archive", + "backup_archive_broken_link": "Unable to access backup archive (broken link to {path:s})", + "backup_archive_mount_failed": "Mounting the backup archive failed", + "backup_archive_name_exists": "The backup's archive name already exists", + "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", + "backup_archive_open_failed": "Unable to open the backup archive", + "backup_archive_system_part_not_available": "System part '{part:s}' not available in this backup", + "backup_archive_writing_error": "Unable to add files to backup into the compressed archive", + "backup_ask_for_copying_if_needed": "Some files couldn't be prepared to be backuped using the method that avoid to temporarily waste space on the system. To perform the backup, {size:s}MB should be used temporarily. Do you agree?", + "backup_borg_not_implemented": "Borg backup method is not yet implemented", + "backup_cant_mount_uncompress_archive": "Unable to mount in readonly mode the uncompress archive directory", + "backup_cleaning_failed": "Unable to clean-up the temporary backup directory", + "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", + "backup_couldnt_bind": "Couldn't bind {src:s} to {dest:s}.", + "backup_created": "Backup created", + "backup_creating_archive": "Creating the backup archive...", + "backup_creation_failed": "Backup creation failed", + "backup_csv_addition_failed": "Unable to add files to backup into the CSV file", + "backup_csv_creation_failed": "Unable to create the CSV file needed for future restore operations", + "backup_custom_backup_error": "Custom backup method failure on 'backup' step", + "backup_custom_mount_error": "Custom backup method failure on 'mount' step", + "backup_custom_need_mount_error": "Custom backup method failure on 'need_mount' step", + "backup_delete_error": "Unable to delete '{path:s}'", + "backup_deleted": "The backup has been deleted", + "backup_extracting_archive": "Extracting the backup archive...", + "backup_hook_unknown": "Backup hook '{hook:s}' unknown", + "backup_invalid_archive": "Invalid backup archive", + "backup_method_borg_finished": "Backup into borg finished", + "backup_method_copy_finished": "Backup copy finished", + "backup_method_custom_finished": "Custom backup method '{method:s}' finished", + "backup_method_tar_finished": "Backup tar archive created", + "backup_no_uncompress_archive_dir": "Uncompress archive directory doesn't exist", + "backup_nothings_done": "There is nothing to save", + "backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", + "backup_output_directory_not_empty": "The output directory is not empty", + "backup_output_directory_required": "You must provide an output directory for the backup", + "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", + "backup_running_app_script": "Running backup script of app '{app:s}'...", + "backup_running_hooks": "Running backup hooks...", + "backup_system_part_failed": "Unable to backup the '{part:s}' system part", + "backup_unable_to_organize_files": "Unable to organize files in the archive with the quick method", + "backup_with_no_backup_script_for_app": "App {app:s} has no backup script. Ignoring.", + "backup_with_no_restore_script_for_app": "App {app:s} has no restore script, you won't be able to automatically restore the backup of this app.", + "certmanager_acme_not_configured_for_domain": "Certificate for domain {domain:s} does not appear to be correctly installed. Please run cert-install for this domain first.", + "certmanager_attempt_to_renew_nonLE_cert": "The certificate for domain {domain:s} is not issued by Let's Encrypt. Cannot renew it automatically!", + "certmanager_attempt_to_renew_valid_cert": "The certificate for domain {domain:s} is not about to expire! Use --force to bypass", + "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", + "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}", + "certmanager_cert_install_success": "Successfully installed Let's Encrypt certificate for domain {domain:s}!", + "certmanager_cert_install_success_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", + "certmanager_cert_renew_success": "Successfully renewed Let's Encrypt certificate for domain {domain:s}!", + "certmanager_cert_signing_failed": "Signing the new certificate failed", + "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow...", + "certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge: the nginx configuration file {filepath:s} is conflicting and should be removed first", + "certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted - please try again later.", + "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use --force)", + "certmanager_domain_dns_ip_differs_from_public_ip": "The DNS 'A' record for domain {domain:s} is different from this server IP. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use --no-checks to disable those checks.)", + "certmanager_domain_http_not_working": "It seems that the domain {domain:s} cannot be accessed through HTTP. Please check your DNS and nginx configuration is okay", + "certmanager_domain_not_resolved_locally": "The domain {domain:s} cannot be resolved from inside your Yunohost server. This might happen if you recently modified your DNS record. If so, please wait a few hours for it to propagate. If the issue persists, consider adding {domain:s} to /etc/hosts. (If you know what you are doing, use --no-checks to disable those checks.)", + "certmanager_domain_unknown": "Unknown domain {domain:s}", + "certmanager_error_no_A_record": "No DNS 'A' record found for {domain:s}. You need to make your domain name point to your machine to be able to install a Let's Encrypt certificate! (If you know what you are doing, use --no-checks to disable those checks.)", + "certmanager_hit_rate_limit": "Too many certificates already issued for exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details", + "certmanager_http_check_timeout": "Timed out when server tried to contact itself through HTTP using public IP address (domain {domain:s} with ip {ip:s}). You may be experiencing hairpinning issue or the firewall/router ahead of your server is misconfigured.", + "certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})", + "certmanager_old_letsencrypt_app_detected": "\nYunohost detected that the 'letsencrypt' app is installed, which conflits with the new built-in certificate management features in Yunohost. If you wish to use the new built-in features, please run the following commands to migrate your installation:\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B.: this will attempt to re-install certificates for all domains with a Let's Encrypt certificate or self-signed certificate", + "certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})", + "certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})", + "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", + "custom_appslist_name_required": "You must provide a name for your custom app list", + "diagnosis_debian_version_error": "Can't retrieve the Debian version: {error}", + "diagnosis_kernel_version_error": "Can't retrieve kernel version: {error}", + "diagnosis_monitor_disk_error": "Can't monitor disks: {error}", + "diagnosis_monitor_network_error": "Can't monitor network: {error}", + "diagnosis_monitor_system_error": "Can't monitor system: {error}", + "diagnosis_no_apps": "No installed application", + "dnsmasq_isnt_installed": "dnsmasq does not seem to be installed, please run 'apt-get remove bind9 && apt-get install dnsmasq'", + "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", + "domain_cert_gen_failed": "Unable to generate certificate", + "domain_created": "The domain has been created", + "domain_creation_failed": "Unable to create domain", + "domain_deleted": "The domain has been deleted", + "domain_deletion_failed": "Unable to delete domain", + "domain_dns_conf_is_just_a_recommendation": "This command shows you what is the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", + "domain_dyndns_already_subscribed": "You've already subscribed to a DynDNS domain", + "domain_dyndns_dynette_is_unreachable": "Unable to reach YunoHost dynette, either your YunoHost is not correctly connected to the internet or the dynette server is down. Error: {error}", + "domain_dyndns_invalid": "Invalid domain to use with DynDNS", + "domain_dyndns_root_unknown": "Unknown DynDNS root domain", + "domain_exists": "Domain already exists", + "domain_hostname_failed": "Failed to set new hostname", + "domain_uninstall_app_first": "One or more apps are installed on this domain. Please uninstall them before proceeding to domain removal", + "domain_unknown": "Unknown domain", + "domain_zone_exists": "DNS zone file already exists", + "domain_zone_not_found": "DNS zone file not found for domain {:s}", + "domains_available": "Available domains:", + "done": "Done", + "downloading": "Downloading...", + "dyndns_could_not_check_provide": "Could not check if {provider:s} can provide {domain:s}.", + "dyndns_cron_installed": "The DynDNS cron job has been installed", + "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job", + "dyndns_cron_removed": "The DynDNS cron job has been removed", + "dyndns_ip_update_failed": "Unable to update IP address on DynDNS", + "dyndns_ip_updated": "Your IP address has been updated on DynDNS", + "dyndns_key_generating": "DNS key is being generated, it may take a while...", + "dyndns_key_not_found": "DNS key not found for the domain", + "dyndns_no_domain_registered": "No domain has been registered with DynDNS", + "dyndns_registered": "The DynDNS domain has been registered", + "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", + "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", + "dyndns_unavailable": "Domain {domain:s} is not available.", + "executing_command": "Executing command '{command:s}'...", + "executing_script": "Executing script '{script:s}'...", + "extracting": "Extracting...", + "field_invalid": "Invalid field '{:s}'", + "firewall_reload_failed": "Unable to reload the firewall", + "firewall_reloaded": "The firewall has been reloaded", + "firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.", + "format_datetime_short": "%m/%d/%Y %I:%M %p", + "global_settings_bad_choice_for_enum": "Bad value for setting {setting:s}, received {received_type:s}, except {expected_type:s}", + "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, except {expected_type:s}", + "global_settings_cant_open_settings": "Failed to open settings file, reason: {reason:s}", + "global_settings_cant_serialize_settings": "Failed to serialize settings data, reason: {reason:s}", + "global_settings_cant_write_settings": "Failed to write settings file, reason: {reason:s}", + "global_settings_key_doesnt_exists": "The key '{settings_key:s}' doesn't exists in the global settings, you can see all the available keys by doing 'yunohost settings list'", + "global_settings_reset_success": "Success. Your previous settings have been backuped in {path:s}", + "global_settings_setting_example_bool": "Example boolean option", + "global_settings_setting_example_enum": "Example enum option", + "global_settings_setting_example_int": "Example int option", + "global_settings_setting_example_string": "Example string option", + "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json", + "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", + "hook_exec_failed": "Script execution failed: {path:s}", + "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", + "hook_list_by_invalid": "Invalid property to list hook by", + "hook_name_unknown": "Unknown hook name '{name:s}'", + "installation_complete": "Installation complete", + "installation_failed": "Installation failed", + "invalid_url_format": "Invalid URL format", + "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", + "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", + "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user", + "ldap_initialized": "LDAP has been initialized", + "license_undefined": "undefined", + "mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'", + "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", + "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", + "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", + "maindomain_change_failed": "Unable to change the main domain", + "maindomain_changed": "The main domain has been changed", + "migrate_tsig_end": "Migration to hmac-sha512 finished", + "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", + "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", + "migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account...", + "migrate_tsig_wait_2": "2min...", + "migrate_tsig_wait_3": "1min...", + "migrate_tsig_wait_4": "30 secondes...", + "migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed !", + "migrations_backward": "Migrating backward.", + "migrations_bad_value_for_target": "Invalide number for target argument, available migrations numbers are 0 or {}", + "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", + "migrations_current_target": "Migration target is {}", + "migrations_error_failed_to_load_migration": "ERROR: failed to load migration {number} {name}", + "migrations_forward": "Migrating forward", + "migrations_loading_migration": "Loading migration {number} {name}...", + "migrations_migration_has_failed": "Migration {number} {name} has failed with exception {exception}, aborting", + "migrations_no_migrations_to_run": "No migrations to run", + "migrations_show_currently_running_migration": "Running migration {number} {name}...", + "migrations_show_last_migration": "Last ran migration is {}", + "migrations_skip_migration": "Skipping migration {number} {name}...", + "monitor_disabled": "The server monitoring has been disabled", + "monitor_enabled": "The server monitoring has been enabled", + "monitor_glances_con_failed": "Unable to connect to Glances server", + "monitor_not_enabled": "Server monitoring is not enabled", + "monitor_period_invalid": "Invalid time period", + "monitor_stats_file_not_found": "Statistics file not found", + "monitor_stats_no_update": "No monitoring statistics to update", + "monitor_stats_period_unavailable": "No available statistics for the period", + "mountpoint_unknown": "Unknown mountpoint", + "mysql_db_creation_failed": "MySQL database creation failed", + "mysql_db_init_failed": "MySQL database init failed", + "mysql_db_initialized": "The MySQL database has been initialized", + "network_check_mx_ko": "DNS MX record is not set", + "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", + "network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked", + "new_domain_required": "You must provide the new main domain", + "no_appslist_found": "No app list found", + "no_internet_connection": "Server is not connected to the Internet", + "no_ipv6_connectivity": "IPv6 connectivity is not available", + "no_restore_script": "No restore script found for the app '{app:s}'", + "not_enough_disk_space": "Not enough free disk space on '{path:s}'", + "package_not_installed": "Package '{pkgname}' is not installed", + "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", + "package_unknown": "Unknown package '{pkgname}'", + "packages_no_upgrade": "There is no package to upgrade", + "packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later", + "packages_upgrade_failed": "Unable to upgrade all of the packages", + "path_removal_failed": "Unable to remove path {:s}", + "pattern_backup_archive_name": "Must be a valid filename with max 30 characters, and alphanumeric and -_. characters only", + "pattern_domain": "Must be a valid domain name (e.g. my-domain.org)", + "pattern_email": "Must be a valid email address (e.g. someone@domain.org)", + "pattern_firstname": "Must be a valid first name", + "pattern_lastname": "Must be a valid last name", + "pattern_listname": "Must be alphanumeric and underscore characters only", + "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to disable the quota", + "pattern_password": "Must be at least 3 characters long", + "pattern_port": "Must be a valid port number (i.e. 0-65535)", + "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", + "pattern_positive_number": "Must be a positive number", + "pattern_username": "Must be lower-case alphanumeric and underscore characters only", + "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", + "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", + "port_available": "Port {port:d} is available", + "port_unavailable": "Port {port:d} is not available", + "restore_action_required": "You must specify something to restore", + "restore_already_installed_app": "An app is already installed with the id '{app:s}'", + "restore_app_failed": "Unable to restore the app '{app:s}'", + "restore_cleaning_failed": "Unable to clean-up the temporary restoration directory", + "restore_complete": "Restore complete", + "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers:s}]", + "restore_extracting": "Extracting needed files from the archive...", + "restore_failed": "Unable to restore the system", + "restore_hook_unavailable": "Restoration script for '{part:s}' not available on your system and not in the archive either", + "restore_may_be_not_enough_disk_space": "Your system seems not to have enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", + "restore_mounting_archive": "Mounting archive into '{path:s}'", + "restore_not_enough_disk_space": "Not enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", + "restore_nothings_done": "Nothing has been restored", + "restore_removing_tmp_dir_failed": "Unable to remove an old temporary directory", + "restore_running_app_script": "Running restore script of app '{app:s}'...", + "restore_running_hooks": "Running restoration hooks...", + "restore_system_part_failed": "Unable to restore the '{part:s}' system part", + "server_shutdown": "The server will shutdown", + "server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers:s}]", + "server_reboot": "The server will reboot", + "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers:s}]", + "service_add_failed": "Unable to add service '{service:s}'", + "service_added": "The service '{service:s}' has been added", + "service_already_started": "Service '{service:s}' has already been started", + "service_already_stopped": "Service '{service:s}' has already been stopped", + "service_cmd_exec_failed": "Unable to execute command '{command:s}'", + "service_conf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", + "service_conf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", + "service_conf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by service {service} but has been kept back.", + "service_conf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated", + "service_conf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created", + "service_conf_file_remove_failed": "Unable to remove the configuration file '{conf}'", + "service_conf_file_removed": "The configuration file '{conf}' has been removed", + "service_conf_file_updated": "The configuration file '{conf}' has been updated", + "service_conf_new_managed_file": "The configuration file '{conf}' is now managed by the service {service}.", + "service_conf_up_to_date": "The configuration is already up-to-date for service '{service}'", + "service_conf_updated": "The configuration has been updated for service '{service}'", + "service_conf_would_be_updated": "The configuration would have been updated for service '{service}'", + "service_disable_failed": "Unable to disable service '{service:s}'", + "service_disabled": "The service '{service:s}' has been disabled", + "service_enable_failed": "Unable to enable service '{service:s}'", + "service_enabled": "The service '{service:s}' has been enabled", + "service_no_log": "No log to display for service '{service:s}'", + "service_regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for service '{service}'...", + "service_regenconf_failed": "Unable to regenerate the configuration for service(s): {services}", + "service_regenconf_pending_applying": "Applying pending configuration for service '{service}'...", + "service_remove_failed": "Unable to remove service '{service:s}'", + "service_removed": "The service '{service:s}' has been removed", + "service_start_failed": "Unable to start service '{service:s}'", + "service_started": "The service '{service:s}' has been started", + "service_status_failed": "Unable to determine status of service '{service:s}'", + "service_stop_failed": "Unable to stop service '{service:s}'", + "service_stopped": "The service '{service:s}' has been stopped", + "service_unknown": "Unknown service '{service:s}'", + "ssowat_conf_generated": "The SSOwat configuration has been generated", + "ssowat_conf_updated": "The SSOwat configuration has been updated", + "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", + "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", + "system_upgraded": "The system has been upgraded", + "system_username_exists": "Username already exists in the system users", + "unbackup_app": "App '{app:s}' will not be saved", + "unexpected_error": "An unexpected error occured", + "unit_unknown": "Unknown unit '{unit:s}'", + "unlimit": "No quota", + "unrestore_app": "App '{app:s}' will not be restored", + "update_cache_failed": "Unable to update APT cache", + "updating_apt_cache": "Updating the list of available packages...", + "upgrade_complete": "Upgrade complete", + "upgrading_packages": "Upgrading packages...", + "upnp_dev_not_found": "No UPnP device found", + "upnp_disabled": "UPnP has been disabled", + "upnp_enabled": "UPnP has been enabled", + "upnp_port_open_failed": "Unable to open UPnP ports", + "user_created": "The user has been created", + "user_creation_failed": "Unable to create user", + "user_deleted": "The user has been deleted", + "user_deletion_failed": "Unable to delete user", + "user_home_creation_failed": "Unable to create user home folder", + "user_info_failed": "Unable to retrieve user information", + "user_unknown": "Unknown user: {user:s}", + "user_update_failed": "Unable to update user", + "user_updated": "The user has been updated", + "yunohost_already_installed": "YunoHost is already installed", + "yunohost_ca_creation_failed": "Unable to create certificate authority", + "yunohost_ca_creation_success": "The local certification authority has been created.", + "yunohost_configured": "YunoHost has been configured", + "yunohost_installing": "Installing YunoHost...", + "yunohost_not_installed": "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'" +} From d6311b62fe29667b957f71892299cf0331d8ffb0 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 7 Feb 2018 22:04:35 +0100 Subject: [PATCH 119/132] [fix] double backslash again --- locales/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/ar.json b/locales/ar.json index 8f995c9bf..66fa93f45 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -14,7 +14,7 @@ "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", - "app_checkurl_is_deprecated": "Packagers /!\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !", + "app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !", "app_extraction_failed": "Unable to extract installation files", "app_id_invalid": "Invalid app id", "app_incompatible": "The app {app} is incompatible with your YunoHost version", From 78b87a6288bdf43442f80376a39623a9396bb1af Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Wed, 7 Feb 2018 22:09:41 +0100 Subject: [PATCH 120/132] reset file --- locales/ar.json | 367 ------------------------------------------------ 1 file changed, 367 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 66fa93f45..2c63c0851 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -1,369 +1,2 @@ { - "action_invalid": "Invalid action '{action:s}'", - "admin_password": "Administration password", - "admin_password_change_failed": "Unable to change password", - "admin_password_changed": "The administration password has been changed", - "app_already_installed": "{app:s} is already installed", - "app_already_installed_cant_change_url": "This app is already installed. The url cannot be changed just by this function. Look into `app changeurl` if it's available.", - "app_already_up_to_date": "{app:s} is already up to date", - "app_argument_choice_invalid": "Invalid choice for argument '{name:s}', it must be one of {choices:s}", - "app_argument_invalid": "Invalid value for argument '{name:s}': {error:s}", - "app_argument_required": "Argument '{name:s}' is required", - "app_change_no_change_url_script": "The application {app_name:s} doesn't support changing it's URL yet, you might need to upgrade it.", - "app_change_url_failed_nginx_reload": "Failed to reload nginx. Here is the output of 'nginx -t':\n{nginx_errors:s}", - "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", - "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", - "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", - "app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !", - "app_extraction_failed": "Unable to extract installation files", - "app_id_invalid": "Invalid app id", - "app_incompatible": "The app {app} is incompatible with your YunoHost version", - "app_install_files_invalid": "Invalid installation files", - "app_location_already_used": "The app '{app}' is already installed on that location ({path})", - "app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain {domain} is already used by the other app '{other_app}'", - "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", - "app_location_unavailable": "This url is not available or conflicts with an already installed app", - "app_manifest_invalid": "Invalid app manifest: {error}", - "app_no_upgrade": "No app to upgrade", - "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", - "app_not_installed": "{app:s} is not installed", - "app_not_properly_removed": "{app:s} has not been properly removed", - "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", - "app_removed": "{app:s} has been removed", - "app_requirements_checking": "Checking required packages for {app}...", - "app_requirements_failed": "Unable to meet requirements for {app}: {error}", - "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", - "app_sources_fetch_failed": "Unable to fetch sources files", - "app_unknown": "Unknown app", - "app_unsupported_remote_type": "Unsupported remote type used for the app", - "app_upgrade_app_name": "Upgrading app {app}...", - "app_upgrade_failed": "Unable to upgrade {app:s}", - "app_upgrade_some_app_failed": "Unable to upgrade some applications", - "app_upgraded": "{app:s} has been upgraded", - "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", - "appslist_could_not_migrate": "Could not migrate app list {appslist:s} ! Unable to parse the url... The old cron job has been kept in {bkp_file:s}.", - "appslist_fetched": "The application list {appslist:s} has been fetched", - "appslist_migrating": "Migrating application list {appslist:s} ...", - "appslist_name_already_tracked": "There is already a registered application list with name {name:s}.", - "appslist_removed": "The application list {appslist:s} has been removed", - "appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid", - "appslist_retrieve_error": "Unable to retrieve the remote application list {appslist:s}: {error:s}", - "appslist_unknown": "Application list {appslist:s} unknown.", - "appslist_url_already_tracked": "There is already a registered application list with url {url:s}.", - "ask_current_admin_password": "Current administration password", - "ask_email": "Email address", - "ask_firstname": "First name", - "ask_lastname": "Last name", - "ask_list_to_remove": "List to remove", - "ask_main_domain": "Main domain", - "ask_new_admin_password": "New administration password", - "ask_password": "Password", - "ask_path": "Path", - "backup_abstract_method": "This backup method hasn't yet been implemented", - "backup_action_required": "You must specify something to save", - "backup_app_failed": "Unable to back up the app '{app:s}'", - "backup_applying_method_borg": "Sending all files to backup into borg-backup repository...", - "backup_applying_method_copy": "Copying all files to backup...", - "backup_applying_method_custom": "Calling the custom backup method '{method:s}'...", - "backup_applying_method_tar": "Creating the backup tar archive...", - "backup_archive_app_not_found": "App '{app:s}' not found in the backup archive", - "backup_archive_broken_link": "Unable to access backup archive (broken link to {path:s})", - "backup_archive_mount_failed": "Mounting the backup archive failed", - "backup_archive_name_exists": "The backup's archive name already exists", - "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", - "backup_archive_open_failed": "Unable to open the backup archive", - "backup_archive_system_part_not_available": "System part '{part:s}' not available in this backup", - "backup_archive_writing_error": "Unable to add files to backup into the compressed archive", - "backup_ask_for_copying_if_needed": "Some files couldn't be prepared to be backuped using the method that avoid to temporarily waste space on the system. To perform the backup, {size:s}MB should be used temporarily. Do you agree?", - "backup_borg_not_implemented": "Borg backup method is not yet implemented", - "backup_cant_mount_uncompress_archive": "Unable to mount in readonly mode the uncompress archive directory", - "backup_cleaning_failed": "Unable to clean-up the temporary backup directory", - "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", - "backup_couldnt_bind": "Couldn't bind {src:s} to {dest:s}.", - "backup_created": "Backup created", - "backup_creating_archive": "Creating the backup archive...", - "backup_creation_failed": "Backup creation failed", - "backup_csv_addition_failed": "Unable to add files to backup into the CSV file", - "backup_csv_creation_failed": "Unable to create the CSV file needed for future restore operations", - "backup_custom_backup_error": "Custom backup method failure on 'backup' step", - "backup_custom_mount_error": "Custom backup method failure on 'mount' step", - "backup_custom_need_mount_error": "Custom backup method failure on 'need_mount' step", - "backup_delete_error": "Unable to delete '{path:s}'", - "backup_deleted": "The backup has been deleted", - "backup_extracting_archive": "Extracting the backup archive...", - "backup_hook_unknown": "Backup hook '{hook:s}' unknown", - "backup_invalid_archive": "Invalid backup archive", - "backup_method_borg_finished": "Backup into borg finished", - "backup_method_copy_finished": "Backup copy finished", - "backup_method_custom_finished": "Custom backup method '{method:s}' finished", - "backup_method_tar_finished": "Backup tar archive created", - "backup_no_uncompress_archive_dir": "Uncompress archive directory doesn't exist", - "backup_nothings_done": "There is nothing to save", - "backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", - "backup_output_directory_not_empty": "The output directory is not empty", - "backup_output_directory_required": "You must provide an output directory for the backup", - "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", - "backup_running_app_script": "Running backup script of app '{app:s}'...", - "backup_running_hooks": "Running backup hooks...", - "backup_system_part_failed": "Unable to backup the '{part:s}' system part", - "backup_unable_to_organize_files": "Unable to organize files in the archive with the quick method", - "backup_with_no_backup_script_for_app": "App {app:s} has no backup script. Ignoring.", - "backup_with_no_restore_script_for_app": "App {app:s} has no restore script, you won't be able to automatically restore the backup of this app.", - "certmanager_acme_not_configured_for_domain": "Certificate for domain {domain:s} does not appear to be correctly installed. Please run cert-install for this domain first.", - "certmanager_attempt_to_renew_nonLE_cert": "The certificate for domain {domain:s} is not issued by Let's Encrypt. Cannot renew it automatically!", - "certmanager_attempt_to_renew_valid_cert": "The certificate for domain {domain:s} is not about to expire! Use --force to bypass", - "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", - "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}", - "certmanager_cert_install_success": "Successfully installed Let's Encrypt certificate for domain {domain:s}!", - "certmanager_cert_install_success_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", - "certmanager_cert_renew_success": "Successfully renewed Let's Encrypt certificate for domain {domain:s}!", - "certmanager_cert_signing_failed": "Signing the new certificate failed", - "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow...", - "certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge: the nginx configuration file {filepath:s} is conflicting and should be removed first", - "certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted - please try again later.", - "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use --force)", - "certmanager_domain_dns_ip_differs_from_public_ip": "The DNS 'A' record for domain {domain:s} is different from this server IP. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use --no-checks to disable those checks.)", - "certmanager_domain_http_not_working": "It seems that the domain {domain:s} cannot be accessed through HTTP. Please check your DNS and nginx configuration is okay", - "certmanager_domain_not_resolved_locally": "The domain {domain:s} cannot be resolved from inside your Yunohost server. This might happen if you recently modified your DNS record. If so, please wait a few hours for it to propagate. If the issue persists, consider adding {domain:s} to /etc/hosts. (If you know what you are doing, use --no-checks to disable those checks.)", - "certmanager_domain_unknown": "Unknown domain {domain:s}", - "certmanager_error_no_A_record": "No DNS 'A' record found for {domain:s}. You need to make your domain name point to your machine to be able to install a Let's Encrypt certificate! (If you know what you are doing, use --no-checks to disable those checks.)", - "certmanager_hit_rate_limit": "Too many certificates already issued for exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details", - "certmanager_http_check_timeout": "Timed out when server tried to contact itself through HTTP using public IP address (domain {domain:s} with ip {ip:s}). You may be experiencing hairpinning issue or the firewall/router ahead of your server is misconfigured.", - "certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})", - "certmanager_old_letsencrypt_app_detected": "\nYunohost detected that the 'letsencrypt' app is installed, which conflits with the new built-in certificate management features in Yunohost. If you wish to use the new built-in features, please run the following commands to migrate your installation:\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B.: this will attempt to re-install certificates for all domains with a Let's Encrypt certificate or self-signed certificate", - "certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})", - "certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})", - "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", - "custom_appslist_name_required": "You must provide a name for your custom app list", - "diagnosis_debian_version_error": "Can't retrieve the Debian version: {error}", - "diagnosis_kernel_version_error": "Can't retrieve kernel version: {error}", - "diagnosis_monitor_disk_error": "Can't monitor disks: {error}", - "diagnosis_monitor_network_error": "Can't monitor network: {error}", - "diagnosis_monitor_system_error": "Can't monitor system: {error}", - "diagnosis_no_apps": "No installed application", - "dnsmasq_isnt_installed": "dnsmasq does not seem to be installed, please run 'apt-get remove bind9 && apt-get install dnsmasq'", - "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", - "domain_cert_gen_failed": "Unable to generate certificate", - "domain_created": "The domain has been created", - "domain_creation_failed": "Unable to create domain", - "domain_deleted": "The domain has been deleted", - "domain_deletion_failed": "Unable to delete domain", - "domain_dns_conf_is_just_a_recommendation": "This command shows you what is the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", - "domain_dyndns_already_subscribed": "You've already subscribed to a DynDNS domain", - "domain_dyndns_dynette_is_unreachable": "Unable to reach YunoHost dynette, either your YunoHost is not correctly connected to the internet or the dynette server is down. Error: {error}", - "domain_dyndns_invalid": "Invalid domain to use with DynDNS", - "domain_dyndns_root_unknown": "Unknown DynDNS root domain", - "domain_exists": "Domain already exists", - "domain_hostname_failed": "Failed to set new hostname", - "domain_uninstall_app_first": "One or more apps are installed on this domain. Please uninstall them before proceeding to domain removal", - "domain_unknown": "Unknown domain", - "domain_zone_exists": "DNS zone file already exists", - "domain_zone_not_found": "DNS zone file not found for domain {:s}", - "domains_available": "Available domains:", - "done": "Done", - "downloading": "Downloading...", - "dyndns_could_not_check_provide": "Could not check if {provider:s} can provide {domain:s}.", - "dyndns_cron_installed": "The DynDNS cron job has been installed", - "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job", - "dyndns_cron_removed": "The DynDNS cron job has been removed", - "dyndns_ip_update_failed": "Unable to update IP address on DynDNS", - "dyndns_ip_updated": "Your IP address has been updated on DynDNS", - "dyndns_key_generating": "DNS key is being generated, it may take a while...", - "dyndns_key_not_found": "DNS key not found for the domain", - "dyndns_no_domain_registered": "No domain has been registered with DynDNS", - "dyndns_registered": "The DynDNS domain has been registered", - "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", - "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", - "dyndns_unavailable": "Domain {domain:s} is not available.", - "executing_command": "Executing command '{command:s}'...", - "executing_script": "Executing script '{script:s}'...", - "extracting": "Extracting...", - "field_invalid": "Invalid field '{:s}'", - "firewall_reload_failed": "Unable to reload the firewall", - "firewall_reloaded": "The firewall has been reloaded", - "firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.", - "format_datetime_short": "%m/%d/%Y %I:%M %p", - "global_settings_bad_choice_for_enum": "Bad value for setting {setting:s}, received {received_type:s}, except {expected_type:s}", - "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, except {expected_type:s}", - "global_settings_cant_open_settings": "Failed to open settings file, reason: {reason:s}", - "global_settings_cant_serialize_settings": "Failed to serialize settings data, reason: {reason:s}", - "global_settings_cant_write_settings": "Failed to write settings file, reason: {reason:s}", - "global_settings_key_doesnt_exists": "The key '{settings_key:s}' doesn't exists in the global settings, you can see all the available keys by doing 'yunohost settings list'", - "global_settings_reset_success": "Success. Your previous settings have been backuped in {path:s}", - "global_settings_setting_example_bool": "Example boolean option", - "global_settings_setting_example_enum": "Example enum option", - "global_settings_setting_example_int": "Example int option", - "global_settings_setting_example_string": "Example string option", - "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json", - "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", - "hook_exec_failed": "Script execution failed: {path:s}", - "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", - "hook_list_by_invalid": "Invalid property to list hook by", - "hook_name_unknown": "Unknown hook name '{name:s}'", - "installation_complete": "Installation complete", - "installation_failed": "Installation failed", - "invalid_url_format": "Invalid URL format", - "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", - "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", - "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user", - "ldap_initialized": "LDAP has been initialized", - "license_undefined": "undefined", - "mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'", - "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", - "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", - "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", - "maindomain_change_failed": "Unable to change the main domain", - "maindomain_changed": "The main domain has been changed", - "migrate_tsig_end": "Migration to hmac-sha512 finished", - "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", - "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", - "migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account...", - "migrate_tsig_wait_2": "2min...", - "migrate_tsig_wait_3": "1min...", - "migrate_tsig_wait_4": "30 secondes...", - "migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed !", - "migrations_backward": "Migrating backward.", - "migrations_bad_value_for_target": "Invalide number for target argument, available migrations numbers are 0 or {}", - "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", - "migrations_current_target": "Migration target is {}", - "migrations_error_failed_to_load_migration": "ERROR: failed to load migration {number} {name}", - "migrations_forward": "Migrating forward", - "migrations_loading_migration": "Loading migration {number} {name}...", - "migrations_migration_has_failed": "Migration {number} {name} has failed with exception {exception}, aborting", - "migrations_no_migrations_to_run": "No migrations to run", - "migrations_show_currently_running_migration": "Running migration {number} {name}...", - "migrations_show_last_migration": "Last ran migration is {}", - "migrations_skip_migration": "Skipping migration {number} {name}...", - "monitor_disabled": "The server monitoring has been disabled", - "monitor_enabled": "The server monitoring has been enabled", - "monitor_glances_con_failed": "Unable to connect to Glances server", - "monitor_not_enabled": "Server monitoring is not enabled", - "monitor_period_invalid": "Invalid time period", - "monitor_stats_file_not_found": "Statistics file not found", - "monitor_stats_no_update": "No monitoring statistics to update", - "monitor_stats_period_unavailable": "No available statistics for the period", - "mountpoint_unknown": "Unknown mountpoint", - "mysql_db_creation_failed": "MySQL database creation failed", - "mysql_db_init_failed": "MySQL database init failed", - "mysql_db_initialized": "The MySQL database has been initialized", - "network_check_mx_ko": "DNS MX record is not set", - "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", - "network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked", - "new_domain_required": "You must provide the new main domain", - "no_appslist_found": "No app list found", - "no_internet_connection": "Server is not connected to the Internet", - "no_ipv6_connectivity": "IPv6 connectivity is not available", - "no_restore_script": "No restore script found for the app '{app:s}'", - "not_enough_disk_space": "Not enough free disk space on '{path:s}'", - "package_not_installed": "Package '{pkgname}' is not installed", - "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", - "package_unknown": "Unknown package '{pkgname}'", - "packages_no_upgrade": "There is no package to upgrade", - "packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later", - "packages_upgrade_failed": "Unable to upgrade all of the packages", - "path_removal_failed": "Unable to remove path {:s}", - "pattern_backup_archive_name": "Must be a valid filename with max 30 characters, and alphanumeric and -_. characters only", - "pattern_domain": "Must be a valid domain name (e.g. my-domain.org)", - "pattern_email": "Must be a valid email address (e.g. someone@domain.org)", - "pattern_firstname": "Must be a valid first name", - "pattern_lastname": "Must be a valid last name", - "pattern_listname": "Must be alphanumeric and underscore characters only", - "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to disable the quota", - "pattern_password": "Must be at least 3 characters long", - "pattern_port": "Must be a valid port number (i.e. 0-65535)", - "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", - "pattern_positive_number": "Must be a positive number", - "pattern_username": "Must be lower-case alphanumeric and underscore characters only", - "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", - "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", - "port_available": "Port {port:d} is available", - "port_unavailable": "Port {port:d} is not available", - "restore_action_required": "You must specify something to restore", - "restore_already_installed_app": "An app is already installed with the id '{app:s}'", - "restore_app_failed": "Unable to restore the app '{app:s}'", - "restore_cleaning_failed": "Unable to clean-up the temporary restoration directory", - "restore_complete": "Restore complete", - "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers:s}]", - "restore_extracting": "Extracting needed files from the archive...", - "restore_failed": "Unable to restore the system", - "restore_hook_unavailable": "Restoration script for '{part:s}' not available on your system and not in the archive either", - "restore_may_be_not_enough_disk_space": "Your system seems not to have enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", - "restore_mounting_archive": "Mounting archive into '{path:s}'", - "restore_not_enough_disk_space": "Not enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", - "restore_nothings_done": "Nothing has been restored", - "restore_removing_tmp_dir_failed": "Unable to remove an old temporary directory", - "restore_running_app_script": "Running restore script of app '{app:s}'...", - "restore_running_hooks": "Running restoration hooks...", - "restore_system_part_failed": "Unable to restore the '{part:s}' system part", - "server_shutdown": "The server will shutdown", - "server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers:s}]", - "server_reboot": "The server will reboot", - "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers:s}]", - "service_add_failed": "Unable to add service '{service:s}'", - "service_added": "The service '{service:s}' has been added", - "service_already_started": "Service '{service:s}' has already been started", - "service_already_stopped": "Service '{service:s}' has already been stopped", - "service_cmd_exec_failed": "Unable to execute command '{command:s}'", - "service_conf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", - "service_conf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", - "service_conf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by service {service} but has been kept back.", - "service_conf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated", - "service_conf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created", - "service_conf_file_remove_failed": "Unable to remove the configuration file '{conf}'", - "service_conf_file_removed": "The configuration file '{conf}' has been removed", - "service_conf_file_updated": "The configuration file '{conf}' has been updated", - "service_conf_new_managed_file": "The configuration file '{conf}' is now managed by the service {service}.", - "service_conf_up_to_date": "The configuration is already up-to-date for service '{service}'", - "service_conf_updated": "The configuration has been updated for service '{service}'", - "service_conf_would_be_updated": "The configuration would have been updated for service '{service}'", - "service_disable_failed": "Unable to disable service '{service:s}'", - "service_disabled": "The service '{service:s}' has been disabled", - "service_enable_failed": "Unable to enable service '{service:s}'", - "service_enabled": "The service '{service:s}' has been enabled", - "service_no_log": "No log to display for service '{service:s}'", - "service_regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for service '{service}'...", - "service_regenconf_failed": "Unable to regenerate the configuration for service(s): {services}", - "service_regenconf_pending_applying": "Applying pending configuration for service '{service}'...", - "service_remove_failed": "Unable to remove service '{service:s}'", - "service_removed": "The service '{service:s}' has been removed", - "service_start_failed": "Unable to start service '{service:s}'", - "service_started": "The service '{service:s}' has been started", - "service_status_failed": "Unable to determine status of service '{service:s}'", - "service_stop_failed": "Unable to stop service '{service:s}'", - "service_stopped": "The service '{service:s}' has been stopped", - "service_unknown": "Unknown service '{service:s}'", - "ssowat_conf_generated": "The SSOwat configuration has been generated", - "ssowat_conf_updated": "The SSOwat configuration has been updated", - "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", - "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", - "system_upgraded": "The system has been upgraded", - "system_username_exists": "Username already exists in the system users", - "unbackup_app": "App '{app:s}' will not be saved", - "unexpected_error": "An unexpected error occured", - "unit_unknown": "Unknown unit '{unit:s}'", - "unlimit": "No quota", - "unrestore_app": "App '{app:s}' will not be restored", - "update_cache_failed": "Unable to update APT cache", - "updating_apt_cache": "Updating the list of available packages...", - "upgrade_complete": "Upgrade complete", - "upgrading_packages": "Upgrading packages...", - "upnp_dev_not_found": "No UPnP device found", - "upnp_disabled": "UPnP has been disabled", - "upnp_enabled": "UPnP has been enabled", - "upnp_port_open_failed": "Unable to open UPnP ports", - "user_created": "The user has been created", - "user_creation_failed": "Unable to create user", - "user_deleted": "The user has been deleted", - "user_deletion_failed": "Unable to delete user", - "user_home_creation_failed": "Unable to create user home folder", - "user_info_failed": "Unable to retrieve user information", - "user_unknown": "Unknown user: {user:s}", - "user_update_failed": "Unable to update user", - "user_updated": "The user has been updated", - "yunohost_already_installed": "YunoHost is already installed", - "yunohost_ca_creation_failed": "Unable to create certificate authority", - "yunohost_ca_creation_success": "The local certification authority has been created.", - "yunohost_configured": "YunoHost has been configured", - "yunohost_installing": "Installing YunoHost...", - "yunohost_not_installed": "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'" } From c824f403a421e63ede69922c4bb931dad599c1fa Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 9 Feb 2018 16:10:31 +0100 Subject: [PATCH 121/132] [Fix] Referrer, CSP bad conf. cf. Another pr. --- data/templates/nginx/server.tpl.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 11f503c98..20301b2c1 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -43,8 +43,7 @@ server { #ssl_dhparam /etc/ssl/private/dh2048.pem; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - add_header 'Referrer-Policy' 'same-origin'; - add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval';report-uri /csp-violation-report-endpoint/"; + add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval';"; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header X-Download-Options noopen; From 4f616fe8c7a0d2de9f40b80b541a6cbd2b11eea2 Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 9 Feb 2018 16:11:41 +0100 Subject: [PATCH 122/132] [Fix] CSP cf. another PR. --- data/templates/nginx/plain/yunohost_admin.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index eedbd61b3..51424f289 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -39,7 +39,7 @@ server { add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; add_header 'Referrer-Policy' 'same-origin'; - add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval';report-uri /csp-violation-report-endpoint/"; + add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'"; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header X-Download-Options noopen; From 03273e3b946a74972185ad429fd9fe7ea49da474 Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 9 Feb 2018 16:20:29 +0100 Subject: [PATCH 123/132] [fix] typo --- data/templates/nginx/server.tpl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 20301b2c1..d5356fd6a 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -43,7 +43,7 @@ server { #ssl_dhparam /etc/ssl/private/dh2048.pem; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval';"; + add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'"; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header X-Download-Options noopen; From 4276a187a05c01378cd4574f7a09bcff30a9f00f Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 9 Feb 2018 16:24:16 +0100 Subject: [PATCH 124/132] [enh] Comment with the URL of the Mozilla Directives --- data/templates/nginx/server.tpl.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index d5356fd6a..ac2ff8486 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -42,6 +42,9 @@ server { # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; + # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners + # https://wiki.mozilla.org/Security/Guidelines/Web_Security + # https://observatory.mozilla.org/ add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'"; add_header X-Content-Type-Options nosniff; From 6ab29260cf7669da78aa25fa088735b7f5f69846 Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 9 Feb 2018 16:25:09 +0100 Subject: [PATCH 125/132] [enh] Mozilla directives. --- data/templates/nginx/plain/yunohost_admin.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 51424f289..156d61bd6 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -37,6 +37,9 @@ server { # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; + # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners + # https://wiki.mozilla.org/Security/Guidelines/Web_Security + # https://observatory.mozilla.org/ add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; add_header 'Referrer-Policy' 'same-origin'; add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'"; From f70949b35019f51be47bd4a7e3a9b106bc2ee3fe Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 11 Feb 2018 05:39:31 +0100 Subject: [PATCH 126/132] [fix] handle uncatched exception --- src/yunohost/tools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 381cd07e0..f98d48fc5 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -33,8 +33,9 @@ import logging import subprocess import pwd import socket -from collections import OrderedDict +from xmlrpclib import Fault from importlib import import_module +from collections import OrderedDict import apt import apt.progress @@ -569,7 +570,7 @@ def tools_diagnosis(auth, private=False): diagnosis['system'] = OrderedDict() try: disks = monitor_disk(units=['filesystem'], human_readable=True) - except MoulinetteError as e: + except (MoulinetteError, Fault) as e: logger.warning(m18n.n('diagnosis_monitor_disk_error', error=format(e)), exc_info=1) else: diagnosis['system']['disks'] = {} From 88d7a31bda1780b9cc81ff073092fc41629d58b3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 11 Feb 2018 22:23:40 +0100 Subject: [PATCH 127/132] [fix] Microdecision : add mailutils as a dependency --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index dcdd0dd9a..c15c5eec5 100644 --- a/debian/control +++ b/debian/control @@ -18,7 +18,7 @@ Depends: ${python:Depends}, ${misc:Depends} , ca-certificates, netcat-openbsd, iproute , mariadb-server | mysql-server, php5-mysql | php5-mysqlnd , slapd, ldap-utils, sudo-ldap, libnss-ldapd, nscd - , postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail + , postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, mailutils , dovecot-ldap, dovecot-lmtpd, dovecot-managesieved , dovecot-antispam, fail2ban , nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl From 9a80635dbc33babd4da63eff4d2d157991f069e6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 28 Feb 2018 08:54:11 -0500 Subject: [PATCH 128/132] [fix] Fail2ban conf/filter was not matching failed login attempts... --- data/templates/fail2ban/jail.conf | 3 ++- data/templates/fail2ban/yunohost.conf | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/templates/fail2ban/jail.conf b/data/templates/fail2ban/jail.conf index d34763e48..648d44fa8 100644 --- a/data/templates/fail2ban/jail.conf +++ b/data/templates/fail2ban/jail.conf @@ -581,5 +581,6 @@ enabled = true port = http,https protocol = tcp filter = yunohost -logpath = /var/log/nginx*/*error.log +logpath = /var/log/nginx/*error.log + /var/log/nginx/*access.log maxretry = 6 diff --git a/data/templates/fail2ban/yunohost.conf b/data/templates/fail2ban/yunohost.conf index 3ca8f1c8f..a501c10ba 100644 --- a/data/templates/fail2ban/yunohost.conf +++ b/data/templates/fail2ban/yunohost.conf @@ -14,8 +14,8 @@ # (?:::f{4,6}:)?(?P[\w\-.^_]+) # Values: TEXT # -failregex = helpers.lua:[1-9]+: authenticate\(\): Connection failed for: .*, client: - ^ -.*\"POST /yunohost/api/login HTTP/1.1\" 401 22 +failregex = helpers.lua:[0-9]+: authenticate\(\): Connection failed for: .*, client: + ^ -.*\"POST /yunohost/api/login HTTP/1.1\" 401 # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. From 89215f0402ba5502940abc7b5e22f0d8c2326dce Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 28 Feb 2018 08:54:11 -0500 Subject: [PATCH 129/132] [fix] Fail2ban conf/filter was not matching failed login attempts... --- data/templates/fail2ban/jail.conf | 3 ++- data/templates/fail2ban/yunohost.conf | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/templates/fail2ban/jail.conf b/data/templates/fail2ban/jail.conf index d34763e48..648d44fa8 100644 --- a/data/templates/fail2ban/jail.conf +++ b/data/templates/fail2ban/jail.conf @@ -581,5 +581,6 @@ enabled = true port = http,https protocol = tcp filter = yunohost -logpath = /var/log/nginx*/*error.log +logpath = /var/log/nginx/*error.log + /var/log/nginx/*access.log maxretry = 6 diff --git a/data/templates/fail2ban/yunohost.conf b/data/templates/fail2ban/yunohost.conf index 3ca8f1c8f..a501c10ba 100644 --- a/data/templates/fail2ban/yunohost.conf +++ b/data/templates/fail2ban/yunohost.conf @@ -14,8 +14,8 @@ # (?:::f{4,6}:)?(?P[\w\-.^_]+) # Values: TEXT # -failregex = helpers.lua:[1-9]+: authenticate\(\): Connection failed for: .*, client: - ^ -.*\"POST /yunohost/api/login HTTP/1.1\" 401 22 +failregex = helpers.lua:[0-9]+: authenticate\(\): Connection failed for: .*, client: + ^ -.*\"POST /yunohost/api/login HTTP/1.1\" 401 # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. From 00481af499fea58f8ade0a8b65474442def326c8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 7 Mar 2018 12:44:15 +0000 Subject: [PATCH 130/132] Update changelog for 2.7.10 release --- debian/changelog | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 4ebe4e14c..989419109 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,14 @@ +yunohost (2.7.10) stable; urgency=low + + * [fix] Fail2ban conf/filter was not matching failed login attempts... + + -- Alexandre Aubin Wed, 07 Mar 2018 12:43:35 +0000 + yunohost (2.7.9) stable; urgency=low (Bumping version number for stable release) - -- Alexandre Aubin Tue, 30 Jan 2018 17:42:00 +0000 + -- Alexandre Aubin Tue, 30 Jan 2018 17:42:00 +0000 yunohost (2.7.8) testing; urgency=low From 935b972d6e61a8966da862cdee4421ce8e5ffb19 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Mar 2018 17:55:07 +0100 Subject: [PATCH 131/132] Misc fixes in the helpers to clean the autodoc --- data/helpers.d/backend | 17 ++++++++--------- data/helpers.d/ip | 10 +++++----- data/helpers.d/mysql | 4 ++-- data/helpers.d/system | 32 +++++++++++++++++--------------- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index c2c626829..28c5b8e91 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -2,7 +2,7 @@ # # usage: ynh_use_logrotate [logfile] [--non-append] # | arg: logfile - absolute path of logfile -# | option: --non-append - Replace the config file instead of appending this new config. +# | arg: --non-append - (Option) Replace the config file instead of appending this new config. # # If no argument provided, a standard directory will be use. /var/log/${app} # You can provide a path with the directory only or with the logfile. @@ -64,9 +64,9 @@ ynh_remove_logrotate () { # Create a dedicated systemd config # -# usage: ynh_add_systemd_config [Service name] [Template name] -# | arg: Service name (optionnal, $app by default) -# | arg: Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) +# usage: ynh_add_systemd_config [service] [template] +# | arg: service - Service name (optionnal, $app by default) +# | arg: template - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) # # This will use the template ../conf/.service # to generate a systemd config, by replacing the following keywords @@ -76,7 +76,6 @@ ynh_remove_logrotate () { # __APP__ by $app # __FINALPATH__ by $final_path # -# usage: ynh_add_systemd_config ynh_add_systemd_config () { local service_name="${1:-$app}" @@ -101,10 +100,9 @@ ynh_add_systemd_config () { # Remove the dedicated systemd config # -# usage: ynh_remove_systemd_config [Service name] -# | arg: Service name (optionnal, $app by default) +# usage: ynh_remove_systemd_config [service] +# | arg: service - Service name (optionnal, $app by default) # -# usage: ynh_remove_systemd_config ynh_remove_systemd_config () { local service_name="${1:-$app}" @@ -119,6 +117,8 @@ ynh_remove_systemd_config () { # Create a dedicated nginx config # +# usage: ynh_add_nginx_config +# # This will use a template in ../conf/nginx.conf # __PATH__ by $path_url # __DOMAIN__ by $domain @@ -126,7 +126,6 @@ ynh_remove_systemd_config () { # __NAME__ by $app # __FINALPATH__ by $final_path # -# usage: ynh_add_nginx_config ynh_add_nginx_config () { finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" ynh_backup_if_checksum_is_different "$finalnginxconf" diff --git a/data/helpers.d/ip b/data/helpers.d/ip index 874675c9d..092cdff4b 100644 --- a/data/helpers.d/ip +++ b/data/helpers.d/ip @@ -1,10 +1,10 @@ # Validate an IP address # +# usage: ynh_validate_ip [family] [ip_address] +# | ret: 0 for valid ip addresses, 1 otherwise +# # example: ynh_validate_ip 4 111.222.333.444 # -# usage: ynh_validate_ip -# -# exit code : 0 for valid ip addresses, 1 otherwise ynh_validate_ip() { # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 @@ -31,8 +31,8 @@ EOF # example: ynh_validate_ip4 111.222.333.444 # # usage: ynh_validate_ip4 +# | ret: 0 for valid ipv4 addresses, 1 otherwise # -# exit code : 0 for valid ipv4 addresses, 1 otherwise ynh_validate_ip4() { ynh_validate_ip 4 $1 @@ -44,8 +44,8 @@ ynh_validate_ip4() # example: ynh_validate_ip6 2000:dead:beef::1 # # usage: ynh_validate_ip6 +# | ret: 0 for valid ipv6 addresses, 1 otherwise # -# exit code : 0 for valid ipv6 addresses, 1 otherwise ynh_validate_ip6() { ynh_validate_ip 6 $1 diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 8aa81a1fe..452a95eec 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -90,7 +90,7 @@ ynh_mysql_create_user() { # # usage: ynh_mysql_user_exists user # | arg: user - the user for which to check existence -function ynh_mysql_user_exists() +ynh_mysql_user_exists() { local user=$1 if [[ -z $(ynh_mysql_execute_as_root "SELECT User from mysql.user WHERE User = '$user';") ]] @@ -153,7 +153,7 @@ ynh_mysql_remove_db () { # Sanitize a string intended to be the name of a database # (More specifically : replace - and . by _) # -# Exemple: dbname=$(ynh_sanitize_dbid $app) +# example: dbname=$(ynh_sanitize_dbid $app) # # usage: ynh_sanitize_dbid name # | arg: name - name to correct/sanitize diff --git a/data/helpers.d/system b/data/helpers.d/system index f204c836a..dbd21a4ec 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -1,18 +1,17 @@ # Manage a fail of the script # -# Print a warning to inform that the script was failed -# Execute the ynh_clean_setup function if used in the app script -# -# usage of ynh_clean_setup function -# This function provide a way to clean some residual of installation that not managed by remove script. -# To use it, simply add in your script: +# usage: +# ynh_exit_properly is used only by the helper ynh_abort_if_errors. +# You should not use it directly. +# Instead, add to your script: # ynh_clean_setup () { # instructions... # } -# This function is optionnal. # -# Usage: ynh_exit_properly is used only by the helper ynh_abort_if_errors. -# You must not use it directly. +# This function provide a way to clean some residual of installation that not managed by remove script. +# +# It prints a warning to inform that the script was failed, and execute the ynh_clean_setup function if used in the app script +# ynh_exit_properly () { local exit_code=$? if [ "$exit_code" -eq 0 ]; then @@ -31,20 +30,23 @@ ynh_exit_properly () { ynh_die # Exit with error status } -# Exit if an error occurs during the execution of the script. +# Exits if an error occurs during the execution of the script. # -# Stop immediatly the execution if an error occured or if a empty variable is used. -# The execution of the script is derivate to ynh_exit_properly function before exit. +# usage: ynh_abort_if_errors +# +# This configure the rest of the script execution such that, if an error occurs +# or if an empty variable is used, the execution of the script stops +# immediately and a call to `ynh_exit_properly` is triggered. # -# Usage: ynh_abort_if_errors ynh_abort_if_errors () { set -eu # Exit if a command fail, and if a variable is used unset. trap ynh_exit_properly EXIT # Capturing exit signals on shell script } -# Return the Debian release codename (i.e. jessie, stretch, etc.) +# Fetch the Debian release codename # # usage: ynh_get_debian_release +# | ret: The Debian release codename (i.e. jessie, stretch, ...) ynh_get_debian_release () { echo $(lsb_release --codename --short) -} \ No newline at end of file +} From af22474a5068093f22b0074c01eaa784d29a0d5d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 10 Mar 2018 19:01:33 +0100 Subject: [PATCH 132/132] Add some [internal] flags in the comment to hide a few helpers from autodoc --- data/helpers.d/filesystem | 7 +++++++ data/helpers.d/mysql | 8 ++++++++ data/helpers.d/package | 4 ++++ data/helpers.d/print | 4 ++++ data/helpers.d/system | 5 ++++- 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 6361d278e..6836aeebe 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -136,6 +136,8 @@ ynh_restore () { # Return the path in the archive where has been stocked the origin path # +# [internal] +# # usage: _get_archive_path ORIGIN_PATH _get_archive_path () { # For security reasons we use csv python library to read the CSV @@ -203,6 +205,9 @@ ynh_restore_file () { } # Deprecated helper since it's a dangerous one! +# +# [internal] +# ynh_bind_or_cp() { local AS_ROOT=${3:-0} local NO_ROOT=0 @@ -213,6 +218,8 @@ ynh_bind_or_cp() { # Create a directory under /tmp # +# [internal] +# # Deprecated helper # # usage: ynh_mkdir_tmp diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 452a95eec..7bc93fad5 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -35,6 +35,8 @@ ynh_mysql_execute_file_as_root() { # Create a database and grant optionnaly privilegies to a user # +# [internal] +# # usage: ynh_mysql_create_db db [user [pwd]] # | arg: db - the database name to create # | arg: user - the user to grant privilegies @@ -56,6 +58,8 @@ ynh_mysql_create_db() { # Drop a database # +# [internal] +# # If you intend to drop the database *and* the associated user, # consider using ynh_mysql_remove_db instead. # @@ -78,6 +82,8 @@ ynh_mysql_dump_db() { # Create a user # +# [internal] +# # usage: ynh_mysql_create_user user pwd [host] # | arg: user - the user name to create # | arg: pwd - the password to identify user by @@ -103,6 +109,8 @@ ynh_mysql_user_exists() # Drop a user # +# [internal] +# # usage: ynh_mysql_drop_user user # | arg: user - the user name to drop ynh_mysql_drop_user() { diff --git a/data/helpers.d/package b/data/helpers.d/package index c616105d1..4d147488c 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -26,6 +26,8 @@ ynh_package_version() { # APT wrapper for non-interactive operation # +# [internal] +# # usage: ynh_apt update ynh_apt() { DEBIAN_FRONTEND=noninteractive sudo apt-get -y -qq $@ @@ -73,6 +75,8 @@ ynh_package_autopurge() { # Build and install a package from an equivs control file # +# [internal] +# # example: generate an empty control file with `equivs-control`, adjust its # content and use helper to build and install the package: # ynh_package_install_from_equivs /path/to/controlfile diff --git a/data/helpers.d/print b/data/helpers.d/print index d9c8f1ec4..b13186d62 100644 --- a/data/helpers.d/print +++ b/data/helpers.d/print @@ -6,7 +6,11 @@ ynh_die() { } # Ignore the yunohost-cli log to prevent errors with conditionals commands +# +# [internal] +# # usage: ynh_no_log COMMAND +# # Simply duplicate the log, execute the yunohost command and replace the log without the result of this command # It's a very badly hack... ynh_no_log() { diff --git a/data/helpers.d/system b/data/helpers.d/system index dbd21a4ec..70cc57493 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -1,5 +1,7 @@ # Manage a fail of the script # +# [internal] +# # usage: # ynh_exit_properly is used only by the helper ynh_abort_if_errors. # You should not use it directly. @@ -36,7 +38,8 @@ ynh_exit_properly () { # # This configure the rest of the script execution such that, if an error occurs # or if an empty variable is used, the execution of the script stops -# immediately and a call to `ynh_exit_properly` is triggered. +# immediately and a call to `ynh_clean_setup` is triggered if it has been +# defined by your script. # ynh_abort_if_errors () { set -eu # Exit if a command fail, and if a variable is used unset.