From 365f7694a9b27d2684eef93616aad19b3d0398df Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 23 Jun 2016 18:36:21 +0200 Subject: [PATCH 001/208] [enh] display file path on file_not_exist error --- src/yunohost/hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 500db919f..2d46cfcd5 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -315,7 +315,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, if path[0] != '/': path = os.path.realpath(path) if not os.path.isfile(path): - raise MoulinetteError(errno.EIO, m18n.g('file_not_exist')) + raise MoulinetteError(errno.EIO, m18n.g('file_not_exist', path=path)) # Construct command variables cmd_args = '' From 937f26bdaaca64849570c472914e8d8a2e7f1fbf Mon Sep 17 00:00:00 2001 From: opi Date: Thu, 21 Jul 2016 11:43:02 +0200 Subject: [PATCH 002/208] [fix] Raise error on malformed SSOwat persistent conf. --- locales/en.json | 1 + src/yunohost/app.py | 3 +++ src/yunohost/tools.py | 3 +++ src/yunohost/user.py | 15 +++++++++------ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index e939b26fa..a91b0c525 100644 --- a/locales/en.json +++ b/locales/en.json @@ -209,6 +209,7 @@ "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_error": "Erro while reading SSOwat persistent configuration", "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", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index af61ff63b..27046fd01 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -806,6 +806,9 @@ def app_makedefault(auth, app, domain=None): try: with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) + except ValueError: + raise MoulinetteError(errno.EINVAL, + m18n.n('ssowat_persistent_conf_error')) except IOError: ssowat_conf = {} diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index f78e32363..1898e7023 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -226,6 +226,9 @@ def tools_postinstall(domain, password, ignore_dyndns=False): try: with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) + except ValueError: + raise MoulinetteError(errno.EINVAL, + m18n.n('ssowat_persistent_conf_error')) except IOError: ssowat_conf = {} diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ec7dd539c..e04ba9883 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -177,14 +177,17 @@ def user_create(auth, username, firstname, lastname, mail, password, try: with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) + except ValueError: + raise MoulinetteError(errno.EINVAL, + m18n.n('ssowat_persistent_conf_error')) + except IOError: + ssowat_conf = {} - if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']: - del ssowat_conf['redirected_urls']['/'] + if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']: + del ssowat_conf['redirected_urls']['/'] - with open('/etc/ssowat/conf.json.persistent', 'w+') as f: - json.dump(ssowat_conf, f, sort_keys=True, indent=4) - - except IOError: pass + with open('/etc/ssowat/conf.json.persistent', 'w+') as f: + json.dump(ssowat_conf, f, sort_keys=True, indent=4) if auth.add(rdn, attr_dict): From d95052a9e9a1b26115339bb2ed22e1ed01f7c6ed Mon Sep 17 00:00:00 2001 From: opi Date: Sun, 28 Aug 2016 15:39:45 +0200 Subject: [PATCH 003/208] [enh] Catch SSOwat persistent configuration write error. --- locales/en.json | 3 ++- src/yunohost/app.py | 2 +- src/yunohost/tools.py | 2 +- src/yunohost/user.py | 10 +++++++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index a91b0c525..3a3c8b0b2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -209,7 +209,8 @@ "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_error": "Erro while reading SSOwat persistent configuration", + "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration", + "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration", "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", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 27046fd01..4f4a678d1 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -808,7 +808,7 @@ def app_makedefault(auth, app, domain=None): ssowat_conf = json.loads(str(json_conf.read())) except ValueError: raise MoulinetteError(errno.EINVAL, - m18n.n('ssowat_persistent_conf_error')) + m18n.n('ssowat_persistent_conf_read_error')) except IOError: ssowat_conf = {} diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 1898e7023..7af818a30 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -228,7 +228,7 @@ def tools_postinstall(domain, password, ignore_dyndns=False): ssowat_conf = json.loads(str(json_conf.read())) except ValueError: raise MoulinetteError(errno.EINVAL, - m18n.n('ssowat_persistent_conf_error')) + m18n.n('ssowat_persistent_conf_read_error')) except IOError: ssowat_conf = {} diff --git a/src/yunohost/user.py b/src/yunohost/user.py index e04ba9883..4cef87429 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -179,15 +179,19 @@ def user_create(auth, username, firstname, lastname, mail, password, ssowat_conf = json.loads(str(json_conf.read())) except ValueError: raise MoulinetteError(errno.EINVAL, - m18n.n('ssowat_persistent_conf_error')) + m18n.n('ssowat_persistent_conf_read_error')) except IOError: ssowat_conf = {} if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']: del ssowat_conf['redirected_urls']['/'] - with open('/etc/ssowat/conf.json.persistent', 'w+') as f: - json.dump(ssowat_conf, f, sort_keys=True, indent=4) + try: + with open('/etc/ssowat/conf.json.persistent', 'w+') as f: + json.dump(ssowat_conf, f, sort_keys=True, indent=4) + except IOError: + raise MoulinetteError(errno.EINVAL, + m18n.n('ssowat_persistent_conf_write_error')) if auth.add(rdn, attr_dict): From 6149e6c6deba4664cc2e3fa4008efd1b0d823627 Mon Sep 17 00:00:00 2001 From: opi Date: Sun, 28 Aug 2016 15:42:47 +0200 Subject: [PATCH 004/208] [fix] Write SSOwat configuration file only if needed. --- src/yunohost/user.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 4cef87429..51c87f59b 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -185,13 +185,12 @@ def user_create(auth, username, firstname, lastname, mail, password, if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']: del ssowat_conf['redirected_urls']['/'] - - try: - with open('/etc/ssowat/conf.json.persistent', 'w+') as f: - json.dump(ssowat_conf, f, sort_keys=True, indent=4) - except IOError: - raise MoulinetteError(errno.EINVAL, - m18n.n('ssowat_persistent_conf_write_error')) + try: + with open('/etc/ssowat/conf.json.persistent', 'w+') as f: + json.dump(ssowat_conf, f, sort_keys=True, indent=4) + except IOError: + raise MoulinetteError(errno.EINVAL, + m18n.n('ssowat_persistent_conf_write_error')) if auth.add(rdn, attr_dict): From 81e464ce1f4031475ed7051712b75a75745629e7 Mon Sep 17 00:00:00 2001 From: opi Date: Sun, 28 Aug 2016 23:36:45 +0200 Subject: [PATCH 005/208] [enh] Display full exception error message. --- locales/en.json | 4 ++-- src/yunohost/app.py | 13 +++++++++---- src/yunohost/tools.py | 13 +++++++++---- src/yunohost/user.py | 10 +++++----- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3a3c8b0b2..dc801a908 100644 --- a/locales/en.json +++ b/locales/en.json @@ -209,8 +209,8 @@ "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", - "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration", + "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}", + "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}", "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", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4f4a678d1..b8290f1bb 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -806,9 +806,9 @@ def app_makedefault(auth, app, domain=None): try: with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) - except ValueError: + except ValueError as e: raise MoulinetteError(errno.EINVAL, - m18n.n('ssowat_persistent_conf_read_error')) + m18n.n('ssowat_persistent_conf_read_error', error=e.strerror)) except IOError: ssowat_conf = {} @@ -817,8 +817,13 @@ def app_makedefault(auth, app, domain=None): ssowat_conf['redirected_urls'][domain +'/'] = app_domain + app_path - with open('/etc/ssowat/conf.json.persistent', 'w+') as f: - json.dump(ssowat_conf, f, sort_keys=True, indent=4) + try: + with open('/etc/ssowat/conf.json.persistent', 'w+') as f: + json.dump(ssowat_conf, f, sort_keys=True, indent=4) + except IOError as e: + raise MoulinetteError(errno.EPERM, + m18n.n('ssowat_persistent_conf_write_error', error=e.strerror)) + os.system('chmod 644 /etc/ssowat/conf.json.persistent') diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 7af818a30..906b34e65 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -226,9 +226,9 @@ def tools_postinstall(domain, password, ignore_dyndns=False): try: with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) - except ValueError: + except ValueError as e: raise MoulinetteError(errno.EINVAL, - m18n.n('ssowat_persistent_conf_read_error')) + m18n.n('ssowat_persistent_conf_read_error', error=e.strerror)) except IOError: ssowat_conf = {} @@ -237,8 +237,13 @@ def tools_postinstall(domain, password, ignore_dyndns=False): ssowat_conf['redirected_urls']['/'] = domain +'/yunohost/admin' - with open('/etc/ssowat/conf.json.persistent', 'w+') as f: - json.dump(ssowat_conf, f, sort_keys=True, indent=4) + try: + with open('/etc/ssowat/conf.json.persistent', 'w+') as f: + json.dump(ssowat_conf, f, sort_keys=True, indent=4) + except IOError as e: + raise MoulinetteError(errno.EPERM, + m18n.n('ssowat_persistent_conf_write_error', error=e.strerror)) + os.system('chmod 644 /etc/ssowat/conf.json.persistent') diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 51c87f59b..677ba05d4 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -177,9 +177,9 @@ def user_create(auth, username, firstname, lastname, mail, password, try: with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) - except ValueError: + except ValueError as e: raise MoulinetteError(errno.EINVAL, - m18n.n('ssowat_persistent_conf_read_error')) + m18n.n('ssowat_persistent_conf_read_error', error=e.strerror)) except IOError: ssowat_conf = {} @@ -188,9 +188,9 @@ def user_create(auth, username, firstname, lastname, mail, password, try: with open('/etc/ssowat/conf.json.persistent', 'w+') as f: json.dump(ssowat_conf, f, sort_keys=True, indent=4) - except IOError: - raise MoulinetteError(errno.EINVAL, - m18n.n('ssowat_persistent_conf_write_error')) + except IOError as e: + raise MoulinetteError(errno.EPERM, + m18n.n('ssowat_persistent_conf_write_error', error=e.strerror)) if auth.add(rdn, attr_dict): From 9a66a00278f10a7a4f167147f78a17c6b0f82d50 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 8 Aug 2016 07:46:38 +0200 Subject: [PATCH 006/208] [fix] wait for admin user to be available after a slapd regen-conf, this fix install on slow hardware/vps --- data/hooks/conf_regen/06-slapd | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index b3353962e..aef47c347 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -102,6 +102,23 @@ do_post_regen() { fi sudo service slapd force-reload + + # on slow hardware/vm this regen conf would exit before the admin user that + # is stored in ldap is available because ldap seems to slow to restart + # so we'll wait either until we are able to log as admin or until a timeout + # is reached + # we need to do this because the next hooks executed after this one during + # postinstall requires to run as admin thus breaking postinstall on slow + # hardware which mean yunohost can't be correctly installed on those hardware + # and this sucks + # wait a maximum time of 5 minutes + # yes, force-reload behave like a restart + number_of_wait=0 + while ! sudo su admin -c '' && ((number_of_wait < 60)) + do + sleep 5 + ((number_of_wait += 1)) + done } FORCE=${2:-0} From 9225d12da88a1d3eb46c49d0f47d2c06621bfd70 Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Wed, 7 Sep 2016 17:57:04 +0200 Subject: [PATCH 007/208] [enh] Remove useless comments autogenerated in the past --- data/templates/dovecot/dovecot.conf | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/templates/dovecot/dovecot.conf b/data/templates/dovecot/dovecot.conf index 3daa670bf..244b78780 100644 --- a/data/templates/dovecot/dovecot.conf +++ b/data/templates/dovecot/dovecot.conf @@ -1,5 +1,3 @@ -# 2.1.7: /etc/dovecot/dovecot.conf -# OS: Linux 3.2.0-3-686-pae i686 Debian wheezy/sid ext4 listen = *, :: auth_mechanisms = plain login login_greeting = Dovecot ready!! From e3f4686c90f83dcbd985145e8aab25f0f89e17a6 Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Wed, 7 Sep 2016 18:04:29 +0200 Subject: [PATCH 008/208] [enh] Hide the fact that we are speaking to dovecot --- data/templates/dovecot/dovecot.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/data/templates/dovecot/dovecot.conf b/data/templates/dovecot/dovecot.conf index 244b78780..00916d88d 100644 --- a/data/templates/dovecot/dovecot.conf +++ b/data/templates/dovecot/dovecot.conf @@ -1,6 +1,5 @@ listen = *, :: auth_mechanisms = plain login -login_greeting = Dovecot ready!! mail_gid = 8 mail_home = /var/mail/%n mail_location = maildir:/var/mail/%n From e4832234185d45322318041dde99131b9b30c2f5 Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Wed, 7 Sep 2016 18:20:21 +0200 Subject: [PATCH 009/208] [enh] reorder dovecot main configuration so that it is easier to read and extend --- data/templates/dovecot/dovecot.conf | 64 ++++++++++++++++------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/data/templates/dovecot/dovecot.conf b/data/templates/dovecot/dovecot.conf index 00916d88d..af745c93d 100644 --- a/data/templates/dovecot/dovecot.conf +++ b/data/templates/dovecot/dovecot.conf @@ -1,15 +1,44 @@ listen = *, :: auth_mechanisms = plain login + mail_gid = 8 mail_home = /var/mail/%n mail_location = maildir:/var/mail/%n mail_uid = 500 + +protocols = imap sieve + +mail_plugins = $mail_plugins quota + +ssl = yes +ssl_cert = Date: Wed, 7 Sep 2016 18:37:53 +0200 Subject: [PATCH 010/208] [enh] Remove autocreate plugin related settings, unused now autosubscribe & autosubscribe2 are part of the autocreate plugin configuration. Should have been removed with f36e4e2a837ae1947aa6f196622f5e3bed637a9 See https://github.com/YunoHost/yunohost-config-dovecot/pull/3 --- data/templates/dovecot/dovecot.conf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/data/templates/dovecot/dovecot.conf b/data/templates/dovecot/dovecot.conf index af745c93d..fbaa61ca9 100644 --- a/data/templates/dovecot/dovecot.conf +++ b/data/templates/dovecot/dovecot.conf @@ -77,11 +77,6 @@ plugin { antispam_pipe_program_notspam_arg = learn_ham } -plugin { - autosubscribe = Trash - autosubscribe2 = Junk -} - plugin { quota = maildir:User quota quota_rule2 = SPAM:ignore From ea17e9fa80094d1a1c47059cdf428f876bd749f9 Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Fri, 9 Sep 2016 17:12:50 +0200 Subject: [PATCH 011/208] [enh] Allow for dovecot configuration extensions --- data/hooks/conf_regen/25-dovecot | 7 +++++++ data/templates/dovecot/dovecot.conf | 5 +++++ data/templates/dovecot/post-ext.conf | 1 + data/templates/dovecot/pre-ext.conf | 1 + 4 files changed, 14 insertions(+) create mode 100644 data/templates/dovecot/post-ext.conf create mode 100644 data/templates/dovecot/pre-ext.conf diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index 5d82470a5..4c5ae24c1 100755 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -26,11 +26,18 @@ do_pre_regen() { 's/^\(listen =\).*/\1 */' \ "${dovecot_dir}/dovecot.conf" fi + + mkdir -p "${dovecot_dir}/yunohost.d" + cp pre-ext.conf "${dovecot_dir}/yunohost.d" + cp post-ext.conf "${dovecot_dir}/yunohost.d" } do_post_regen() { regen_conf_files=$1 + sudo mkdir -p "/etc/dovecot/yunohost.d/pre-ext.d" + sudo mkdir -p "/etc/dovecot/yunohost.d/post-ext.d" + # create vmail user id vmail > /dev/null 2>&1 \ || sudo adduser --system --ingroup mail --uid 500 vmail diff --git a/data/templates/dovecot/dovecot.conf b/data/templates/dovecot/dovecot.conf index fbaa61ca9..5ea10ea79 100644 --- a/data/templates/dovecot/dovecot.conf +++ b/data/templates/dovecot/dovecot.conf @@ -1,3 +1,5 @@ +!include yunohost.d/pre-ext.conf + listen = *, :: auth_mechanisms = plain login @@ -10,6 +12,7 @@ protocols = imap sieve mail_plugins = $mail_plugins quota + ssl = yes ssl_cert = Date: Sun, 9 Oct 2016 21:34:03 +0200 Subject: [PATCH 012/208] [enh] no cli option to avoid removing an application on installation failure --- data/actionsmap/yunohost.yml | 3 +++ src/yunohost/app.py | 28 +++++++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 5a1465258..126993076 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -436,6 +436,9 @@ app: -a: full: --args help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path") + -n: + full: --no-remove-on-fail + help: Debug option to avoid removing the app on a filed installation ### app_remove() TODO: Write help remove: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index caa38e95b..5ac0cb971 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -440,7 +440,7 @@ def app_upgrade(auth, app=[], url=None, file=None): logger.success(m18n.n('upgrade_complete')) -def app_install(auth, app, label=None, args=None): +def app_install(auth, app, label=None, args=None, no_remove_on_fail=False): """ Install apps @@ -448,6 +448,7 @@ def app_install(auth, app, label=None, args=None): app -- Name, local path or git URL of the app to install label -- Custom name for the app args -- Serialize arguments for app installation + no_remove_on_fail -- Debug option to avoid removing the app on a filed installation """ from yunohost.hook import hook_add, hook_remove, hook_exec @@ -541,19 +542,20 @@ def app_install(auth, app, label=None, args=None): logger.exception(m18n.n('unexpected_error')) finally: if install_retcode != 0: - # Setup environment for remove script - env_dict_remove = {} - env_dict_remove["YNH_APP_ID"] = app_id - env_dict_remove["YNH_APP_INSTANCE_NAME"] = app_instance_name - env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(instance_number) + if not no_remove_on_fail: + # Setup environment for remove script + env_dict_remove = {} + env_dict_remove["YNH_APP_ID"] = app_id + env_dict_remove["YNH_APP_INSTANCE_NAME"] = app_instance_name + env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(instance_number) - # Execute remove script - remove_retcode = hook_exec( - os.path.join(extracted_app_folder, 'scripts/remove'), - args=[app_instance_name], env=env_dict_remove) - if remove_retcode != 0: - logger.warning(m18n.n('app_not_properly_removed', - app=app_instance_name)) + # Execute remove script + remove_retcode = hook_exec( + os.path.join(extracted_app_folder, 'scripts/remove'), + args=[app_instance_name], env=env_dict_remove) + if remove_retcode != 0: + logger.warning(m18n.n('app_not_properly_removed', + app=app_instance_name)) # Clean tmp folders shutil.rmtree(app_setting_path) From 76a294b28438e12b2d29f381ad941dc2bacf3bc8 Mon Sep 17 00:00:00 2001 From: Moul Date: Wed, 12 Oct 2016 09:39:05 +0200 Subject: [PATCH 013/208] =?UTF-8?q?[fix]=20typo:=20install=20filed=20?= =?UTF-8?q?=E2=80=93>=20failed.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/actionsmap/yunohost.yml | 2 +- src/yunohost/app.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 126993076..56b28cf38 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -438,7 +438,7 @@ app: help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path") -n: full: --no-remove-on-fail - help: Debug option to avoid removing the app on a filed installation + help: Debug option to avoid removing the app on a failed installation ### app_remove() TODO: Write help remove: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5ac0cb971..f85e9f191 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -448,7 +448,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_fail=False): app -- Name, local path or git URL of the app to install label -- Custom name for the app args -- Serialize arguments for app installation - no_remove_on_fail -- Debug option to avoid removing the app on a filed installation + no_remove_on_fail -- Debug option to avoid removing the app on a failed installation """ from yunohost.hook import hook_add, hook_remove, hook_exec From 7579eef600a24f674e319344eb3241b40b40fdf3 Mon Sep 17 00:00:00 2001 From: JimboJoe Date: Fri, 14 Oct 2016 18:03:58 +0200 Subject: [PATCH 014/208] Update backup.py if /home/yunohost.backup/archives is on a different device (for instance, a NAS), backups fail with the message: ... File "/usr/lib/moulinette/yunohost/backup.py", line 303, in backup_create '{:s}/{:s}.info.json'.format(archives_path, name)) OSError: [Errno 18] Invalid cross-device link A simple solution is to use shutil.move because, as stated in the documentation, "If the destination is on the current filesystem, then os.rename() is used. Otherwise, src is copied (using shutil.copy2()) to dst and then removed.", which is the needed behavior. This PR has been successfully tested. --- 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 32dda58a1..dd7c73852 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -299,7 +299,7 @@ def backup_create(name=None, description=None, output_directory=None, m18n.n('backup_creation_failed')) # Move info file - os.rename(tmp_dir + '/info.json', + shutil.move(tmp_dir + '/info.json', '{:s}/{:s}.info.json'.format(archives_path, name)) # Clean temporary directory From 35fa386ce3b9c52b469b1ae16400ab4c067e2bef Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 28 Oct 2016 13:59:42 -0400 Subject: [PATCH 015/208] First draft of certificate management integration (e.g. Let's Encrypt certificate install) --- data/actionsmap/yunohost.yml | 58 +++- locales/de.json | 2 +- locales/en.json | 18 +- locales/es.json | 2 +- locales/fr.json | 2 +- locales/nl.json | 2 +- locales/pt.json | 2 +- src/yunohost/app.py | 16 + src/yunohost/certificate.py | 641 +++++++++++++++++++++++++++++++++++ src/yunohost/domain.py | 46 +-- 10 files changed, 748 insertions(+), 41 deletions(-) create mode 100644 src/yunohost/certificate.py diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 5a1465258..4d0db97ec 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -305,8 +305,64 @@ domain: - !!str ^[0-9]+$ - "pattern_positive_number" + ### certificate_status() + cert-status: + action_help: List status of current certificates (all by default). + api: GET /certs/status/ + configuration: + authenticate: all + authenticator: ldap-anonymous + arguments: + domainList: + help: Domains to check + nargs: "*" + --full: + help: Show more details + action: store_true - ### domain_info() + ### certificate_install() + cert-install: + action_help: Install Let's Encrypt certificates for given domains (all by default). + api: POST /certs/enable/ + configuration: + authenticate: all + authenticator: ldap-anonymous + arguments: + domainList: + help: Domains for which to install the certificates + nargs: "*" + --force: + help: Install even if current certificate is not self-signed + action: store_true + --no-checks: + help: Does not perform any check that your domain seems correcly configured (DNS, reachability) before attempting to install. (Not recommended) + action: store_true + --self-signed: + help: Install self-signed certificate instead of Let's Encrypt + action: store_true + + ### certificate_renew() + cert-renew: + action_help: Renew the Let's Encrypt certificates for given domains (all by default). + api: POST /certs/renew/ + configuration: + authenticate: all + authenticator: ldap-anonymous + arguments: + domainList: + help: Domains for which to renew the certificates + nargs: "*" + --force: + help: Ignore the validity treshold (30 days) + action: store_true + --email: + help: Send an email to root with logs if some renewing fails + action: store_true + --no-checks: + help: Does not perform any check that your domain seems correcly configured (DNS, reachability) before attempting to renew. (Not recommended) + action: store_true + + ### domain_info() # info: # action_help: Get domain informations # api: GET /domains/ diff --git a/locales/de.json b/locales/de.json index 1331c56b4..e57315caa 100644 --- a/locales/de.json +++ b/locales/de.json @@ -57,7 +57,7 @@ "custom_app_url_required": "Es muss eine URL angegeben um deine benutzerdefinierte App {app:s} zu aktualisieren", "custom_appslist_name_required": "Du musst einen Namen für deine benutzerdefinierte Appliste angeben", "dnsmasq_isnt_installed": "dnsmasq scheint nicht installiert zu sein. Bitte führe 'apt-get remove bind9 && apt-get install dnsmasq' aus", - "domain_cert_gen_failed": "Zertifikat konnte nicht erzeugt werden", + "certmanager_domain_cert_gen_failed": "Zertifikat konnte nicht erzeugt werden", "domain_created": "Domain erfolgreich erzeugt", "domain_creation_failed": "Konnte Domain nicht erzeugen", "domain_deleted": "Domain erfolgreich gelöscht", diff --git a/locales/en.json b/locales/en.json index e939b26fa..35da2eef0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -71,7 +71,6 @@ "diagnostic_monitor_system_error": "Can't monitor system: {error}", "diagnostic_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_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", @@ -237,5 +236,20 @@ "yunohost_ca_creation_failed": "Unable to create certificate authority", "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'." + "yunohost_not_installed": "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'.", + "certmanager_domain_cert_gen_failed": "Unable to generate certificate", + "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_domain_unknown": "Unknown domain {domain:s}", + "certmanager_domain_cert_not_selfsigned" : "The certificate of domain {domain:s} is not self-signed. Are you sure you want to replace it ? (Use --force)", + "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow...", + "certmanager_attempt_to_renew_nonLE_cert" : "The certificate of domain {domain:s} is not issued by Let's Encrypt. Cannot renew it automatically !", + "certmanager_attempt_to_renew_valid_cert" : "The certificate of domain {domain:s} is not about to expire ! Use --force to bypass", + "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_error_contacting_dns_api" : "Error contacting the DNS API ({api:s}). Use --no-checks to disable checks.", + "certmanager_error_parsing_dns" : "Error parsing the return value from the DNS API : {value:s}. Please verify your DNS configuration for domain {domain:s}. Use --no-checks to disable checks.", + "certmanager_domain_dns_ip_differs_from_public_ip" : "The DNS 'A' record for domain {domain:s} is different from this server IP. Give some time for the DNS to refresh, or use --no-checks to disable checks.", + "certmanager_no_A_dns_record" : "No DNS record of type A found for {domain:s}. You need to configure the DNS for your domain before installing a certificate !", + "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file : {file:s})", + "certmanager_cert_install_success" : "Successfully installed Let's Encrypt certificate for domain {domain:s} !", + "certmanager_cert_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain:s} !" } diff --git a/locales/es.json b/locales/es.json index 549cbe29a..fdd04d10f 100644 --- a/locales/es.json +++ b/locales/es.json @@ -72,7 +72,7 @@ "diagnostic_monitor_system_error": "No se puede monitorizar el sistema: {error}", "diagnostic_no_apps": "Aplicación no instalada", "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecuta 'apt-get remove bind9 && apt-get install dnsmasq'", - "domain_cert_gen_failed": "No se pudo crear el certificado", + "certmanager_domain_cert_gen_failed": "No se pudo crear el certificado", "domain_created": "El dominio ha sido creado", "domain_creation_failed": "No se pudo crear el dominio", "domain_deleted": "El dominio ha sido eliminado", diff --git a/locales/fr.json b/locales/fr.json index 7898de57f..6691b2f28 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -73,7 +73,7 @@ "diagnostic_monitor_system_error": "Impossible de superviser le système : {error}", "diagnostic_no_apps": "Aucune application installée", "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer « apt-get remove bind9 && apt-get install dnsmasq »", - "domain_cert_gen_failed": "Impossible de générer le certificat", + "certmanager_domain_cert_gen_failed": "Impossible de générer le certificat", "domain_created": "Le domaine a été créé", "domain_creation_failed": "Impossible de créer le domaine", "domain_deleted": "Le domaine a été supprimé", diff --git a/locales/nl.json b/locales/nl.json index c2bfed31e..57b05e309 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -37,7 +37,7 @@ "custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {app:s} bij te werken", "custom_appslist_name_required": "U moet een naam opgeven voor uw aangepaste app-lijst", "dnsmasq_isnt_installed": "dnsmasq lijkt niet geïnstalleerd te zijn, voer alstublieft het volgende commando uit: 'apt-get remove bind9 && apt-get install dnsmasq'", - "domain_cert_gen_failed": "Kan certificaat niet genereren", + "certmanager_domain_cert_gen_failed": "Kan certificaat niet genereren", "domain_created": "Domein succesvol aangemaakt", "domain_creation_failed": "Kan domein niet aanmaken", "domain_deleted": "Domein succesvol verwijderd", diff --git a/locales/pt.json b/locales/pt.json index d3796d2e9..b9c9e4bce 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -36,7 +36,7 @@ "backup_output_directory_not_empty": "A pasta de destino não se encontra vazia", "custom_app_url_required": "Deve proporcionar uma URL para atualizar a sua aplicação personalizada {app:s}", "custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada", - "domain_cert_gen_failed": "Não foi possível gerar o certificado", + "certmanager_domain_cert_gen_failed": "Não foi possível gerar o certificado", "domain_created": "Domínio criado com êxito", "domain_creation_failed": "Não foi possível criar o domínio", "domain_deleted": "Domínio removido com êxito", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index caa38e95b..2326995ae 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1031,6 +1031,22 @@ def app_ssowatconf(auth): for domain in domains: skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) + # Authorize ACME challenge url if a domain seems configured for it... + for domain in domains: + + # Check ACME challenge file is present in nginx conf + nginx_acme_challenge_conf_file = "/etc/nginx/conf.d/"+domain+".d/000-acmechallenge.conf" + if not (os.path.isfile(nginx_acme_challenge_conf_file)) : continue + + # Check the file contains the ACME challenge uri + acme_uri = '/.well-known/acme-challenge' + if not (acme_uri in open(nginx_acme_challenge_conf_file).read()) : continue + + # If so, then authorize the ACME challenge uri to unprotected regex + regex = domain+"/%.well%-known/acme%-challenge/.*$" + unprotected_regex.append(regex) + + conf_dict = { 'portal_domain': main_domain, 'portal_path': '/yunohost/sso/', diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py new file mode 100644 index 000000000..4ed5cb588 --- /dev/null +++ b/src/yunohost/certificate.py @@ -0,0 +1,641 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2016 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 + +""" + +""" yunohost_certificate.py + + Manage certificates, in particular Let's encrypt +""" + +import os +import sys +import errno +import requests +import shutil +import pwd +import grp +import json +import smtplib + +from OpenSSL import crypto +from datetime import datetime +from tabulate import tabulate +from acme_tiny import get_crt as sign_certificate + +from moulinette.core import MoulinetteError +from moulinette.utils.log import getActionLogger +import yunohost.domain +from yunohost.service import _run_service_command +from yunohost.app import app_setting, app_ssowatconf + + +logger = getActionLogger('yunohost.certmanager') + +# Misc stuff we need + +cert_folder = "/etc/yunohost/certs/" +tmp_folder = "/tmp/acme-challenge-private/" +webroot_folder = "/tmp/acme-challenge-public/" + +selfCA_file = "/etc/ssl/certs/ca-yunohost_crt.pem" +account_key_file = "/etc/yunohost/letsencrypt_account.pem" + +key_size = 2048 + +validity_limit = 15 # days + +# For tests +#certification_authority = "https://acme-staging.api.letsencrypt.org" +# For prod +certification_authority = "https://acme-v01.api.letsencrypt.org" + +intermediate_certificate_url = "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem" + +############################################################################### +# Front-end stuff # +############################################################################### + +# Status + +def certificate_status(auth, domainList, full = False): + """ + Print the status of certificate for given domains (all by default) + + Keyword argument: + domainList -- Domains to be checked + full -- Display more info about the certificates + """ + + # If no domains given, consider all yunohost domains + if (domainList == []) : domainList = yunohost.domain.domain_list(auth)['domains'] + # Else, validate that yunohost knows the domains given + else : + for domain in domainList : + # Is it in Yunohost dmomain list ? + if domain not in yunohost.domain.domain_list(auth)['domains']: + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) + + # Get status for each domain, and prepare display using tabulate + if not full : + headers = [ "Domain", "Certificate status", "Authority type", "Days remaining"] + else : + headers = [ "Domain", "Certificate subject", "Certificate status", "Authority type", "Authority name", "Days remaining"] + lines = [] + for domain in domainList : + status = _get_status(domain) + + line = [] + line.append(domain) + if (full) : line.append(status["subject"]) + line.append(_summary_code_to_string(status["summaryCode"])) + line.append(status["CAtype"]) + if (full) : line.append(status["CAname"]) + line.append(status["validity"]) + lines.append(line) + + print(tabulate(lines, headers=headers, tablefmt="simple", stralign="center")) + + +def certificate_install(auth, domainList, force=False, no_checks=False, self_signed=False) : + """ + Install a Let's Encrypt certificate for given domains (all by default) + + Keyword argument: + domainList -- Domains on which to install certificates + force -- Install even if current certificate is not self-signed + no-check -- Disable some checks about the reachability of web server + before attempting the install + self-signed -- Instal self-signed certificates instead of Let's Encrypt + """ + if (self_signed) : + certificate_install_selfsigned(domainList, force) + else : + certificate_install_letsencrypt(auth, domainList, force, no_checks) + + +# Install self-signed + +def certificate_install_selfsigned(domainList, force=False) : + + for domain in domainList : + + # Check we ain't trying to overwrite a good cert ! + status = _get_status(domain) + if (status != {}) and (status["summaryCode"] > 0) and (not force) : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) + + + cert_folder_domain = cert_folder+"/"+domain + + # Create cert folder if it does not exists yet + try: + os.listdir(cert_folder_domain) + except OSError: + os.makedirs(cert_folder_domain) + + # Get serial + ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' + with open('%s/serial' % ssl_dir, 'r') as f: + serial = f.readline().rstrip() + + # FIXME : should refactor this to avoid so many os.system() calls... + # We should be able to do all this using OpenSSL.crypto and os/shutil + command_list = [ + 'cp %s/openssl.cnf %s' % (ssl_dir, cert_folder_domain), + 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, cert_folder_domain), + 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch' + % (cert_folder_domain, ssl_dir, ssl_dir), + 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch' + % (cert_folder_domain, ssl_dir, ssl_dir), + 'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % cert_folder_domain, + 'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, cert_folder_domain), + 'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, cert_folder_domain), + 'cat %s/ca.pem >> %s/crt.pem' % (cert_folder_domain, cert_folder_domain) + ] + + for command in command_list: + if os.system(command) != 0: + raise MoulinetteError(errno.EIO, m18n.n('certmanager_domain_cert_gen_failed')) + + _set_permissions(cert_folder_domain, "root", "root", 0755); + _set_permissions(cert_folder_domain+"/key.pem", "root", "metronome", 0640); + _set_permissions(cert_folder_domain+"/crt.pem", "root", "metronome", 0640); + _set_permissions(cert_folder_domain+"/openssl.cnf", "root", "root", 0600); + + + +# Install ACME / Let's Encrypt certificate + +def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=False): + + + if not os.path.exists(account_key_file) : + _generate_account_key() + + # If no domains given, consider all yunohost domains with self-signed + # certificates + if (domainList == []) : + for domain in yunohost.domain.domain_list(auth)['domains'] : + + # Is it self-signed ? + status = _get_status(domain) + if (status["CAtype"] != "Self-signed") : continue + + domainList.append(domain) + + # Else, validate that yunohost knows the domains given + else : + for domain in domainList : + # Is it in Yunohost dmomain list ? + if domain not in yunohost.domain.domain_list(auth)['domains']: + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) + + # Is it self-signed ? + status = _get_status(domain) + if (not force) and (status["CAtype"] != "Self-signed") : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_cert_not_selfsigned', domain=domain)) + + + # Actual install steps + for domain in domainList : + + logger.info("Now attempting install of certificate for domain "+domain+" !") + + try : + + if not no_checks : _check_domain_is_correctly_configured(domain) + _backup_current_cert(domain) + _configure_for_acme_challenge(auth, domain) + _fetch_and_enable_new_certificate(domain) + _install_cron() + + logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) + + except Exception as e : + + logger.error("Certificate installation for "+domain+" failed !") + logger.error(str(e)) + + +# Renew + + +def certificate_renew(auth, domainList, force=False, no_checks=False, email=False): + """ + Renew Let's Encrypt certificate for given domains (all by default) + + Keyword argument: + domainList -- Domains for which to renew the certificates + force -- Ignore the validity threshold (15 days) + no-check -- Disable some checks about the reachability of web server + before attempting the renewing + email -- Emails root if some renewing failed + """ + + # If no domains given, consider all yunohost domains with Let's Encrypt + # certificates + if (domainList == []) : + for domain in yunohost.domain.domain_list(auth)['domains'] : + + # Does it has a Let's Encrypt cert ? + status = _get_status(domain) + if (status["CAtype"] != "Let's Encrypt") : continue + + # Does it expires soon ? + if (force) or (status["validity"] <= validity_limit) : + domainList.append(domain) + + if (len(domainList) == 0) : + logger.info("No certificate needs to be renewed.") + + # Else, validate the domain list given + else : + for domain in domainList : + + # Is it in Yunohost dmomain list ? + if domain not in yunohost.domain.domain_list(auth)['domains']: + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) + + status = _get_status(domain) + + # Does it expires soon ? + if not ((force) or (status["validity"] <= validity_limit)) : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain)) + + + # Does it has a Let's Encrypt cert ? + if (status["CAtype"] != "Let's Encrypt") : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain)) + + + # Actual renew steps + for domain in domainList : + + logger.info("Now attempting renewing of certificate for domain "+domain+" !") + + try : + + if not no_checks : _check_domain_is_correctly_configured(domain) + _backup_current_cert(domain) + _fetch_and_enable_new_certificate(domain) + + logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) + + except Exception as e : + + logger.error("Certificate renewing for "+domain+" failed !") + logger.error(str(e)) + + if (email) : + logger.error("Sending email with details to root ...") + _email_renewing_failed(domain, e) + + + + + +############################################################################### +# Back-end stuff # +############################################################################### + +def _install_cron() : + + cron_job_file = "/etc/cron.weekly/certificateRenewer" + + with open(cron_job_file, "w") as f : + + f.write("#!/bin/bash\n") + f.write("yunohost domain cert-renew --email\n") + + _set_permissions(cron_job_file, "root", "root", 0755); + +def _email_renewing_failed(domain, e) : + + from_ = "certmanager@"+domain+" (Certificate Manager)" + to_ = "root" + subject_ = "Certificate renewing attempt for "+domain+" failed!" + + exceptionMessage = str(e) + logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") + text = """ +At attempt for renewing the certificate for domain %s failed with the following +error : + +%s + +Here's the tail of /var/log/yunohost/yunohost-cli.log, which might help to +investigate : + +%s + +-- Certificate Manager + +""" % (domain, exceptionMessage, logs) + + message = """ +From: %s +To: %s +Subject: %s + +%s +""" % (from_, to_, subject_, text) + + smtp = smtplib.SMTP("localhost") + smtp.sendmail(from_, [ to_ ], message) + smtp.quit() + + + +def _configure_for_acme_challenge(auth, domain) : + + nginx_conf_file = "/etc/nginx/conf.d/"+domain+".d/000-acmechallenge.conf" + + nginx_configuration = ''' +location '/.well-known/acme-challenge' +{ + default_type "text/plain"; + alias '''+webroot_folder+'''; +} + ''' + + # Write the conf + if os.path.exists(nginx_conf_file) : + logger.info("Nginx configuration file for ACME challenge already exists for domain, skipping.") + else : + logger.info("Adding Nginx configuration file for Acme challenge for domain " + domain + ".") + with open(nginx_conf_file, "w") as f : + f.write(nginx_configuration) + + # Assume nginx conf is okay, and reload it + # (FIXME : maybe add a check that it is, using nginx -t, haven't found + # any clean function already implemented in yunohost to do this though) + _run_service_command("reload", "nginx") + + app_ssowatconf(auth) + +def _fetch_and_enable_new_certificate(domain) : + + # Make sure tmp folder exists + logger.debug("Making sure tmp folders exists...") + + if not (os.path.exists(webroot_folder)) : os.makedirs(webroot_folder) + if not (os.path.exists(tmp_folder)) : os.makedirs(tmp_folder) + _set_permissions(webroot_folder, "root", "www-data", 0650); + _set_permissions(tmp_folder, "root", "root", 0640); + + # Prepare certificate signing request + logger.info("Prepare key and certificate signing request (CSR) for "+domain+"...") + + domain_key_file = tmp_folder+"/"+domain+".pem" + _generate_key(domain_key_file) + _set_permissions(domain_key_file, "root", "metronome", 0640); + + _prepare_certificate_signing_request(domain, domain_key_file, tmp_folder) + + # Sign the certificate + logger.info("Now using ACME Tiny to sign the certificate...") + + domain_csr_file = tmp_folder+"/"+domain+".csr" + + signed_certificate = sign_certificate(account_key_file, + domain_csr_file, + webroot_folder, + log=logger, + CA=certification_authority) + intermediate_certificate = requests.get(intermediate_certificate_url).text + + # Now save the key and signed certificate + logger.info("Saving the key and signed certificate...") + + # Create corresponding directory + date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") + new_cert_folder = cert_folder+"/" + domain + "." + date_tag + os.makedirs(new_cert_folder) + _set_permissions(new_cert_folder, "root", "root", 0655); + + # Move the private key + shutil.move(domain_key_file, new_cert_folder+"/key.pem") + + # Write the cert + domain_cert_file = new_cert_folder+"/crt.pem" + with open(domain_cert_file, "w") as f : + f.write(signed_certificate) + f.write(intermediate_certificate) + _set_permissions(domain_cert_file, "root", "metronome", 0640); + + + + logger.info("Enabling the new certificate...") + + # Replace (if necessary) the link or folder for live cert + live_link = cert_folder+"/"+domain + + if not os.path.islink(live_link) : + shutil.rmtree(live_link) # Well, yep, hopefully that's not too dangerous (directory should have been backuped before calling this command) + elif os.path.lexists(live_link) : + os.remove(live_link) + + os.symlink(new_cert_folder, live_link) + + # Check the status of the certificate is now good + + statusSummaryCode = _get_status(domain)["summaryCode"] + if (statusSummaryCode < 20) : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_certificate_fetching_or_enabling_failed', domain=domain)) + + + logger.info("Restarting services...") + + for s in [ "nginx", "postfix", "dovecot", "metronome" ] : + _run_service_command("restart", s) + + +def _prepare_certificate_signing_request(domain, key_file, output_folder) : + + # Init a request + csr = crypto.X509Req() + + # Set the domain + csr.get_subject().CN = domain + + # Set the key + with open(key_file, 'rt') as f : + key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) + csr.set_pubkey(key) + + # Sign the request + csr.sign(key, "sha256") + + # Save the request in tmp folder + csr_file = output_folder+domain+".csr" + logger.info("Saving to "+csr_file+" .") + with open(csr_file, "w") as f : + f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr)) + + +def _get_status(domain) : + + cert_file = cert_folder+"/"+domain+"/crt.pem" + + if (not os.path.isfile(cert_file)) : return {} + + try : + cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file).read()) + except : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file)) + + certSubject = cert.get_subject().CN + certIssuer = cert.get_issuer().CN + validUpTo = datetime.strptime(cert.get_notAfter(),"%Y%m%d%H%M%SZ") + daysRemaining = (validUpTo - datetime.now()).days + + CAtype = None + if (certIssuer == _name_selfCA()) : + CAtype = "Self-signed" + elif (certIssuer.startswith("Let's Encrypt")) : + CAtype = "Let's Encrypt" + elif (certIssuer.startswith("Fake LE")) : + CAtype = "Fake Let's Encrypt" + else : + CAtype = "Other / Unknown" + + # Unknown by default + statusSummaryCode = 0 + # Critical + if (daysRemaining <= 0) : statusSummaryCode = -30 + # Warning, self-signed, browser will display a warning discouraging visitors to enter website + elif (CAtype == "Self-signed") or (CAtype == "Fake Let's Encrypt") : statusSummaryCode = -20 + # Attention, certificate will expire soon (should be renewed automatically if Let's Encrypt) + elif (daysRemaining < validity_limit) : statusSummaryCode = -10 + # CA not known, but still a valid certificate, so okay ! + elif (CAtype == "Other / Unknown") : statusSummaryCode = 10 + # Let's Encrypt, great ! + elif (CAtype == "Let's Encrypt") : statusSummaryCode = 20 + + return { "domain" : domain, + "subject" : certSubject, + "CAname" : certIssuer, + "CAtype" : CAtype, + "validity" : daysRemaining, + "summaryCode" : statusSummaryCode + } + +############################################################################### +# Misc small stuff ... # +############################################################################### + +def _generate_account_key() : + + logger.info("Generating account key ...") + _generate_key(account_key_file) + _set_permissions(account_key_file, "root", "root", 0400) + +def _generate_key(destinationPath) : + + k = crypto.PKey() + k.generate_key(crypto.TYPE_RSA, key_size) + + with open(destinationPath, "w") as f : + f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k)) + +def _set_permissions(path, user, group, permissions) : + + uid = pwd.getpwnam(user).pw_uid + gid = grp.getgrnam(group).gr_gid + + os.chown(path, uid, gid) + os.chmod(path, permissions) + +def _backup_current_cert(domain): + + logger.info("Backuping existing certificate for domain "+domain) + + cert_folder_domain = cert_folder+"/"+domain + + dateTag = datetime.now().strftime("%Y%m%d.%H%M%S") + backup_folder = cert_folder_domain+"-backup-"+dateTag + + shutil.copytree(cert_folder_domain, backup_folder) + + +def _check_domain_is_correctly_configured(domain) : + + public_ip = yunohost.domain.get_public_ip() + + # Check if IP from DNS matches public IP + if not _dns_ip_match_public_ip(public_ip, domain) : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_dns_ip_differs_from_public_ip', domain=domain)) + + # Check if domain seems to be accessible through HTTP ? + if not _domain_is_accessible_through_HTTP(public_ip, domain) : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_http_not_working', domain=domain)) + +def _dns_ip_match_public_ip(public_ip, domain) : + + try : + r = requests.get("http://dns-api.org/A/"+domain) + except : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_contacting_dns_api', api="dns-api.org")) + + if (r.text == "[{\"error\":\"NXDOMAIN\"}]") : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_A_dns_record', domain=domain)) + + try : + dns_ip = json.loads(r.text)[0]["value"] + except : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=r.text)) + + if (dns_ip != public_ip) : + return False + else : + return True + +def _domain_is_accessible_through_HTTP(ip, domain) : + + try : + requests.head("http://"+ip, headers = { "Host" : domain }) + except Exception : + return False + + return True + +def _summary_code_to_string(code) : + + if (code <= -30) : return "CRITICAL" + elif (code <= -20) : return "WARNING" + elif (code <= -10) : return "Attention" + elif (code <= 0) : return "Unknown?" + elif (code <= 10) : return "Good" + elif (code <= 20) : return "Great!" + + return "Unknown?" + +def _name_selfCA() : + + cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(selfCA_file).read()) + return cert.get_subject().CN + + +def _tail(n, filePath): + stdin,stdout = os.popen2("tail -n "+str(n)+" "+filePath) + stdin.close() + lines = stdout.readlines(); stdout.close() + lines = "".join(lines) + return lines diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 98fa368ed..58ca41b08 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -34,10 +34,11 @@ import errno import requests from urllib import urlopen -from moulinette.core import MoulinetteError +from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from yunohost.service import service_regen_conf +from yunohost.service import service_regen_conf +import yunohost.certificate logger = getActionLogger('yunohost.domain') @@ -113,37 +114,7 @@ def domain_add(auth, domain, dyndns=False): m18n.n('domain_dyndns_root_unknown')) try: - # Commands - ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' - ssl_domain_path = '/etc/yunohost/certs/%s' % domain - with open('%s/serial' % ssl_dir, 'r') as f: - serial = f.readline().rstrip() - try: os.listdir(ssl_domain_path) - except OSError: os.makedirs(ssl_domain_path) - - command_list = [ - 'cp %s/openssl.cnf %s' % (ssl_dir, ssl_domain_path), - 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, ssl_domain_path), - 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch' - % (ssl_domain_path, ssl_dir, ssl_dir), - 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch' - % (ssl_domain_path, ssl_dir, ssl_dir), - 'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % ssl_domain_path, - 'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, ssl_domain_path), - 'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, ssl_domain_path), - 'chmod 755 %s' % ssl_domain_path, - 'chmod 640 %s/key.pem' % ssl_domain_path, - 'chmod 640 %s/crt.pem' % ssl_domain_path, - 'chmod 600 %s/openssl.cnf' % ssl_domain_path, - 'chown root:metronome %s/key.pem' % ssl_domain_path, - 'chown root:metronome %s/crt.pem' % ssl_domain_path, - 'cat %s/ca.pem >> %s/crt.pem' % (ssl_domain_path, ssl_domain_path) - ] - - for command in command_list: - if os.system(command) != 0: - raise MoulinetteError(errno.EIO, - m18n.n('domain_cert_gen_failed')) + yunohost.certificate.certificate_install_selfsigned([domain], False) try: auth.validate_uniqueness({ 'virtualdomain': domain }) @@ -285,6 +256,14 @@ def domain_dns_conf(domain, ttl=None): return result +def domain_cert_status(auth, domainList, full=False): + return yunohost.certificate.certificate_status(auth, domainList, full) + +def domain_cert_install(auth, domainList, force=False, no_checks=False, self_signed=False): + return yunohost.certificate.certificate_install(auth, domainList, force, no_checks, self_signed) + +def domain_cert_renew(auth, domainList, force=False, no_checks=False, email=False): + return yunohost.certificate.certificate_renew(auth, domainList, force, no_checks, email) def get_public_ip(protocol=4): """Retrieve the public IP address from ip.yunohost.org""" @@ -301,3 +280,4 @@ def get_public_ip(protocol=4): logger.debug('cannot retrieve public IPv%d' % protocol, exc_info=1) raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) + From d47f5919d679e9cbb87b6980e700735e975b72d8 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 04:22:56 +0100 Subject: [PATCH 016/208] [mod] remove unused imports --- src/yunohost/certificate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 4ed5cb588..69a78964e 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -25,7 +25,6 @@ """ import os -import sys import errno import requests import shutil @@ -43,7 +42,7 @@ from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger import yunohost.domain from yunohost.service import _run_service_command -from yunohost.app import app_setting, app_ssowatconf +from yunohost.app import app_ssowatconf logger = getActionLogger('yunohost.certmanager') From bec8f6347959cb5ab38cb24842703e5858bef8f4 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 04:24:54 +0100 Subject: [PATCH 017/208] [mod] autopep8 --- src/yunohost/certificate.py | 415 ++++++++++++++++++------------------ 1 file changed, 210 insertions(+), 205 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 69a78964e..e11ea9e3b 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -33,32 +33,34 @@ import grp import json import smtplib -from OpenSSL import crypto -from datetime import datetime -from tabulate import tabulate -from acme_tiny import get_crt as sign_certificate +from OpenSSL import crypto +from datetime import datetime +from tabulate import tabulate +from acme_tiny import get_crt as sign_certificate -from moulinette.core import MoulinetteError -from moulinette.utils.log import getActionLogger import yunohost.domain -from yunohost.service import _run_service_command -from yunohost.app import app_ssowatconf + +from moulinette.core import MoulinetteError +from moulinette.utils.log import getActionLogger + +from yunohost.app import app_ssowatconf +from yunohost.service import _run_service_command logger = getActionLogger('yunohost.certmanager') # Misc stuff we need -cert_folder = "/etc/yunohost/certs/" -tmp_folder = "/tmp/acme-challenge-private/" -webroot_folder = "/tmp/acme-challenge-public/" +cert_folder = "/etc/yunohost/certs/" +tmp_folder = "/tmp/acme-challenge-private/" +webroot_folder = "/tmp/acme-challenge-public/" -selfCA_file = "/etc/ssl/certs/ca-yunohost_crt.pem" +selfCA_file = "/etc/ssl/certs/ca-yunohost_crt.pem" account_key_file = "/etc/yunohost/letsencrypt_account.pem" -key_size = 2048 +key_size = 2048 -validity_limit = 15 # days +validity_limit = 15 # days # For tests #certification_authority = "https://acme-staging.api.letsencrypt.org" @@ -73,7 +75,8 @@ intermediate_certificate_url = "https://letsencrypt.org/certs/lets-encrypt-x3-cr # Status -def certificate_status(auth, domainList, full = False): + +def certificate_status(auth, domainList, full=False): """ Print the status of certificate for given domains (all by default) @@ -83,36 +86,39 @@ def certificate_status(auth, domainList, full = False): """ # If no domains given, consider all yunohost domains - if (domainList == []) : domainList = yunohost.domain.domain_list(auth)['domains'] + if (domainList == []): + domainList = yunohost.domain.domain_list(auth)['domains'] # Else, validate that yunohost knows the domains given - else : - for domain in domainList : + else: + for domain in domainList: # Is it in Yunohost dmomain list ? if domain not in yunohost.domain.domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) # Get status for each domain, and prepare display using tabulate - if not full : - headers = [ "Domain", "Certificate status", "Authority type", "Days remaining"] - else : - headers = [ "Domain", "Certificate subject", "Certificate status", "Authority type", "Authority name", "Days remaining"] + if not full: + headers = ["Domain", "Certificate status", "Authority type", "Days remaining"] + else: + headers = ["Domain", "Certificate subject", "Certificate status", "Authority type", "Authority name", "Days remaining"] lines = [] - for domain in domainList : + for domain in domainList: status = _get_status(domain) line = [] line.append(domain) - if (full) : line.append(status["subject"]) + if (full): + line.append(status["subject"]) line.append(_summary_code_to_string(status["summaryCode"])) line.append(status["CAtype"]) - if (full) : line.append(status["CAname"]) + if (full): + line.append(status["CAname"]) line.append(status["validity"]) lines.append(line) print(tabulate(lines, headers=headers, tablefmt="simple", stralign="center")) -def certificate_install(auth, domainList, force=False, no_checks=False, self_signed=False) : +def certificate_install(auth, domainList, force=False, no_checks=False, self_signed=False): """ Install a Let's Encrypt certificate for given domains (all by default) @@ -122,31 +128,29 @@ def certificate_install(auth, domainList, force=False, no_checks=False, self_sig no-check -- Disable some checks about the reachability of web server before attempting the install self-signed -- Instal self-signed certificates instead of Let's Encrypt - """ - if (self_signed) : + """ + if (self_signed): certificate_install_selfsigned(domainList, force) - else : + else: certificate_install_letsencrypt(auth, domainList, force, no_checks) -# Install self-signed +# Install self-signed -def certificate_install_selfsigned(domainList, force=False) : - - for domain in domainList : +def certificate_install_selfsigned(domainList, force=False): + for domain in domainList: # Check we ain't trying to overwrite a good cert ! status = _get_status(domain) - if (status != {}) and (status["summaryCode"] > 0) and (not force) : + if (status != {}) and (status["summaryCode"] > 0) and (not force): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) - - cert_folder_domain = cert_folder+"/"+domain + cert_folder_domain = cert_folder + "/" + domain # Create cert folder if it does not exists yet - try: + try: os.listdir(cert_folder_domain) - except OSError: + except OSError: os.makedirs(cert_folder_domain) # Get serial @@ -157,79 +161,77 @@ def certificate_install_selfsigned(domainList, force=False) : # FIXME : should refactor this to avoid so many os.system() calls... # We should be able to do all this using OpenSSL.crypto and os/shutil command_list = [ - 'cp %s/openssl.cnf %s' % (ssl_dir, cert_folder_domain), - 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, cert_folder_domain), + 'cp %s/openssl.cnf %s' % (ssl_dir, cert_folder_domain), + 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, cert_folder_domain), 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch' % (cert_folder_domain, ssl_dir, ssl_dir), 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch' % (cert_folder_domain, ssl_dir, ssl_dir), 'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % cert_folder_domain, - 'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, cert_folder_domain), - 'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, cert_folder_domain), - 'cat %s/ca.pem >> %s/crt.pem' % (cert_folder_domain, cert_folder_domain) + 'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, cert_folder_domain), + 'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, cert_folder_domain), + 'cat %s/ca.pem >> %s/crt.pem' % (cert_folder_domain, cert_folder_domain) ] for command in command_list: if os.system(command) != 0: raise MoulinetteError(errno.EIO, m18n.n('certmanager_domain_cert_gen_failed')) - _set_permissions(cert_folder_domain, "root", "root", 0755); - _set_permissions(cert_folder_domain+"/key.pem", "root", "metronome", 0640); - _set_permissions(cert_folder_domain+"/crt.pem", "root", "metronome", 0640); - _set_permissions(cert_folder_domain+"/openssl.cnf", "root", "root", 0600); - + _set_permissions(cert_folder_domain, "root", "root", 0755) + _set_permissions(cert_folder_domain + "/key.pem", "root", "metronome", 0640) + _set_permissions(cert_folder_domain + "/crt.pem", "root", "metronome", 0640) + _set_permissions(cert_folder_domain + "/openssl.cnf", "root", "root", 0600) # Install ACME / Let's Encrypt certificate def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=False): - - - if not os.path.exists(account_key_file) : + if not os.path.exists(account_key_file): _generate_account_key() # If no domains given, consider all yunohost domains with self-signed # certificates - if (domainList == []) : - for domain in yunohost.domain.domain_list(auth)['domains'] : + if (domainList == []): + for domain in yunohost.domain.domain_list(auth)['domains']: # Is it self-signed ? status = _get_status(domain) - if (status["CAtype"] != "Self-signed") : continue + if (status["CAtype"] != "Self-signed"): + continue domainList.append(domain) # Else, validate that yunohost knows the domains given - else : - for domain in domainList : + else: + for domain in domainList: # Is it in Yunohost dmomain list ? if domain not in yunohost.domain.domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) # Is it self-signed ? status = _get_status(domain) - if (not force) and (status["CAtype"] != "Self-signed") : + if (not force) and (status["CAtype"] != "Self-signed"): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_cert_not_selfsigned', domain=domain)) - # Actual install steps - for domain in domainList : - - logger.info("Now attempting install of certificate for domain "+domain+" !") + for domain in domainList: - try : + logger.info("Now attempting install of certificate for domain " + domain + " !") - if not no_checks : _check_domain_is_correctly_configured(domain) + try: + + if not no_checks: + _check_domain_is_correctly_configured(domain) _backup_current_cert(domain) _configure_for_acme_challenge(auth, domain) _fetch_and_enable_new_certificate(domain) _install_cron() - + logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) - - except Exception as e : - - logger.error("Certificate installation for "+domain+" failed !") + + except Exception as e: + + logger.error("Certificate installation for " + domain + " failed !") logger.error(str(e)) @@ -250,86 +252,82 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals # If no domains given, consider all yunohost domains with Let's Encrypt # certificates - if (domainList == []) : - for domain in yunohost.domain.domain_list(auth)['domains'] : + if (domainList == []): + for domain in yunohost.domain.domain_list(auth)['domains']: # Does it has a Let's Encrypt cert ? status = _get_status(domain) - if (status["CAtype"] != "Let's Encrypt") : continue + if (status["CAtype"] != "Let's Encrypt"): + continue # Does it expires soon ? - if (force) or (status["validity"] <= validity_limit) : + if (force) or (status["validity"] <= validity_limit): domainList.append(domain) - if (len(domainList) == 0) : + if (len(domainList) == 0): logger.info("No certificate needs to be renewed.") # Else, validate the domain list given - else : - for domain in domainList : + else: + for domain in domainList: # Is it in Yunohost dmomain list ? if domain not in yunohost.domain.domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) - + status = _get_status(domain) # Does it expires soon ? - if not ((force) or (status["validity"] <= validity_limit)) : + if not ((force) or (status["validity"] <= validity_limit)): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain)) - # Does it has a Let's Encrypt cert ? - if (status["CAtype"] != "Let's Encrypt") : + if (status["CAtype"] != "Let's Encrypt"): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain)) - # Actual renew steps - for domain in domainList : - - logger.info("Now attempting renewing of certificate for domain "+domain+" !") + for domain in domainList: - try : + logger.info("Now attempting renewing of certificate for domain " + domain + " !") - if not no_checks : _check_domain_is_correctly_configured(domain) + try: + + if not no_checks: + _check_domain_is_correctly_configured(domain) _backup_current_cert(domain) _fetch_and_enable_new_certificate(domain) logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) - - except Exception as e : - - logger.error("Certificate renewing for "+domain+" failed !") + + except Exception as e: + + logger.error("Certificate renewing for " + domain + " failed !") logger.error(str(e)) - if (email) : + if (email): logger.error("Sending email with details to root ...") _email_renewing_failed(domain, e) - - - ############################################################################### # Back-end stuff # ############################################################################### -def _install_cron() : - +def _install_cron(): cron_job_file = "/etc/cron.weekly/certificateRenewer" - with open(cron_job_file, "w") as f : + with open(cron_job_file, "w") as f: f.write("#!/bin/bash\n") f.write("yunohost domain cert-renew --email\n") - _set_permissions(cron_job_file, "root", "root", 0755); + _set_permissions(cron_job_file, "root", "root", 0755) -def _email_renewing_failed(domain, e) : - from_ = "certmanager@"+domain+" (Certificate Manager)" - to_ = "root" - subject_ = "Certificate renewing attempt for "+domain+" failed!" +def _email_renewing_failed(domain, e): + from_ = "certmanager@" + domain + " (Certificate Manager)" + to_ = "root" + subject_ = "Certificate renewing attempt for " + domain + " failed!" exceptionMessage = str(e) logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") @@ -357,29 +355,27 @@ Subject: %s """ % (from_, to_, subject_, text) smtp = smtplib.SMTP("localhost") - smtp.sendmail(from_, [ to_ ], message) + smtp.sendmail(from_, [to_], message) smtp.quit() - -def _configure_for_acme_challenge(auth, domain) : - - nginx_conf_file = "/etc/nginx/conf.d/"+domain+".d/000-acmechallenge.conf" +def _configure_for_acme_challenge(auth, domain): + nginx_conf_file = "/etc/nginx/conf.d/" + domain + ".d/000-acmechallenge.conf" nginx_configuration = ''' location '/.well-known/acme-challenge' { default_type "text/plain"; - alias '''+webroot_folder+'''; + alias ''' + webroot_folder + '''; } ''' # Write the conf - if os.path.exists(nginx_conf_file) : + if os.path.exists(nginx_conf_file): logger.info("Nginx configuration file for ACME challenge already exists for domain, skipping.") - else : + else: logger.info("Adding Nginx configuration file for Acme challenge for domain " + domain + ".") - with open(nginx_conf_file, "w") as f : + with open(nginx_conf_file, "w") as f: f.write(nginx_configuration) # Assume nginx conf is okay, and reload it @@ -389,29 +385,31 @@ location '/.well-known/acme-challenge' app_ssowatconf(auth) -def _fetch_and_enable_new_certificate(domain) : +def _fetch_and_enable_new_certificate(domain): # Make sure tmp folder exists logger.debug("Making sure tmp folders exists...") - if not (os.path.exists(webroot_folder)) : os.makedirs(webroot_folder) - if not (os.path.exists(tmp_folder)) : os.makedirs(tmp_folder) - _set_permissions(webroot_folder, "root", "www-data", 0650); - _set_permissions(tmp_folder, "root", "root", 0640); + if not (os.path.exists(webroot_folder)): + os.makedirs(webroot_folder) + if not (os.path.exists(tmp_folder)): + os.makedirs(tmp_folder) + _set_permissions(webroot_folder, "root", "www-data", 0650) + _set_permissions(tmp_folder, "root", "root", 0640) # Prepare certificate signing request - logger.info("Prepare key and certificate signing request (CSR) for "+domain+"...") + logger.info("Prepare key and certificate signing request (CSR) for " + domain + "...") - domain_key_file = tmp_folder+"/"+domain+".pem" + domain_key_file = tmp_folder + "/" + domain + ".pem" _generate_key(domain_key_file) - _set_permissions(domain_key_file, "root", "metronome", 0640); + _set_permissions(domain_key_file, "root", "metronome", 0640) _prepare_certificate_signing_request(domain, domain_key_file, tmp_folder) # Sign the certificate logger.info("Now using ACME Tiny to sign the certificate...") - domain_csr_file = tmp_folder+"/"+domain+".csr" + domain_csr_file = tmp_folder + "/" + domain + ".csr" signed_certificate = sign_certificate(account_key_file, domain_csr_file, @@ -425,30 +423,28 @@ def _fetch_and_enable_new_certificate(domain) : # Create corresponding directory date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") - new_cert_folder = cert_folder+"/" + domain + "." + date_tag + new_cert_folder = cert_folder + "/" + domain + "." + date_tag os.makedirs(new_cert_folder) - _set_permissions(new_cert_folder, "root", "root", 0655); + _set_permissions(new_cert_folder, "root", "root", 0655) # Move the private key - shutil.move(domain_key_file, new_cert_folder+"/key.pem") + shutil.move(domain_key_file, new_cert_folder + "/key.pem") # Write the cert - domain_cert_file = new_cert_folder+"/crt.pem" - with open(domain_cert_file, "w") as f : + domain_cert_file = new_cert_folder + "/crt.pem" + with open(domain_cert_file, "w") as f: f.write(signed_certificate) f.write(intermediate_certificate) - _set_permissions(domain_cert_file, "root", "metronome", 0640); - - + _set_permissions(domain_cert_file, "root", "metronome", 0640) logger.info("Enabling the new certificate...") # Replace (if necessary) the link or folder for live cert - live_link = cert_folder+"/"+domain + live_link = cert_folder + "/" + domain - if not os.path.islink(live_link) : - shutil.rmtree(live_link) # Well, yep, hopefully that's not too dangerous (directory should have been backuped before calling this command) - elif os.path.lexists(live_link) : + if not os.path.islink(live_link): + shutil.rmtree(live_link) # Well, yep, hopefully that's not too dangerous (directory should have been backuped before calling this command) + elif os.path.lexists(live_link): os.remove(live_link) os.symlink(new_cert_folder, live_link) @@ -456,18 +452,16 @@ def _fetch_and_enable_new_certificate(domain) : # Check the status of the certificate is now good statusSummaryCode = _get_status(domain)["summaryCode"] - if (statusSummaryCode < 20) : + if (statusSummaryCode < 20): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_certificate_fetching_or_enabling_failed', domain=domain)) - logger.info("Restarting services...") - for s in [ "nginx", "postfix", "dovecot", "metronome" ] : + for s in ["nginx", "postfix", "dovecot", "metronome"]: _run_service_command("restart", s) -def _prepare_certificate_signing_request(domain, key_file, output_folder) : - +def _prepare_certificate_signing_request(domain, key_file, output_folder): # Init a request csr = crypto.X509Req() @@ -475,7 +469,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder) : csr.get_subject().CN = domain # Set the key - with open(key_file, 'rt') as f : + with open(key_file, 'rt') as f: key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) csr.set_pubkey(key) @@ -483,158 +477,169 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder) : csr.sign(key, "sha256") # Save the request in tmp folder - csr_file = output_folder+domain+".csr" - logger.info("Saving to "+csr_file+" .") - with open(csr_file, "w") as f : + csr_file = output_folder + domain + ".csr" + logger.info("Saving to " + csr_file + " .") + with open(csr_file, "w") as f: f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr)) -def _get_status(domain) : +def _get_status(domain): + cert_file = cert_folder + "/" + domain + "/crt.pem" - cert_file = cert_folder+"/"+domain+"/crt.pem" + if (not os.path.isfile(cert_file)): + return {} - if (not os.path.isfile(cert_file)) : return {} - - try : + try: cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file).read()) - except : - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file)) + except: + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file)) certSubject = cert.get_subject().CN certIssuer = cert.get_issuer().CN - validUpTo = datetime.strptime(cert.get_notAfter(),"%Y%m%d%H%M%SZ") + validUpTo = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ") daysRemaining = (validUpTo - datetime.now()).days CAtype = None - if (certIssuer == _name_selfCA()) : + if (certIssuer == _name_selfCA()): CAtype = "Self-signed" - elif (certIssuer.startswith("Let's Encrypt")) : + elif (certIssuer.startswith("Let's Encrypt")): CAtype = "Let's Encrypt" - elif (certIssuer.startswith("Fake LE")) : + elif (certIssuer.startswith("Fake LE")): CAtype = "Fake Let's Encrypt" - else : + else: CAtype = "Other / Unknown" # Unknown by default statusSummaryCode = 0 # Critical - if (daysRemaining <= 0) : statusSummaryCode = -30 + if (daysRemaining <= 0): + statusSummaryCode = -30 # Warning, self-signed, browser will display a warning discouraging visitors to enter website - elif (CAtype == "Self-signed") or (CAtype == "Fake Let's Encrypt") : statusSummaryCode = -20 + elif (CAtype == "Self-signed") or (CAtype == "Fake Let's Encrypt"): + statusSummaryCode = -20 # Attention, certificate will expire soon (should be renewed automatically if Let's Encrypt) - elif (daysRemaining < validity_limit) : statusSummaryCode = -10 + elif (daysRemaining < validity_limit): + statusSummaryCode = -10 # CA not known, but still a valid certificate, so okay ! - elif (CAtype == "Other / Unknown") : statusSummaryCode = 10 + elif (CAtype == "Other / Unknown"): + statusSummaryCode = 10 # Let's Encrypt, great ! - elif (CAtype == "Let's Encrypt") : statusSummaryCode = 20 + elif (CAtype == "Let's Encrypt"): + statusSummaryCode = 20 - return { "domain" : domain, - "subject" : certSubject, - "CAname" : certIssuer, - "CAtype" : CAtype, - "validity" : daysRemaining, - "summaryCode" : statusSummaryCode - } + return {"domain": domain, + "subject": certSubject, + "CAname": certIssuer, + "CAtype": CAtype, + "validity": daysRemaining, + "summaryCode": statusSummaryCode + } ############################################################################### # Misc small stuff ... # ############################################################################### -def _generate_account_key() : +def _generate_account_key(): logger.info("Generating account key ...") _generate_key(account_key_file) _set_permissions(account_key_file, "root", "root", 0400) -def _generate_key(destinationPath) : +def _generate_key(destinationPath): k = crypto.PKey() k.generate_key(crypto.TYPE_RSA, key_size) - with open(destinationPath, "w") as f : + with open(destinationPath, "w") as f: f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k)) -def _set_permissions(path, user, group, permissions) : +def _set_permissions(path, user, group, permissions): uid = pwd.getpwnam(user).pw_uid gid = grp.getgrnam(group).gr_gid os.chown(path, uid, gid) os.chmod(path, permissions) + def _backup_current_cert(domain): + logger.info("Backuping existing certificate for domain " + domain) - logger.info("Backuping existing certificate for domain "+domain) - - cert_folder_domain = cert_folder+"/"+domain + cert_folder_domain = cert_folder + "/" + domain dateTag = datetime.now().strftime("%Y%m%d.%H%M%S") - backup_folder = cert_folder_domain+"-backup-"+dateTag + backup_folder = cert_folder_domain + "-backup-" + dateTag shutil.copytree(cert_folder_domain, backup_folder) -def _check_domain_is_correctly_configured(domain) : - +def _check_domain_is_correctly_configured(domain): public_ip = yunohost.domain.get_public_ip() # Check if IP from DNS matches public IP - if not _dns_ip_match_public_ip(public_ip, domain) : + if not _dns_ip_match_public_ip(public_ip, domain): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_dns_ip_differs_from_public_ip', domain=domain)) # Check if domain seems to be accessible through HTTP ? - if not _domain_is_accessible_through_HTTP(public_ip, domain) : - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_http_not_working', domain=domain)) - -def _dns_ip_match_public_ip(public_ip, domain) : + if not _domain_is_accessible_through_HTTP(public_ip, domain): + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_http_not_working', domain=domain)) - try : - r = requests.get("http://dns-api.org/A/"+domain) - except : + +def _dns_ip_match_public_ip(public_ip, domain): + try: + r = requests.get("http://dns-api.org/A/" + domain) + except: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_contacting_dns_api', api="dns-api.org")) - - if (r.text == "[{\"error\":\"NXDOMAIN\"}]") : + + if (r.text == "[{\"error\":\"NXDOMAIN\"}]"): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_A_dns_record', domain=domain)) - try : + try: dns_ip = json.loads(r.text)[0]["value"] - except : + except: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=r.text)) - if (dns_ip != public_ip) : + if (dns_ip != public_ip): return False - else : + else: return True -def _domain_is_accessible_through_HTTP(ip, domain) : - try : - requests.head("http://"+ip, headers = { "Host" : domain }) - except Exception : +def _domain_is_accessible_through_HTTP(ip, domain): + try: + requests.head("http://" + ip, headers={"Host": domain}) + except Exception: return False return True -def _summary_code_to_string(code) : - if (code <= -30) : return "CRITICAL" - elif (code <= -20) : return "WARNING" - elif (code <= -10) : return "Attention" - elif (code <= 0) : return "Unknown?" - elif (code <= 10) : return "Good" - elif (code <= 20) : return "Great!" +def _summary_code_to_string(code): + if (code <= -30): + return "CRITICAL" + elif (code <= -20): + return "WARNING" + elif (code <= -10): + return "Attention" + elif (code <= 0): + return "Unknown?" + elif (code <= 10): + return "Good" + elif (code <= 20): + return "Great!" return "Unknown?" -def _name_selfCA() : +def _name_selfCA(): cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(selfCA_file).read()) return cert.get_subject().CN def _tail(n, filePath): - stdin,stdout = os.popen2("tail -n "+str(n)+" "+filePath) - stdin.close() - lines = stdout.readlines(); stdout.close() - lines = "".join(lines) - return lines + stdin, stdout = os.popen2("tail -n " + str(n) + " " + filePath) + stdin.close() + lines = stdout.readlines() + stdout.close() + lines = "".join(lines) + return lines From 32bf7423676938f88d561038781dfed94d37dbf4 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 04:25:23 +0100 Subject: [PATCH 018/208] [mod] trailing spaces --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2326995ae..c8e9ff481 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1037,11 +1037,11 @@ def app_ssowatconf(auth): # Check ACME challenge file is present in nginx conf nginx_acme_challenge_conf_file = "/etc/nginx/conf.d/"+domain+".d/000-acmechallenge.conf" if not (os.path.isfile(nginx_acme_challenge_conf_file)) : continue - + # Check the file contains the ACME challenge uri acme_uri = '/.well-known/acme-challenge' if not (acme_uri in open(nginx_acme_challenge_conf_file).read()) : continue - + # If so, then authorize the ACME challenge uri to unprotected regex regex = domain+"/%.well%-known/acme%-challenge/.*$" unprotected_regex.append(regex) From 02fc92d21083b869a7f8afe1ed34c24b2d0a0601 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 04:27:12 +0100 Subject: [PATCH 019/208] [mod] pep8 --- src/yunohost/app.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c8e9ff481..e3d5ad202 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1035,15 +1035,19 @@ def app_ssowatconf(auth): for domain in domains: # Check ACME challenge file is present in nginx conf - nginx_acme_challenge_conf_file = "/etc/nginx/conf.d/"+domain+".d/000-acmechallenge.conf" - if not (os.path.isfile(nginx_acme_challenge_conf_file)) : continue + nginx_acme_challenge_conf_file = "/etc/nginx/conf.d/%s.d/000-acmechallenge.conf" % domain + + if not os.path.isfile(nginx_acme_challenge_conf_file): + continue # Check the file contains the ACME challenge uri acme_uri = '/.well-known/acme-challenge' - if not (acme_uri in open(nginx_acme_challenge_conf_file).read()) : continue + + if not acme_uri in open(nginx_acme_challenge_conf_file).read(): + continue # If so, then authorize the ACME challenge uri to unprotected regex - regex = domain+"/%.well%-known/acme%-challenge/.*$" + regex = domain + "/%.well%-known/acme%-challenge/.*$" unprotected_regex.append(regex) From 6a1727da89f26de8b10a7a83099af245e21e7cfb Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 04:27:39 +0100 Subject: [PATCH 020/208] [mod] remove useless imports --- src/yunohost/domain.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 58ca41b08..614409fa7 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -24,10 +24,8 @@ Manage domains """ import os -import sys import datetime import re -import shutil import json import yaml import errno From ef6287795299baf7b4b89d28387f5b9feca8ae54 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 04:29:16 +0100 Subject: [PATCH 021/208] [mod] pep8 --- src/yunohost/domain.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 614409fa7..1a296ee97 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -30,14 +30,16 @@ import json import yaml import errno import requests + from urllib import urlopen -from moulinette.core import MoulinetteError +from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from yunohost.service import service_regen_conf import yunohost.certificate +from yunohost.service import service_regen_conf + logger = getActionLogger('yunohost.domain') @@ -115,7 +117,7 @@ def domain_add(auth, domain, dyndns=False): yunohost.certificate.certificate_install_selfsigned([domain], False) try: - auth.validate_uniqueness({ 'virtualdomain': domain }) + auth.validate_uniqueness({'virtualdomain': domain}) except MoulinetteError: raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists')) @@ -254,15 +256,19 @@ def domain_dns_conf(domain, ttl=None): return result + def domain_cert_status(auth, domainList, full=False): return yunohost.certificate.certificate_status(auth, domainList, full) + def domain_cert_install(auth, domainList, force=False, no_checks=False, self_signed=False): return yunohost.certificate.certificate_install(auth, domainList, force, no_checks, self_signed) + def domain_cert_renew(auth, domainList, force=False, no_checks=False, email=False): return yunohost.certificate.certificate_renew(auth, domainList, force, no_checks, email) + def get_public_ip(protocol=4): """Retrieve the public IP address from ip.yunohost.org""" if protocol == 4: @@ -278,4 +284,3 @@ def get_public_ip(protocol=4): logger.debug('cannot retrieve public IPv%d' % protocol, exc_info=1) raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) - From aff4dc40868b8e0fbfbc740bed17bc9ffd3991bb Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 04:42:21 +0100 Subject: [PATCH 022/208] [mod] more verbose error --- locales/en.json | 2 +- src/yunohost/certificate.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 35da2eef0..c2f7b7402 100644 --- a/locales/en.json +++ b/locales/en.json @@ -249,7 +249,7 @@ "certmanager_error_parsing_dns" : "Error parsing the return value from the DNS API : {value:s}. Please verify your DNS configuration for domain {domain:s}. Use --no-checks to disable checks.", "certmanager_domain_dns_ip_differs_from_public_ip" : "The DNS 'A' record for domain {domain:s} is different from this server IP. Give some time for the DNS to refresh, or use --no-checks to disable checks.", "certmanager_no_A_dns_record" : "No DNS record of type A found for {domain:s}. You need to configure the DNS for your domain before installing a certificate !", - "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file : {file:s})", + "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_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain:s} !" } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index e11ea9e3b..9d19d84cd 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -491,8 +491,10 @@ def _get_status(domain): try: cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file).read()) - except: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file)) + except Exception as exception: + import traceback + traceback.print_exc(file=sys.stdout) + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception)) certSubject = cert.get_subject().CN certIssuer = cert.get_issuer().CN From 917c23073581d2f2d5bbc814ef50c48b8426af5c Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 04:53:16 +0100 Subject: [PATCH 023/208] [mod] more pythonic and explicit tests with more verbose errors --- locales/en.json | 2 +- src/yunohost/certificate.py | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/locales/en.json b/locales/en.json index c2f7b7402..bdfd1b14a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -245,7 +245,7 @@ "certmanager_attempt_to_renew_nonLE_cert" : "The certificate of domain {domain:s} is not issued by Let's Encrypt. Cannot renew it automatically !", "certmanager_attempt_to_renew_valid_cert" : "The certificate of domain {domain:s} is not about to expire ! Use --force to bypass", "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_error_contacting_dns_api" : "Error contacting the DNS API ({api:s}). Use --no-checks to disable checks.", + "certmanager_error_contacting_dns_api" : "Error contacting the DNS API ({api:s}), reason: {reason:s}. Use --no-checks to disable checks.", "certmanager_error_parsing_dns" : "Error parsing the return value from the DNS API : {value:s}. Please verify your DNS configuration for domain {domain:s}. Use --no-checks to disable checks.", "certmanager_domain_dns_ip_differs_from_public_ip" : "The DNS 'A' record for domain {domain:s} is different from this server IP. Give some time for the DNS to refresh, or use --no-checks to disable checks.", "certmanager_no_A_dns_record" : "No DNS record of type A found for {domain:s}. You need to configure the DNS for your domain before installing a certificate !", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 9d19d84cd..89c00943c 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -589,22 +589,25 @@ def _check_domain_is_correctly_configured(domain): def _dns_ip_match_public_ip(public_ip, domain): try: - r = requests.get("http://dns-api.org/A/" + domain) - except: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_contacting_dns_api', api="dns-api.org")) + result = requests.get("http://dns-api.org/A/" + domain) + except Exception as exception: + import traceback + traceback.print_exc(file=sys.stdout) + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_contacting_dns_api', api="dns-api.org", reason=exception)) - if (r.text == "[{\"error\":\"NXDOMAIN\"}]"): + dns_ip = result.json() + if not dns_ip or "value" not in dns_ip[0]: + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=result.text)) + + dns_ip = dns_ip[0]["value"] + + if dns_ip.get("error") == "NXDOMAIN": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_A_dns_record', domain=domain)) - try: - dns_ip = json.loads(r.text)[0]["value"] - except: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=r.text)) - - if (dns_ip != public_ip): - return False - else: + if dns_ip == public_ip: return True + else: + return False def _domain_is_accessible_through_HTTP(ip, domain): From 29f5f2d7534da9edaae2cd97ea9adb7b27cb09cb Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 04:56:06 +0100 Subject: [PATCH 024/208] [mod] more pythonic code --- src/yunohost/certificate.py | 127 ++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 89c00943c..c318b63c2 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -25,12 +25,12 @@ """ import os +import sys import errno import requests import shutil import pwd import grp -import json import smtplib from OpenSSL import crypto @@ -100,18 +100,23 @@ def certificate_status(auth, domainList, full=False): headers = ["Domain", "Certificate status", "Authority type", "Days remaining"] else: headers = ["Domain", "Certificate subject", "Certificate status", "Authority type", "Authority name", "Days remaining"] + lines = [] for domain in domainList: status = _get_status(domain) line = [] line.append(domain) - if (full): + + if full: line.append(status["subject"]) + line.append(_summary_code_to_string(status["summaryCode"])) line.append(status["CAtype"]) - if (full): + + if full: line.append(status["CAname"]) + line.append(status["validity"]) lines.append(line) @@ -142,20 +147,19 @@ def certificate_install_selfsigned(domainList, force=False): # Check we ain't trying to overwrite a good cert ! status = _get_status(domain) - if (status != {}) and (status["summaryCode"] > 0) and (not force): + + if status != {} and status["summaryCode"] > 0 and not force: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) - cert_folder_domain = cert_folder + "/" + domain + cert_folder_domain = os.path.join(cert_folder, domain) # Create cert folder if it does not exists yet - try: - os.listdir(cert_folder_domain) - except OSError: + if not os.path.exists(cert_folder_domain): os.makedirs(cert_folder_domain) # Get serial ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' - with open('%s/serial' % ssl_dir, 'r') as f: + with open(os.path.join(ssl_dir, 'serial'), 'r') as f: serial = f.readline().rstrip() # FIXME : should refactor this to avoid so many os.system() calls... @@ -178,25 +182,22 @@ def certificate_install_selfsigned(domainList, force=False): raise MoulinetteError(errno.EIO, m18n.n('certmanager_domain_cert_gen_failed')) _set_permissions(cert_folder_domain, "root", "root", 0755) - _set_permissions(cert_folder_domain + "/key.pem", "root", "metronome", 0640) - _set_permissions(cert_folder_domain + "/crt.pem", "root", "metronome", 0640) - _set_permissions(cert_folder_domain + "/openssl.cnf", "root", "root", 0600) + _set_permissions(os.path.join(cert_folder_domain, "key.pem"), "root", "metronome", 0640) + _set_permissions(os.path.join(cert_folder_domain, "crt.pem"), "root", "metronome", 0640) + _set_permissions(os.path.join(cert_folder_domain, "openssl.cnf"), "root", "root", 0600) -# Install ACME / Let's Encrypt certificate - def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=False): if not os.path.exists(account_key_file): _generate_account_key() # If no domains given, consider all yunohost domains with self-signed # certificates - if (domainList == []): + if domainList == []: for domain in yunohost.domain.domain_list(auth)['domains']: - # Is it self-signed ? status = _get_status(domain) - if (status["CAtype"] != "Self-signed"): + if status["CAtype"] != "Self-signed": continue domainList.append(domain) @@ -210,7 +211,7 @@ def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=Fal # Is it self-signed ? status = _get_status(domain) - if (not force) and (status["CAtype"] != "Self-signed"): + if not force and status["CAtype"] != "Self-signed": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_cert_not_selfsigned', domain=domain)) # Actual install steps @@ -219,9 +220,9 @@ def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=Fal logger.info("Now attempting install of certificate for domain " + domain + " !") try: - if not no_checks: _check_domain_is_correctly_configured(domain) + _backup_current_cert(domain) _configure_for_acme_challenge(auth, domain) _fetch_and_enable_new_certificate(domain) @@ -230,14 +231,10 @@ def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=Fal logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) except Exception as e: - - logger.error("Certificate installation for " + domain + " failed !") + logger.error("Certificate installation for %s failed !" % domain) logger.error(str(e)) -# Renew - - def certificate_renew(auth, domainList, force=False, no_checks=False, email=False): """ Renew Let's Encrypt certificate for given domains (all by default) @@ -257,7 +254,7 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals # Does it has a Let's Encrypt cert ? status = _get_status(domain) - if (status["CAtype"] != "Let's Encrypt"): + if status["CAtype"] != "Let's Encrypt": continue # Does it expires soon ? @@ -278,20 +275,18 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals status = _get_status(domain) # Does it expires soon ? - if not ((force) or (status["validity"] <= validity_limit)): + if not (force or status["validity"] <= validity_limit): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain)) # Does it has a Let's Encrypt cert ? - if (status["CAtype"] != "Let's Encrypt"): + if status["CAtype"] != "Let's Encrypt": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain)) # Actual renew steps for domain in domainList: - logger.info("Now attempting renewing of certificate for domain " + domain + " !") try: - if not no_checks: _check_domain_is_correctly_configured(domain) _backup_current_cert(domain) @@ -300,11 +295,10 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) except Exception as e: - logger.error("Certificate renewing for " + domain + " failed !") logger.error(str(e)) - if (email): + if email: logger.error("Sending email with details to root ...") _email_renewing_failed(domain, e) @@ -317,7 +311,6 @@ def _install_cron(): cron_job_file = "/etc/cron.weekly/certificateRenewer" with open(cron_job_file, "w") as f: - f.write("#!/bin/bash\n") f.write("yunohost domain cert-renew --email\n") @@ -325,9 +318,9 @@ def _install_cron(): def _email_renewing_failed(domain, e): - from_ = "certmanager@" + domain + " (Certificate Manager)" + from_ = "certmanager@%s (Certificate Manager)" % domain to_ = "root" - subject_ = "Certificate renewing attempt for " + domain + " failed!" + subject_ = "Certificate renewing attempt for %s failed!" % domain exceptionMessage = str(e) logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") @@ -360,7 +353,7 @@ Subject: %s def _configure_for_acme_challenge(auth, domain): - nginx_conf_file = "/etc/nginx/conf.d/" + domain + ".d/000-acmechallenge.conf" + nginx_conf_file = "/etc/nginx/conf.d/%s.d/000-acmechallenge.conf" % domain nginx_configuration = ''' location '/.well-known/acme-challenge' @@ -375,6 +368,7 @@ location '/.well-known/acme-challenge' logger.info("Nginx configuration file for ACME challenge already exists for domain, skipping.") else: logger.info("Adding Nginx configuration file for Acme challenge for domain " + domain + ".") + with open(nginx_conf_file, "w") as f: f.write(nginx_configuration) @@ -390,17 +384,19 @@ def _fetch_and_enable_new_certificate(domain): # Make sure tmp folder exists logger.debug("Making sure tmp folders exists...") - if not (os.path.exists(webroot_folder)): + if not os.path.exists(webroot_folder): os.makedirs(webroot_folder) - if not (os.path.exists(tmp_folder)): + + if not os.path.exists(tmp_folder): os.makedirs(tmp_folder) + _set_permissions(webroot_folder, "root", "www-data", 0650) _set_permissions(tmp_folder, "root", "root", 0640) # Prepare certificate signing request logger.info("Prepare key and certificate signing request (CSR) for " + domain + "...") - domain_key_file = tmp_folder + "/" + domain + ".pem" + domain_key_file = "%s/%s.pem" % (tmp_folder, domain) _generate_key(domain_key_file) _set_permissions(domain_key_file, "root", "metronome", 0640) @@ -409,13 +405,14 @@ def _fetch_and_enable_new_certificate(domain): # Sign the certificate logger.info("Now using ACME Tiny to sign the certificate...") - domain_csr_file = tmp_folder + "/" + domain + ".csr" + domain_csr_file = "%s/%s.csr" % (tmp_folder, domain) signed_certificate = sign_certificate(account_key_file, domain_csr_file, webroot_folder, log=logger, CA=certification_authority) + intermediate_certificate = requests.get(intermediate_certificate_url).text # Now save the key and signed certificate @@ -423,42 +420,47 @@ def _fetch_and_enable_new_certificate(domain): # Create corresponding directory date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") - new_cert_folder = cert_folder + "/" + domain + "." + date_tag + + new_cert_folder = "%s/%s.%s" % (cert_folder, domain, date_tag) os.makedirs(new_cert_folder) + _set_permissions(new_cert_folder, "root", "root", 0655) # Move the private key - shutil.move(domain_key_file, new_cert_folder + "/key.pem") + shutil.move(domain_key_file, os.path.join(new_cert_folder, "key.pem")) # Write the cert - domain_cert_file = new_cert_folder + "/crt.pem" + domain_cert_file = os.path.join(new_cert_folder, "crt.pem") + with open(domain_cert_file, "w") as f: f.write(signed_certificate) f.write(intermediate_certificate) + _set_permissions(domain_cert_file, "root", "metronome", 0640) logger.info("Enabling the new certificate...") # Replace (if necessary) the link or folder for live cert - live_link = cert_folder + "/" + domain + live_link = os.path.join(cert_folder, domain) if not os.path.islink(live_link): shutil.rmtree(live_link) # Well, yep, hopefully that's not too dangerous (directory should have been backuped before calling this command) + elif os.path.lexists(live_link): os.remove(live_link) os.symlink(new_cert_folder, live_link) # Check the status of the certificate is now good - statusSummaryCode = _get_status(domain)["summaryCode"] - if (statusSummaryCode < 20): + + if statusSummaryCode < 20: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_certificate_fetching_or_enabling_failed', domain=domain)) logger.info("Restarting services...") - for s in ["nginx", "postfix", "dovecot", "metronome"]: - _run_service_command("restart", s) + for service in ("nginx", "postfix", "dovecot", "metronome"): + _run_service_command("restart", service) def _prepare_certificate_signing_request(domain, key_file, output_folder): @@ -471,6 +473,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Set the key with open(key_file, 'rt') as f: key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) + csr.set_pubkey(key) # Sign the request @@ -479,6 +482,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Save the request in tmp folder csr_file = output_folder + domain + ".csr" logger.info("Saving to " + csr_file + " .") + with open(csr_file, "w") as f: f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr)) @@ -486,7 +490,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): def _get_status(domain): cert_file = cert_folder + "/" + domain + "/crt.pem" - if (not os.path.isfile(cert_file)): + if not os.path.isfile(cert_file): return {} try: @@ -504,38 +508,47 @@ def _get_status(domain): CAtype = None if (certIssuer == _name_selfCA()): CAtype = "Self-signed" + elif (certIssuer.startswith("Let's Encrypt")): CAtype = "Let's Encrypt" + elif (certIssuer.startswith("Fake LE")): CAtype = "Fake Let's Encrypt" + else: CAtype = "Other / Unknown" # Unknown by default statusSummaryCode = 0 + # Critical if (daysRemaining <= 0): statusSummaryCode = -30 + # Warning, self-signed, browser will display a warning discouraging visitors to enter website elif (CAtype == "Self-signed") or (CAtype == "Fake Let's Encrypt"): statusSummaryCode = -20 + # Attention, certificate will expire soon (should be renewed automatically if Let's Encrypt) elif (daysRemaining < validity_limit): statusSummaryCode = -10 + # CA not known, but still a valid certificate, so okay ! elif (CAtype == "Other / Unknown"): statusSummaryCode = 10 + # Let's Encrypt, great ! elif (CAtype == "Let's Encrypt"): statusSummaryCode = 20 - return {"domain": domain, - "subject": certSubject, - "CAname": certIssuer, - "CAtype": CAtype, - "validity": daysRemaining, - "summaryCode": statusSummaryCode - } + return { + "domain": domain, + "subject": certSubject, + "CAname": certIssuer, + "CAtype": CAtype, + "validity": daysRemaining, + "summaryCode": statusSummaryCode + } ############################################################################### # Misc small stuff ... # @@ -567,10 +580,10 @@ def _set_permissions(path, user, group, permissions): def _backup_current_cert(domain): logger.info("Backuping existing certificate for domain " + domain) - cert_folder_domain = cert_folder + "/" + domain + cert_folder_domain = os.path.join(cert_folder, domain) dateTag = datetime.now().strftime("%Y%m%d.%H%M%S") - backup_folder = cert_folder_domain + "-backup-" + dateTag + backup_folder = "%s-backup-%s" % (cert_folder_domain, dateTag) shutil.copytree(cert_folder_domain, backup_folder) From 39aaa1763984c0ee48eef15c4fc6b2a2fb1028db Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:08:44 +0100 Subject: [PATCH 025/208] [mod] remove useless () --- 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 c318b63c2..f94a2d4e6 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -86,7 +86,7 @@ def certificate_status(auth, domainList, full=False): """ # If no domains given, consider all yunohost domains - if (domainList == []): + if domainList == []: domainList = yunohost.domain.domain_list(auth)['domains'] # Else, validate that yunohost knows the domains given else: From 7dbbd7fdc2bb919af490f213573da5221ce82ce6 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:19:15 +0100 Subject: [PATCH 026/208] [fix] correctly handle all cases --- src/yunohost/certificate.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index f94a2d4e6..5dc16cba8 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -608,15 +608,24 @@ def _dns_ip_match_public_ip(public_ip, domain): traceback.print_exc(file=sys.stdout) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_contacting_dns_api', api="dns-api.org", reason=exception)) - dns_ip = result.json() - if not dns_ip or "value" not in dns_ip[0]: + try: + dns_ip = result.json() + except Exception as exception: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=result.text)) - dns_ip = dns_ip[0]["value"] + if len(dns_ip) == 0: + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=result.text)) + + dns_ip = dns_ip[0] if dns_ip.get("error") == "NXDOMAIN": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_A_dns_record', domain=domain)) + if "value" not in dns_ip: + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=result.text)) + + dns_ip = dns_ip["value"] + if dns_ip == public_ip: return True else: From ea179b1683b9ac96eb7fbb8ab493319cb7fd5a72 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:20:13 +0100 Subject: [PATCH 027/208] [mod] simplier condition --- src/yunohost/certificate.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 5dc16cba8..7d96aebca 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -626,10 +626,7 @@ def _dns_ip_match_public_ip(public_ip, domain): dns_ip = dns_ip["value"] - if dns_ip == public_ip: - return True - else: - return False + return dns_ip == public_ip def _domain_is_accessible_through_HTTP(ip, domain): From 9a4dbd5d31abb3956f55956875ce0f9f863295eb Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:20:40 +0100 Subject: [PATCH 028/208] [fix] uses https --- 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 7d96aebca..932bf1855 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -602,7 +602,7 @@ def _check_domain_is_correctly_configured(domain): def _dns_ip_match_public_ip(public_ip, domain): try: - result = requests.get("http://dns-api.org/A/" + domain) + result = requests.get("https://dns-api.org/A/" + domain) except Exception as exception: import traceback traceback.print_exc(file=sys.stdout) From ac9f61c7b1f808c2e93c977831a9a54a7d0552b5 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:21:27 +0100 Subject: [PATCH 029/208] [mod] more pythonic code --- src/yunohost/certificate.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 932bf1855..5f8c7ff2c 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -639,17 +639,22 @@ def _domain_is_accessible_through_HTTP(ip, domain): def _summary_code_to_string(code): - if (code <= -30): + if code <= -30: return "CRITICAL" - elif (code <= -20): - return "WARNING" - elif (code <= -10): - return "Attention" - elif (code <= 0): - return "Unknown?" - elif (code <= 10): - return "Good" - elif (code <= 20): + + if code <= -20: + return "WARNING" + + if code <= -10: + return "Attention" + + if code <= 0: + return "Unknown?" + + if code <= 10: + return "Good" + + if code <= 20: return "Great!" return "Unknown?" From ac901528c325bfc7f62e7a3c5c7f1a8c43f3d18a Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:22:30 +0100 Subject: [PATCH 030/208] [mod] uses + for strings --- 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 5f8c7ff2c..de21299d1 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -666,7 +666,7 @@ def _name_selfCA(): def _tail(n, filePath): - stdin, stdout = os.popen2("tail -n " + str(n) + " " + filePath) + stdin, stdout = os.popen2("tail -n %s '%s'" % (n, filePath)) stdin.close() lines = stdout.readlines() stdout.close() From cd21edb26704250305275f61fdbca9c357d9f8aa Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:22:43 +0100 Subject: [PATCH 031/208] [mod] pep8 --- src/yunohost/certificate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index de21299d1..94defcd2b 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -665,8 +665,8 @@ def _name_selfCA(): return cert.get_subject().CN -def _tail(n, filePath): - stdin, stdout = os.popen2("tail -n %s '%s'" % (n, filePath)) +def _tail(n, file_path): + stdin, stdout = os.popen2("tail -n %s '%s'" % (n, file_path)) stdin.close() lines = stdout.readlines() stdout.close() From a7b9226667935158501fe39a56bcd1066359fa18 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:23:32 +0100 Subject: [PATCH 032/208] [mod] lisibility --- src/yunohost/certificate.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 94defcd2b..b1efb3725 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -667,8 +667,10 @@ def _name_selfCA(): def _tail(n, file_path): stdin, stdout = os.popen2("tail -n %s '%s'" % (n, file_path)) + stdin.close() + lines = stdout.readlines() stdout.close() - lines = "".join(lines) - return lines + + return "".join(lines) From dd893b0838d78e2588102ee71b6c70c068bc34c5 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:26:52 +0100 Subject: [PATCH 033/208] [mod] remove useless () --- src/yunohost/certificate.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index b1efb3725..1196a2e93 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -506,13 +506,13 @@ def _get_status(domain): daysRemaining = (validUpTo - datetime.now()).days CAtype = None - if (certIssuer == _name_selfCA()): + if certIssuer == _name_selfCA(): CAtype = "Self-signed" - elif (certIssuer.startswith("Let's Encrypt")): + elif certIssuer.startswith("Let's Encrypt"): CAtype = "Let's Encrypt" - elif (certIssuer.startswith("Fake LE")): + elif certIssuer.startswith("Fake LE"): CAtype = "Fake Let's Encrypt" else: @@ -522,23 +522,23 @@ def _get_status(domain): statusSummaryCode = 0 # Critical - if (daysRemaining <= 0): + if daysRemaining <= 0: statusSummaryCode = -30 # Warning, self-signed, browser will display a warning discouraging visitors to enter website - elif (CAtype == "Self-signed") or (CAtype == "Fake Let's Encrypt"): + elif CAtype == "Self-signed" or CAtype == "Fake Let's Encrypt": statusSummaryCode = -20 # Attention, certificate will expire soon (should be renewed automatically if Let's Encrypt) - elif (daysRemaining < validity_limit): + elif daysRemaining < validity_limit: statusSummaryCode = -10 # CA not known, but still a valid certificate, so okay ! - elif (CAtype == "Other / Unknown"): + elif CAtype == "Other / Unknown": statusSummaryCode = 10 # Let's Encrypt, great ! - elif (CAtype == "Let's Encrypt"): + elif CAtype == "Let's Encrypt": statusSummaryCode = 20 return { From 34f98905175033f5ecf5575797517df573b72123 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:29:10 +0100 Subject: [PATCH 034/208] [mod] more pythonic string concatenation --- src/yunohost/certificate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 1196a2e93..12ee4dbf8 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -359,15 +359,15 @@ def _configure_for_acme_challenge(auth, domain): location '/.well-known/acme-challenge' { default_type "text/plain"; - alias ''' + webroot_folder + '''; + alias %s; } - ''' + ''' % webroot_folder # Write the conf if os.path.exists(nginx_conf_file): logger.info("Nginx configuration file for ACME challenge already exists for domain, skipping.") else: - logger.info("Adding Nginx configuration file for Acme challenge for domain " + domain + ".") + logger.info("Adding Nginx configuration file for Acme challenge for domain %s.", domain) with open(nginx_conf_file, "w") as f: f.write(nginx_configuration) @@ -394,7 +394,7 @@ def _fetch_and_enable_new_certificate(domain): _set_permissions(tmp_folder, "root", "root", 0640) # Prepare certificate signing request - logger.info("Prepare key and certificate signing request (CSR) for " + domain + "...") + logger.info("Prepare key and certificate signing request (CSR) for %s...", domain) domain_key_file = "%s/%s.pem" % (tmp_folder, domain) _generate_key(domain_key_file) From 55d007f13037a0efc2ffe1661ec7e907e466fec7 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:30:54 +0100 Subject: [PATCH 035/208] [mod] avoid useless indentation --- src/yunohost/certificate.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 12ee4dbf8..f83f98570 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -366,18 +366,19 @@ location '/.well-known/acme-challenge' # Write the conf if os.path.exists(nginx_conf_file): logger.info("Nginx configuration file for ACME challenge already exists for domain, skipping.") - else: - logger.info("Adding Nginx configuration file for Acme challenge for domain %s.", domain) + return - with open(nginx_conf_file, "w") as f: - f.write(nginx_configuration) + logger.info("Adding Nginx configuration file for Acme challenge for domain %s.", domain) - # Assume nginx conf is okay, and reload it - # (FIXME : maybe add a check that it is, using nginx -t, haven't found - # any clean function already implemented in yunohost to do this though) - _run_service_command("reload", "nginx") + with open(nginx_conf_file, "w") as f: + f.write(nginx_configuration) - app_ssowatconf(auth) + # Assume nginx conf is okay, and reload it + # (FIXME : maybe add a check that it is, using nginx -t, haven't found + # any clean function already implemented in yunohost to do this though) + _run_service_command("reload", "nginx") + + app_ssowatconf(auth) def _fetch_and_enable_new_certificate(domain): From e8e07d464add2dc54ac84a613fc64a970354a87c Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:31:36 +0100 Subject: [PATCH 036/208] [mod] remove useless variable --- src/yunohost/certificate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index f83f98570..426e0f66a 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -317,12 +317,11 @@ def _install_cron(): _set_permissions(cron_job_file, "root", "root", 0755) -def _email_renewing_failed(domain, e): +def _email_renewing_failed(domain, exceptionMessage): from_ = "certmanager@%s (Certificate Manager)" % domain to_ = "root" subject_ = "Certificate renewing attempt for %s failed!" % domain - exceptionMessage = str(e) logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") text = """ At attempt for renewing the certificate for domain %s failed with the following From 5615e3f4fe7dc9045bfa699b7e43484bb9b4dba3 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:33:21 +0100 Subject: [PATCH 037/208] [mod] uses logger string concatenation api --- src/yunohost/certificate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 426e0f66a..c35d15f8e 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -284,7 +284,7 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals # Actual renew steps for domain in domainList: - logger.info("Now attempting renewing of certificate for domain " + domain + " !") + logger.info("Now attempting renewing of certificate for domain %s !", domain) try: if not no_checks: @@ -295,7 +295,7 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) except Exception as e: - logger.error("Certificate renewing for " + domain + " failed !") + logger.error("Certificate renewing for %s failed !", domain) logger.error(str(e)) if email: From 8ca5d59a96366c1ec285f5e400e8e34d01a4d3b2 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:33:57 +0100 Subject: [PATCH 038/208] [mod] remove useless () --- src/yunohost/certificate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index c35d15f8e..9562a3a5f 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -249,7 +249,7 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals # If no domains given, consider all yunohost domains with Let's Encrypt # certificates - if (domainList == []): + if domainList == []: for domain in yunohost.domain.domain_list(auth)['domains']: # Does it has a Let's Encrypt cert ? @@ -258,10 +258,10 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals continue # Does it expires soon ? - if (force) or (status["validity"] <= validity_limit): + if force or status["validity"] <= validity_limit: domainList.append(domain) - if (len(domainList) == 0): + if len(domainList) == 0: logger.info("No certificate needs to be renewed.") # Else, validate the domain list given @@ -275,7 +275,7 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals status = _get_status(domain) # Does it expires soon ? - if not (force or status["validity"] <= validity_limit): + if not force or status["validity"] <= validity_limit: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain)) # Does it has a Let's Encrypt cert ? From 65e9a4b6d8ac456b61a1bc9fdd59c012f10003b4 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:34:30 +0100 Subject: [PATCH 039/208] [mod] uses logger string concatenation api --- src/yunohost/certificate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 9562a3a5f..f903c266a 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -217,7 +217,7 @@ def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=Fal # Actual install steps for domain in domainList: - logger.info("Now attempting install of certificate for domain " + domain + " !") + logger.info("Now attempting install of certificate for domain %s!", domain) try: if not no_checks: @@ -231,7 +231,7 @@ def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=Fal logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) except Exception as e: - logger.error("Certificate installation for %s failed !" % domain) + logger.error("Certificate installation for %s failed !", domain) logger.error(str(e)) From c1252120a13f720cd811f9379fbc86f91a17734f Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:36:02 +0100 Subject: [PATCH 040/208] [mod] typo --- 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 f903c266a..a547d2000 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -91,7 +91,7 @@ def certificate_status(auth, domainList, full=False): # Else, validate that yunohost knows the domains given else: for domain in domainList: - # Is it in Yunohost dmomain list ? + # Is it in Yunohost domain list ? if domain not in yunohost.domain.domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) From 02b5ea62feff550a522d4dead957ee6ed048dc4e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:42:16 +0100 Subject: [PATCH 041/208] [mod] pep8 --- data/actionsmap/yunohost.yml | 6 ++--- src/yunohost/certificate.py | 48 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 4d0db97ec..8f08a98b8 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -313,7 +313,7 @@ domain: authenticate: all authenticator: ldap-anonymous arguments: - domainList: + domain_list: help: Domains to check nargs: "*" --full: @@ -328,7 +328,7 @@ domain: authenticate: all authenticator: ldap-anonymous arguments: - domainList: + domain_list: help: Domains for which to install the certificates nargs: "*" --force: @@ -349,7 +349,7 @@ domain: authenticate: all authenticator: ldap-anonymous arguments: - domainList: + domain_list: help: Domains for which to renew the certificates nargs: "*" --force: diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index a547d2000..c2f51270d 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -76,21 +76,21 @@ intermediate_certificate_url = "https://letsencrypt.org/certs/lets-encrypt-x3-cr # Status -def certificate_status(auth, domainList, full=False): +def certificate_status(auth, domain_list, full=False): """ Print the status of certificate for given domains (all by default) Keyword argument: - domainList -- Domains to be checked + domain_list -- Domains to be checked full -- Display more info about the certificates """ # If no domains given, consider all yunohost domains - if domainList == []: - domainList = yunohost.domain.domain_list(auth)['domains'] + if domain_list == []: + domain_list = yunohost.domain.domain_list(auth)['domains'] # Else, validate that yunohost knows the domains given else: - for domain in domainList: + for domain in domain_list: # Is it in Yunohost domain list ? if domain not in yunohost.domain.domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) @@ -102,7 +102,7 @@ def certificate_status(auth, domainList, full=False): headers = ["Domain", "Certificate subject", "Certificate status", "Authority type", "Authority name", "Days remaining"] lines = [] - for domain in domainList: + for domain in domain_list: status = _get_status(domain) line = [] @@ -123,27 +123,27 @@ def certificate_status(auth, domainList, full=False): print(tabulate(lines, headers=headers, tablefmt="simple", stralign="center")) -def certificate_install(auth, domainList, force=False, no_checks=False, self_signed=False): +def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False): """ Install a Let's Encrypt certificate for given domains (all by default) Keyword argument: - domainList -- Domains on which to install certificates + domain_list -- Domains on which to install certificates force -- Install even if current certificate is not self-signed no-check -- Disable some checks about the reachability of web server before attempting the install self-signed -- Instal self-signed certificates instead of Let's Encrypt """ if (self_signed): - certificate_install_selfsigned(domainList, force) + certificate_install_selfsigned(domain_list, force) else: - certificate_install_letsencrypt(auth, domainList, force, no_checks) + certificate_install_letsencrypt(auth, domain_list, force, no_checks) # Install self-signed -def certificate_install_selfsigned(domainList, force=False): - for domain in domainList: +def certificate_install_selfsigned(domain_list, force=False): + for domain in domain_list: # Check we ain't trying to overwrite a good cert ! status = _get_status(domain) @@ -187,24 +187,24 @@ def certificate_install_selfsigned(domainList, force=False): _set_permissions(os.path.join(cert_folder_domain, "openssl.cnf"), "root", "root", 0600) -def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=False): +def certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False): if not os.path.exists(account_key_file): _generate_account_key() # If no domains given, consider all yunohost domains with self-signed # certificates - if domainList == []: + if domain_list == []: for domain in yunohost.domain.domain_list(auth)['domains']: status = _get_status(domain) if status["CAtype"] != "Self-signed": continue - domainList.append(domain) + domain_list.append(domain) # Else, validate that yunohost knows the domains given else: - for domain in domainList: + for domain in domain_list: # Is it in Yunohost dmomain list ? if domain not in yunohost.domain.domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) @@ -215,7 +215,7 @@ def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=Fal raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_cert_not_selfsigned', domain=domain)) # Actual install steps - for domain in domainList: + for domain in domain_list: logger.info("Now attempting install of certificate for domain %s!", domain) @@ -235,12 +235,12 @@ def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=Fal logger.error(str(e)) -def certificate_renew(auth, domainList, force=False, no_checks=False, email=False): +def certificate_renew(auth, domain_list, force=False, no_checks=False, email=False): """ Renew Let's Encrypt certificate for given domains (all by default) Keyword argument: - domainList -- Domains for which to renew the certificates + domain_list -- Domains for which to renew the certificates force -- Ignore the validity threshold (15 days) no-check -- Disable some checks about the reachability of web server before attempting the renewing @@ -249,7 +249,7 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals # If no domains given, consider all yunohost domains with Let's Encrypt # certificates - if domainList == []: + if domain_list == []: for domain in yunohost.domain.domain_list(auth)['domains']: # Does it has a Let's Encrypt cert ? @@ -259,14 +259,14 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals # Does it expires soon ? if force or status["validity"] <= validity_limit: - domainList.append(domain) + domain_list.append(domain) - if len(domainList) == 0: + if len(domain_list) == 0: logger.info("No certificate needs to be renewed.") # Else, validate the domain list given else: - for domain in domainList: + for domain in domain_list: # Is it in Yunohost dmomain list ? if domain not in yunohost.domain.domain_list(auth)['domains']: @@ -283,7 +283,7 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain)) # Actual renew steps - for domain in domainList: + for domain in domain_list: logger.info("Now attempting renewing of certificate for domain %s !", domain) try: From 8b24ab73c25db0ce095c25bf9c03644990b47180 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:42:49 +0100 Subject: [PATCH 042/208] [mod] small opti, getting domain list can be slow --- src/yunohost/certificate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index c2f51270d..73e9ae22b 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -90,9 +90,10 @@ def certificate_status(auth, domain_list, full=False): domain_list = yunohost.domain.domain_list(auth)['domains'] # Else, validate that yunohost knows the domains given else: + yunohost_domains_list = yunohost.domain.domain_list(auth)['domains'] for domain in domain_list: # Is it in Yunohost domain list ? - if domain not in yunohost.domain.domain_list(auth)['domains']: + if domain not in yunohost_domains_list: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) # Get status for each domain, and prepare display using tabulate From 3b5cadb907f1ca00d1c3710221f57dca4befce41 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:43:32 +0100 Subject: [PATCH 043/208] [mod] realign stuff --- src/yunohost/certificate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 73e9ae22b..81219eab5 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -82,7 +82,7 @@ def certificate_status(auth, domain_list, full=False): Keyword argument: domain_list -- Domains to be checked - full -- Display more info about the certificates + full -- Display more info about the certificates """ # If no domains given, consider all yunohost domains @@ -130,10 +130,10 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si Keyword argument: domain_list -- Domains on which to install certificates - force -- Install even if current certificate is not self-signed - no-check -- Disable some checks about the reachability of web server + force -- Install even if current certificate is not self-signed + no-check -- Disable some checks about the reachability of web server before attempting the install - self-signed -- Instal self-signed certificates instead of Let's Encrypt + self-signed -- Instal self-signed certificates instead of Let's Encrypt """ if (self_signed): certificate_install_selfsigned(domain_list, force) From 487e1d25888939b9d6af577ca21e2dfac305a2ac Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 05:54:45 +0100 Subject: [PATCH 044/208] [mod] pylint --- src/yunohost/certificate.py | 98 ++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 81219eab5..b62118257 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -17,9 +17,7 @@ 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 -""" - -""" yunohost_certificate.py + yunohost_certificate.py Manage certificates, in particular Let's encrypt """ @@ -27,22 +25,22 @@ import os import sys import errno -import requests import shutil import pwd import grp import smtplib +import requests from OpenSSL import crypto from datetime import datetime from tabulate import tabulate from acme_tiny import get_crt as sign_certificate -import yunohost.domain - from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger +import yunohost.domain + from yunohost.app import app_ssowatconf from yunohost.service import _run_service_command @@ -135,7 +133,7 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si before attempting the install self-signed -- Instal self-signed certificates instead of Let's Encrypt """ - if (self_signed): + if self_signed: certificate_install_selfsigned(domain_list, force) else: certificate_install_letsencrypt(auth, domain_list, force, no_checks) @@ -318,13 +316,13 @@ def _install_cron(): _set_permissions(cron_job_file, "root", "root", 0755) -def _email_renewing_failed(domain, exceptionMessage): +def _email_renewing_failed(domain, exception_message): from_ = "certmanager@%s (Certificate Manager)" % domain to_ = "root" subject_ = "Certificate renewing attempt for %s failed!" % domain logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") - text = """ + text = """ At attempt for renewing the certificate for domain %s failed with the following error : @@ -337,7 +335,7 @@ investigate : -- Certificate Manager -""" % (domain, exceptionMessage, logs) +""" % (domain, exception_message, logs) message = """ From: %s @@ -397,7 +395,7 @@ def _fetch_and_enable_new_certificate(domain): # Prepare certificate signing request logger.info("Prepare key and certificate signing request (CSR) for %s...", domain) - domain_key_file = "%s/%s.pem" % (tmp_folder, domain) + domain_key_file = "%s/%s.pem" % (tmp_folder, domain) _generate_key(domain_key_file) _set_permissions(domain_key_file, "root", "metronome", 0640) @@ -453,9 +451,9 @@ def _fetch_and_enable_new_certificate(domain): os.symlink(new_cert_folder, live_link) # Check the status of the certificate is now good - statusSummaryCode = _get_status(domain)["summaryCode"] + status_summary_code = _get_status(domain)["summaryCode"] - if statusSummaryCode < 20: + if status_summary_code < 20: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_certificate_fetching_or_enabling_failed', domain=domain)) logger.info("Restarting services...") @@ -501,54 +499,54 @@ def _get_status(domain): traceback.print_exc(file=sys.stdout) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception)) - certSubject = cert.get_subject().CN - certIssuer = cert.get_issuer().CN - validUpTo = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ") - daysRemaining = (validUpTo - datetime.now()).days + cert_subject = cert.get_subject().CN + cert_issuer = cert.get_issuer().CN + valid_up_to = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ") + days_remaining = (valid_up_to - datetime.now()).days - CAtype = None - if certIssuer == _name_selfCA(): - CAtype = "Self-signed" + CA_type = None + if cert_issuer == _name_self_CA(): + CA_type = "Self-signed" - elif certIssuer.startswith("Let's Encrypt"): - CAtype = "Let's Encrypt" + elif cert_issuer.startswith("Let's Encrypt"): + CA_type = "Let's Encrypt" - elif certIssuer.startswith("Fake LE"): - CAtype = "Fake Let's Encrypt" + elif cert_issuer.startswith("Fake LE"): + CA_type = "Fake Let's Encrypt" else: - CAtype = "Other / Unknown" + CA_type = "Other / Unknown" # Unknown by default - statusSummaryCode = 0 + status_summary_code = 0 # Critical - if daysRemaining <= 0: - statusSummaryCode = -30 + if days_remaining <= 0: + status_summary_code = -30 # Warning, self-signed, browser will display a warning discouraging visitors to enter website - elif CAtype == "Self-signed" or CAtype == "Fake Let's Encrypt": - statusSummaryCode = -20 + elif CA_type == "Self-signed" or CA_type == "Fake Let's Encrypt": + status_summary_code = -20 # Attention, certificate will expire soon (should be renewed automatically if Let's Encrypt) - elif daysRemaining < validity_limit: - statusSummaryCode = -10 + elif days_remaining < validity_limit: + status_summary_code = -10 # CA not known, but still a valid certificate, so okay ! - elif CAtype == "Other / Unknown": - statusSummaryCode = 10 + elif CA_type == "Other / Unknown": + status_summary_code = 10 # Let's Encrypt, great ! - elif CAtype == "Let's Encrypt": - statusSummaryCode = 20 + elif CA_type == "Let's Encrypt": + status_summary_code = 20 return { "domain": domain, - "subject": certSubject, - "CAname": certIssuer, - "CAtype": CAtype, - "validity": daysRemaining, - "summaryCode": statusSummaryCode + "subject": cert_subject, + "CAname": cert_issuer, + "CAtype": CA_type, + "validity": days_remaining, + "summaryCode": status_summary_code } ############################################################################### @@ -562,11 +560,11 @@ def _generate_account_key(): _set_permissions(account_key_file, "root", "root", 0400) -def _generate_key(destinationPath): +def _generate_key(destination_path): k = crypto.PKey() k.generate_key(crypto.TYPE_RSA, key_size) - with open(destinationPath, "w") as f: + with open(destination_path, "w") as f: f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k)) @@ -583,8 +581,8 @@ def _backup_current_cert(domain): cert_folder_domain = os.path.join(cert_folder, domain) - dateTag = datetime.now().strftime("%Y%m%d.%H%M%S") - backup_folder = "%s-backup-%s" % (cert_folder_domain, dateTag) + date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") + backup_folder = "%s-backup-%s" % (cert_folder_domain, date_tag) shutil.copytree(cert_folder_domain, backup_folder) @@ -644,16 +642,16 @@ def _summary_code_to_string(code): return "CRITICAL" if code <= -20: - return "WARNING" + return "WARNING" if code <= -10: - return "Attention" + return "Attention" if code <= 0: - return "Unknown?" + return "Unknown?" if code <= 10: - return "Good" + return "Good" if code <= 20: return "Great!" @@ -661,7 +659,7 @@ def _summary_code_to_string(code): return "Unknown?" -def _name_selfCA(): +def _name_self_CA(): cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(selfCA_file).read()) return cert.get_subject().CN From 3fc2d45a6ab139a101a78205cdaecb1912eb6c5e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 13:40:05 +0100 Subject: [PATCH 045/208] [mod] remove useless comments --- src/yunohost/certificate.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index b62118257..43c64aa34 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -47,8 +47,6 @@ from yunohost.service import _run_service_command logger = getActionLogger('yunohost.certmanager') -# Misc stuff we need - cert_folder = "/etc/yunohost/certs/" tmp_folder = "/tmp/acme-challenge-private/" webroot_folder = "/tmp/acme-challenge-public/" @@ -71,8 +69,6 @@ intermediate_certificate_url = "https://letsencrypt.org/certs/lets-encrypt-x3-cr # Front-end stuff # ############################################################################### -# Status - def certificate_status(auth, domain_list, full=False): """ @@ -139,8 +135,6 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si certificate_install_letsencrypt(auth, domain_list, force, no_checks) -# Install self-signed - def certificate_install_selfsigned(domain_list, force=False): for domain in domain_list: @@ -152,7 +146,6 @@ def certificate_install_selfsigned(domain_list, force=False): cert_folder_domain = os.path.join(cert_folder, domain) - # Create cert folder if it does not exists yet if not os.path.exists(cert_folder_domain): os.makedirs(cert_folder_domain) From ac9f2643a755d5ebb2e2a11c4b5f08877b55e367 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 13:40:26 +0100 Subject: [PATCH 046/208] [mod] move a part of os.system calls to native shutil/os --- src/yunohost/certificate.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 43c64aa34..c8d55170c 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -154,25 +154,32 @@ def certificate_install_selfsigned(domain_list, force=False): with open(os.path.join(ssl_dir, 'serial'), 'r') as f: serial = f.readline().rstrip() + shutil.copyfile(os.path.join(ssl_dir, "openssl.cnf"), os.path.join(cert_folder_domain, "openssl.cnf")) + # FIXME : should refactor this to avoid so many os.system() calls... # We should be able to do all this using OpenSSL.crypto and os/shutil command_list = [ - 'cp %s/openssl.cnf %s' % (ssl_dir, cert_folder_domain), 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, cert_folder_domain), 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch' % (cert_folder_domain, ssl_dir, ssl_dir), 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch' % (cert_folder_domain, ssl_dir, ssl_dir), - 'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % cert_folder_domain, - 'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, cert_folder_domain), - 'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, cert_folder_domain), - 'cat %s/ca.pem >> %s/crt.pem' % (cert_folder_domain, cert_folder_domain) ] for command in command_list: if os.system(command) != 0: raise MoulinetteError(errno.EIO, m18n.n('certmanager_domain_cert_gen_failed')) + os.symlink('/etc/ssl/certs/ca-yunohost_crt.pem', os.path.join(cert_folder_domain, "ca.pem")) + shutil.copyfile(os.path.join(ssl_dir, "certs", "yunohost_key.pem"), os.path.join(cert_folder_domain, "key.pem")) + shutil.copyfile(os.path.join(ssl_dir, "newcerts", "%s.pem" % serial), os.path.join(cert_folder_domain, "crt.pem")) + + # append ca.pem at the end of crt.pem + with open(os.path.join(cert_folder_domain, "ca.pem"), "r") as ca_pem: + with open(os.path.join(cert_folder_domain, "crt.pem"), "a") as crt_pem: + crt_pem.write("\n") + crt_pem.write(ca_pem.read()) + _set_permissions(cert_folder_domain, "root", "root", 0755) _set_permissions(os.path.join(cert_folder_domain, "key.pem"), "root", "metronome", 0640) _set_permissions(os.path.join(cert_folder_domain, "crt.pem"), "root", "metronome", 0640) From a1b42e65f8ddc61902fdc6066b7ea59d7de77cd7 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 13:49:40 +0100 Subject: [PATCH 047/208] [mod] remove useless variables --- src/yunohost/app.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e3d5ad202..83fc8614c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1041,14 +1041,11 @@ def app_ssowatconf(auth): continue # Check the file contains the ACME challenge uri - acme_uri = '/.well-known/acme-challenge' - - if not acme_uri in open(nginx_acme_challenge_conf_file).read(): + if not '/.well-known/acme-challenge' in open(nginx_acme_challenge_conf_file).read(): continue # If so, then authorize the ACME challenge uri to unprotected regex - regex = domain + "/%.well%-known/acme%-challenge/.*$" - unprotected_regex.append(regex) + unprotected_regex.append(domain + "/%.well%-known/acme%-challenge/.*$") conf_dict = { From bd41f0d4a2c1555ea63940db21713e73c9bfe823 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 13:55:04 +0100 Subject: [PATCH 048/208] [mod] os.path.join --- 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 c8d55170c..e71d6cdeb 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -487,7 +487,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): def _get_status(domain): - cert_file = cert_folder + "/" + domain + "/crt.pem" + cert_file = os.path.join(cert_folder, domain, "crt.pem") if not os.path.isfile(cert_file): return {} From 248508dcaa824018d031aeb75e447df6bb8fb95c Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 14:00:42 +0100 Subject: [PATCH 049/208] [mod] do not uses tabulate, the api needs json --- src/yunohost/certificate.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index e71d6cdeb..c8ea58d71 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -90,32 +90,19 @@ def certificate_status(auth, domain_list, full=False): if domain not in yunohost_domains_list: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) - # Get status for each domain, and prepare display using tabulate - if not full: - headers = ["Domain", "Certificate status", "Authority type", "Days remaining"] - else: - headers = ["Domain", "Certificate subject", "Certificate status", "Authority type", "Authority name", "Days remaining"] - lines = [] + for domain in domain_list: status = _get_status(domain) + status["summaryCode"] = _summary_code_to_string(status["summaryCode"]) - line = [] - line.append(domain) + if not full: + del status["subject"] + del status["CAname"] - if full: - line.append(status["subject"]) + lines.append(status) - line.append(_summary_code_to_string(status["summaryCode"])) - line.append(status["CAtype"]) - - if full: - line.append(status["CAname"]) - - line.append(status["validity"]) - lines.append(line) - - print(tabulate(lines, headers=headers, tablefmt="simple", stralign="center")) + return lines def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False): From 299aa978f717a9d3e3f04b9f0a3cbb6d2d031caa Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 14:06:34 +0100 Subject: [PATCH 050/208] [mod] simplify code --- 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 c8ea58d71..8c2662148 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -128,7 +128,7 @@ def certificate_install_selfsigned(domain_list, force=False): # Check we ain't trying to overwrite a good cert ! status = _get_status(domain) - if status != {} and status["summaryCode"] > 0 and not force: + if status and status["summaryCode"] > 0 and not force: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) cert_folder_domain = os.path.join(cert_folder, domain) From 289eb014806de3d7c4609bb989c4abbbc002b175 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 15:21:23 +0100 Subject: [PATCH 051/208] [mod] small opti, getting domain list can be long --- src/yunohost/certificate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 8c2662148..9a3c564b0 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -191,8 +191,8 @@ def certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=Fa # Else, validate that yunohost knows the domains given else: for domain in domain_list: - # Is it in Yunohost dmomain list ? - if domain not in yunohost.domain.domain_list(auth)['domains']: + yunohost_domains_list = yunohost.domain.domain_list(auth)['domains'] + if domain not in yunohost_domains_list: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) # Is it self-signed ? From fef1ca08c56d8b0c3530f861cca3982ad4bab619 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 30 Oct 2016 11:48:27 -0400 Subject: [PATCH 052/208] Changing name of cron job to be consistent with other yunohost crons, as requested by @opi --- 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 9a3c564b0..19e9cea38 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -294,7 +294,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal ############################################################################### def _install_cron(): - cron_job_file = "/etc/cron.weekly/certificateRenewer" + cron_job_file = "/etc/cron.weekly/yunohost-certificate-renew" with open(cron_job_file, "w") as f: f.write("#!/bin/bash\n") From 7f8aa4cc75d4e644bd80b148988cb4449ae51477 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 16:14:19 +0100 Subject: [PATCH 053/208] [mod] remove useless assign --- src/yunohost/certificate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 19e9cea38..aa8a77efb 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -491,7 +491,6 @@ def _get_status(domain): valid_up_to = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ") days_remaining = (valid_up_to - datetime.now()).days - CA_type = None if cert_issuer == _name_self_CA(): CA_type = "Self-signed" From 2d89964bc7ef5aae06f253dd67a5d387e99c676b Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 17:15:06 +0100 Subject: [PATCH 054/208] [enh] include tracebak into error email --- src/yunohost/certificate.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index aa8a77efb..94be8ee3e 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -281,12 +281,17 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) except Exception as e: + import traceback + from StringIO import StringIO + stack = StringIO() + traceback.print_exc(file=stack) logger.error("Certificate renewing for %s failed !", domain) + logger.error(stack.getvalue()) logger.error(str(e)) if email: logger.error("Sending email with details to root ...") - _email_renewing_failed(domain, e) + _email_renewing_failed(domain, e, stack.getvalue()) ############################################################################### @@ -303,7 +308,7 @@ def _install_cron(): _set_permissions(cron_job_file, "root", "root", 0755) -def _email_renewing_failed(domain, exception_message): +def _email_renewing_failed(domain, exception_message, stack): from_ = "certmanager@%s (Certificate Manager)" % domain to_ = "root" subject_ = "Certificate renewing attempt for %s failed!" % domain @@ -313,6 +318,7 @@ def _email_renewing_failed(domain, exception_message): At attempt for renewing the certificate for domain %s failed with the following error : +%s %s Here's the tail of /var/log/yunohost/yunohost-cli.log, which might help to @@ -322,7 +328,7 @@ investigate : -- Certificate Manager -""" % (domain, exception_message, logs) +""" % (domain, exception_message, stack, logs) message = """ From: %s From 5495281e83eaefd1b8aadc63ad65953efd253129 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 17:34:16 +0100 Subject: [PATCH 055/208] [mod] remove the summary code concept and switch to code/verbose duet instead --- src/yunohost/certificate.py | 110 ++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 94be8ee3e..680149506 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -94,11 +94,10 @@ def certificate_status(auth, domain_list, full=False): for domain in domain_list: status = _get_status(domain) - status["summaryCode"] = _summary_code_to_string(status["summaryCode"]) if not full: del status["subject"] - del status["CAname"] + del status["CA_name"] lines.append(status) @@ -128,7 +127,7 @@ def certificate_install_selfsigned(domain_list, force=False): # Check we ain't trying to overwrite a good cert ! status = _get_status(domain) - if status and status["summaryCode"] > 0 and not force: + if status and status["summary"]["code"] in ('good', 'great') and not force: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) cert_folder_domain = os.path.join(cert_folder, domain) @@ -183,7 +182,7 @@ def certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=Fa for domain in yunohost.domain.domain_list(auth)['domains']: status = _get_status(domain) - if status["CAtype"] != "Self-signed": + if status["CA_type"]["code"] != "self-signed": continue domain_list.append(domain) @@ -197,7 +196,7 @@ def certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=Fa # Is it self-signed ? status = _get_status(domain) - if not force and status["CAtype"] != "Self-signed": + if not force and status["CA_type"]["code"] != "self-signed": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_cert_not_selfsigned', domain=domain)) # Actual install steps @@ -240,7 +239,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal # Does it has a Let's Encrypt cert ? status = _get_status(domain) - if status["CAtype"] != "Let's Encrypt": + if status["CA_type"]["code"] != "lets-encrypt": continue # Does it expires soon ? @@ -265,7 +264,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain)) # Does it has a Let's Encrypt cert ? - if status["CAtype"] != "Let's Encrypt": + if status["CA_type"]["code"] != "lets-encrypt": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain)) # Actual renew steps @@ -444,9 +443,9 @@ def _fetch_and_enable_new_certificate(domain): os.symlink(new_cert_folder, live_link) # Check the status of the certificate is now good - status_summary_code = _get_status(domain)["summaryCode"] + status_summary = _get_status(domain)["summary"] - if status_summary_code < 20: + if status_summary["code"] != "great": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_certificate_fetching_or_enabling_failed', domain=domain)) logger.info("Restarting services...") @@ -498,47 +497,72 @@ def _get_status(domain): days_remaining = (valid_up_to - datetime.now()).days if cert_issuer == _name_self_CA(): - CA_type = "Self-signed" + CA_type = { + "code": "self-signed", + "verbose": "Self-signed", + } elif cert_issuer.startswith("Let's Encrypt"): - CA_type = "Let's Encrypt" + CA_type = { + "code": "lets-encrypt", + "verbose": "Let's Encrypt", + } elif cert_issuer.startswith("Fake LE"): - CA_type = "Fake Let's Encrypt" + CA_type = { + "code": "fake-lets-encrypt", + "verbose": "Fake Let's Encrypt", + } else: - CA_type = "Other / Unknown" + CA_type = { + "code": "other-unknown", + "verbose": "Other / Unknown", + } - # Unknown by default - status_summary_code = 0 - - # Critical if days_remaining <= 0: - status_summary_code = -30 + status_summary = { + "code": "critical", + "verbose": "CRITICAL", + } - # Warning, self-signed, browser will display a warning discouraging visitors to enter website - elif CA_type == "Self-signed" or CA_type == "Fake Let's Encrypt": - status_summary_code = -20 + elif CA_type["code"] in ("self-signed","fake-lets-encrypt"): + status_summary = { + "code": "warning", + "verbose": "WARNING", + } - # Attention, certificate will expire soon (should be renewed automatically if Let's Encrypt) elif days_remaining < validity_limit: - status_summary_code = -10 + status_summary = { + "code": "attention", + "verbose": "About to expire", + } - # CA not known, but still a valid certificate, so okay ! - elif CA_type == "Other / Unknown": - status_summary_code = 10 + elif CA_type["code"] == "other-unknown": + status_summary = { + "code": "good", + "verbose": "Good", + } - # Let's Encrypt, great ! - elif CA_type == "Let's Encrypt": - status_summary_code = 20 + elif CA_type["code"] == "lets-encrypt": + status_summary = { + "code": "great", + "verbose": "Great!", + } + + else: + status_summary = { + "code": "unknown", + "verbose": "Unknown?", + } return { "domain": domain, "subject": cert_subject, - "CAname": cert_issuer, - "CAtype": CA_type, + "CA_name": cert_issuer, + "CA_type": CA_type, "validity": days_remaining, - "summaryCode": status_summary_code + "summary": status_summary, } ############################################################################### @@ -629,28 +653,6 @@ def _domain_is_accessible_through_HTTP(ip, domain): return True -def _summary_code_to_string(code): - if code <= -30: - return "CRITICAL" - - if code <= -20: - return "WARNING" - - if code <= -10: - return "Attention" - - if code <= 0: - return "Unknown?" - - if code <= 10: - return "Good" - - if code <= 20: - return "Great!" - - return "Unknown?" - - def _name_self_CA(): cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(selfCA_file).read()) return cert.get_subject().CN From 0e7552263d6fe5d4b00f32339656fcc1da8671a2 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 17:34:32 +0100 Subject: [PATCH 056/208] [mod] I only need to reload nginx, not restart it --- src/yunohost/certificate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 680149506..91dd3fc2a 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -450,9 +450,11 @@ def _fetch_and_enable_new_certificate(domain): logger.info("Restarting services...") - for service in ("nginx", "postfix", "dovecot", "metronome"): + for service in ("postfix", "dovecot", "metronome"): _run_service_command("restart", service) + _run_service_command("reload", "nginx") + def _prepare_certificate_signing_request(domain, key_file, output_folder): # Init a request From 11d785a22105c2937410d92f40ccff3e4d3c51d2 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 18:05:37 +0100 Subject: [PATCH 057/208] [mod] remove useless import --- src/yunohost/certificate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 91dd3fc2a..826b70a89 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -33,7 +33,6 @@ import requests from OpenSSL import crypto from datetime import datetime -from tabulate import tabulate from acme_tiny import get_crt as sign_certificate from moulinette.core import MoulinetteError From f1188782e29ea001f435224bf728afcdef9208c0 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 18:08:34 +0100 Subject: [PATCH 058/208] [mod] top level constants should be upper case (pep8) --- src/yunohost/certificate.py | 74 ++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 826b70a89..0b583f805 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -46,23 +46,23 @@ from yunohost.service import _run_service_command logger = getActionLogger('yunohost.certmanager') -cert_folder = "/etc/yunohost/certs/" -tmp_folder = "/tmp/acme-challenge-private/" -webroot_folder = "/tmp/acme-challenge-public/" +CERT_FOLDER = "/etc/yunohost/certs/" +TMP_FOLDER = "/tmp/acme-challenge-private/" +WEBROOT_FOLDER = "/tmp/acme-challenge-public/" -selfCA_file = "/etc/ssl/certs/ca-yunohost_crt.pem" -account_key_file = "/etc/yunohost/letsencrypt_account.pem" +SELF_CA_FILE = "/etc/ssl/certs/ca-yunohost_crt.pem" +ACCOUNT_KEY_FILE = "/etc/yunohost/letsencrypt_account.pem" -key_size = 2048 +KEY_SIZE = 2048 -validity_limit = 15 # days +VALIDITY_LIMIT = 15 # days # For tests -#certification_authority = "https://acme-staging.api.letsencrypt.org" +#CERTIFICATION_AUTHORITY = "https://acme-staging.api.letsencrypt.org" # For prod -certification_authority = "https://acme-v01.api.letsencrypt.org" +CERTIFICATION_AUTHORITY = "https://acme-v01.api.letsencrypt.org" -intermediate_certificate_url = "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem" +INTERMEDIATE_CERTIFICATE_URL = "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem" ############################################################################### # Front-end stuff # @@ -129,7 +129,7 @@ def certificate_install_selfsigned(domain_list, force=False): if status and status["summary"]["code"] in ('good', 'great') and not force: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) - cert_folder_domain = os.path.join(cert_folder, domain) + cert_folder_domain = os.path.join(CERT_FOLDER, domain) if not os.path.exists(cert_folder_domain): os.makedirs(cert_folder_domain) @@ -172,7 +172,7 @@ def certificate_install_selfsigned(domain_list, force=False): def certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False): - if not os.path.exists(account_key_file): + if not os.path.exists(ACCOUNT_KEY_FILE): _generate_account_key() # If no domains given, consider all yunohost domains with self-signed @@ -242,7 +242,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal continue # Does it expires soon ? - if force or status["validity"] <= validity_limit: + if force or status["validity"] <= VALIDITY_LIMIT: domain_list.append(domain) if len(domain_list) == 0: @@ -259,7 +259,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal status = _get_status(domain) # Does it expires soon ? - if not force or status["validity"] <= validity_limit: + if not force or status["validity"] <= VALIDITY_LIMIT: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain)) # Does it has a Let's Encrypt cert ? @@ -350,7 +350,7 @@ location '/.well-known/acme-challenge' default_type "text/plain"; alias %s; } - ''' % webroot_folder + ''' % WEBROOT_FOLDER # Write the conf if os.path.exists(nginx_conf_file): @@ -374,36 +374,36 @@ def _fetch_and_enable_new_certificate(domain): # Make sure tmp folder exists logger.debug("Making sure tmp folders exists...") - if not os.path.exists(webroot_folder): - os.makedirs(webroot_folder) + if not os.path.exists(WEBROOT_FOLDER): + os.makedirs(WEBROOT_FOLDER) - if not os.path.exists(tmp_folder): - os.makedirs(tmp_folder) + if not os.path.exists(TMP_FOLDER): + os.makedirs(TMP_FOLDER) - _set_permissions(webroot_folder, "root", "www-data", 0650) - _set_permissions(tmp_folder, "root", "root", 0640) + _set_permissions(WEBROOT_FOLDER, "root", "www-data", 0650) + _set_permissions(TMP_FOLDER, "root", "root", 0640) # Prepare certificate signing request logger.info("Prepare key and certificate signing request (CSR) for %s...", domain) - domain_key_file = "%s/%s.pem" % (tmp_folder, domain) + domain_key_file = "%s/%s.pem" % (TMP_FOLDER, domain) _generate_key(domain_key_file) _set_permissions(domain_key_file, "root", "metronome", 0640) - _prepare_certificate_signing_request(domain, domain_key_file, tmp_folder) + _prepare_certificate_signing_request(domain, domain_key_file, TMP_FOLDER) # Sign the certificate logger.info("Now using ACME Tiny to sign the certificate...") - domain_csr_file = "%s/%s.csr" % (tmp_folder, domain) + domain_csr_file = "%s/%s.csr" % (TMP_FOLDER, domain) - signed_certificate = sign_certificate(account_key_file, + signed_certificate = sign_certificate(ACCOUNT_KEY_FILE, domain_csr_file, - webroot_folder, + WEBROOT_FOLDER, log=logger, - CA=certification_authority) + CA=CERTIFICATION_AUTHORITY) - intermediate_certificate = requests.get(intermediate_certificate_url).text + intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL).text # Now save the key and signed certificate logger.info("Saving the key and signed certificate...") @@ -411,7 +411,7 @@ def _fetch_and_enable_new_certificate(domain): # Create corresponding directory date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") - new_cert_folder = "%s/%s.%s" % (cert_folder, domain, date_tag) + new_cert_folder = "%s/%s.%s" % (CERT_FOLDER, domain, date_tag) os.makedirs(new_cert_folder) _set_permissions(new_cert_folder, "root", "root", 0655) @@ -431,7 +431,7 @@ def _fetch_and_enable_new_certificate(domain): logger.info("Enabling the new certificate...") # Replace (if necessary) the link or folder for live cert - live_link = os.path.join(cert_folder, domain) + live_link = os.path.join(CERT_FOLDER, domain) if not os.path.islink(live_link): shutil.rmtree(live_link) # Well, yep, hopefully that's not too dangerous (directory should have been backuped before calling this command) @@ -480,7 +480,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): def _get_status(domain): - cert_file = os.path.join(cert_folder, domain, "crt.pem") + cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem") if not os.path.isfile(cert_file): return {} @@ -533,7 +533,7 @@ def _get_status(domain): "verbose": "WARNING", } - elif days_remaining < validity_limit: + elif days_remaining < VALIDITY_LIMIT: status_summary = { "code": "attention", "verbose": "About to expire", @@ -573,13 +573,13 @@ def _get_status(domain): def _generate_account_key(): logger.info("Generating account key ...") - _generate_key(account_key_file) - _set_permissions(account_key_file, "root", "root", 0400) + _generate_key(ACCOUNT_KEY_FILE) + _set_permissions(ACCOUNT_KEY_FILE, "root", "root", 0400) def _generate_key(destination_path): k = crypto.PKey() - k.generate_key(crypto.TYPE_RSA, key_size) + k.generate_key(crypto.TYPE_RSA, KEY_SIZE) with open(destination_path, "w") as f: f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k)) @@ -596,7 +596,7 @@ def _set_permissions(path, user, group, permissions): def _backup_current_cert(domain): logger.info("Backuping existing certificate for domain " + domain) - cert_folder_domain = os.path.join(cert_folder, domain) + cert_folder_domain = os.path.join(CERT_FOLDER, domain) date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") backup_folder = "%s-backup-%s" % (cert_folder_domain, date_tag) @@ -655,7 +655,7 @@ def _domain_is_accessible_through_HTTP(ip, domain): def _name_self_CA(): - cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(selfCA_file).read()) + cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(SELF_CA_FILE).read()) return cert.get_subject().CN From 718011c0ee01137d51fea265ce0b0eb1c435d7af Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 18:48:52 +0100 Subject: [PATCH 059/208] [mod] use logger string concatenation api --- 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 0b583f805..3e80c48f5 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -473,7 +473,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Save the request in tmp folder csr_file = output_folder + domain + ".csr" - logger.info("Saving to " + csr_file + " .") + logger.info("Saving to %s.", csr_file) with open(csr_file, "w") as f: f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr)) From bbbac248b6c0f1ce231e89b6454b439609e3d012 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 30 Oct 2016 18:50:36 +0100 Subject: [PATCH 060/208] [mod] use logger string concatenation api --- 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 3e80c48f5..5d643062f 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -594,7 +594,7 @@ def _set_permissions(path, user, group, permissions): def _backup_current_cert(domain): - logger.info("Backuping existing certificate for domain " + domain) + logger.info("Backuping existing certificate for domain %s", domain) cert_folder_domain = os.path.join(CERT_FOLDER, domain) From 59500c3acc8d6c357ab84ffe684a9a101b9a7e89 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 2 Nov 2016 08:37:47 -0400 Subject: [PATCH 061/208] Adding acme-tiny as a dependency in vendor folder --- src/yunohost/certificate.py | 2 +- src/yunohost/vendor/__init__.py | 0 src/yunohost/vendor/acme_tiny/__init__.py | 0 src/yunohost/vendor/acme_tiny/acme_tiny.py | 198 +++++++++++++++++++++ 4 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/yunohost/vendor/__init__.py create mode 100644 src/yunohost/vendor/acme_tiny/__init__.py create mode 100644 src/yunohost/vendor/acme_tiny/acme_tiny.py diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 5d643062f..636a5e72c 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -33,7 +33,7 @@ import requests from OpenSSL import crypto from datetime import datetime -from acme_tiny import get_crt as sign_certificate +from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger diff --git a/src/yunohost/vendor/__init__.py b/src/yunohost/vendor/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/yunohost/vendor/acme_tiny/__init__.py b/src/yunohost/vendor/acme_tiny/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/yunohost/vendor/acme_tiny/acme_tiny.py b/src/yunohost/vendor/acme_tiny/acme_tiny.py new file mode 100644 index 000000000..0f4cb431f --- /dev/null +++ b/src/yunohost/vendor/acme_tiny/acme_tiny.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python +import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging +try: + from urllib.request import urlopen # Python 3 +except ImportError: + from urllib2 import urlopen # Python 2 + +#DEFAULT_CA = "https://acme-staging.api.letsencrypt.org" +DEFAULT_CA = "https://acme-v01.api.letsencrypt.org" + +LOGGER = logging.getLogger(__name__) +LOGGER.addHandler(logging.StreamHandler()) +LOGGER.setLevel(logging.INFO) + +def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): + # helper function base64 encode for jose spec + def _b64(b): + return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "") + + # parse account key to get public key + log.info("Parsing account key...") + proc = subprocess.Popen(["openssl", "rsa", "-in", account_key, "-noout", "-text"], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + if proc.returncode != 0: + raise IOError("OpenSSL Error: {0}".format(err)) + pub_hex, pub_exp = re.search( + r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)", + out.decode('utf8'), re.MULTILINE|re.DOTALL).groups() + pub_exp = "{0:x}".format(int(pub_exp)) + pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp + header = { + "alg": "RS256", + "jwk": { + "e": _b64(binascii.unhexlify(pub_exp.encode("utf-8"))), + "kty": "RSA", + "n": _b64(binascii.unhexlify(re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))), + }, + } + accountkey_json = json.dumps(header['jwk'], sort_keys=True, separators=(',', ':')) + thumbprint = _b64(hashlib.sha256(accountkey_json.encode('utf8')).digest()) + + # helper function make signed requests + def _send_signed_request(url, payload): + payload64 = _b64(json.dumps(payload).encode('utf8')) + protected = copy.deepcopy(header) + protected["nonce"] = urlopen(CA + "/directory").headers['Replay-Nonce'] + protected64 = _b64(json.dumps(protected).encode('utf8')) + proc = subprocess.Popen(["openssl", "dgst", "-sha256", "-sign", account_key], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate("{0}.{1}".format(protected64, payload64).encode('utf8')) + if proc.returncode != 0: + raise IOError("OpenSSL Error: {0}".format(err)) + data = json.dumps({ + "header": header, "protected": protected64, + "payload": payload64, "signature": _b64(out), + }) + try: + resp = urlopen(url, data.encode('utf8')) + return resp.getcode(), resp.read() + except IOError as e: + return getattr(e, "code", None), getattr(e, "read", e.__str__)() + + # find domains + log.info("Parsing CSR...") + proc = subprocess.Popen(["openssl", "req", "-in", csr, "-noout", "-text"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + if proc.returncode != 0: + raise IOError("Error loading {0}: {1}".format(csr, err)) + domains = set([]) + common_name = re.search(r"Subject:.*? CN=([^\s,;/]+)", out.decode('utf8')) + if common_name is not None: + domains.add(common_name.group(1)) + subject_alt_names = re.search(r"X509v3 Subject Alternative Name: \n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE|re.DOTALL) + if subject_alt_names is not None: + for san in subject_alt_names.group(1).split(", "): + if san.startswith("DNS:"): + domains.add(san[4:]) + + # get the certificate domains and expiration + log.info("Registering account...") + code, result = _send_signed_request(CA + "/acme/new-reg", { + "resource": "new-reg", + "agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf", + }) + if code == 201: + log.info("Registered!") + elif code == 409: + log.info("Already registered!") + else: + raise ValueError("Error registering: {0} {1}".format(code, result)) + + # verify each domain + for domain in domains: + log.info("Verifying {0}...".format(domain)) + + # get new challenge + code, result = _send_signed_request(CA + "/acme/new-authz", { + "resource": "new-authz", + "identifier": {"type": "dns", "value": domain}, + }) + if code != 201: + raise ValueError("Error requesting challenges: {0} {1}".format(code, result)) + + # make the challenge file + challenge = [c for c in json.loads(result.decode('utf8'))['challenges'] if c['type'] == "http-01"][0] + token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token']) + keyauthorization = "{0}.{1}".format(token, thumbprint) + wellknown_path = os.path.join(acme_dir, token) + with open(wellknown_path, "w") as wellknown_file: + wellknown_file.write(keyauthorization) + + # check that the file is in place + wellknown_url = "http://{0}/.well-known/acme-challenge/{1}".format(domain, token) + try: + resp = urlopen(wellknown_url) + resp_data = resp.read().decode('utf8').strip() + assert resp_data == keyauthorization + except (IOError, AssertionError): + os.remove(wellknown_path) + raise ValueError("Wrote file to {0}, but couldn't download {1}".format( + wellknown_path, wellknown_url)) + + # notify challenge are met + code, result = _send_signed_request(challenge['uri'], { + "resource": "challenge", + "keyAuthorization": keyauthorization, + }) + if code != 202: + raise ValueError("Error triggering challenge: {0} {1}".format(code, result)) + + # wait for challenge to be verified + while True: + try: + resp = urlopen(challenge['uri']) + challenge_status = json.loads(resp.read().decode('utf8')) + except IOError as e: + raise ValueError("Error checking challenge: {0} {1}".format( + e.code, json.loads(e.read().decode('utf8')))) + if challenge_status['status'] == "pending": + time.sleep(2) + elif challenge_status['status'] == "valid": + log.info("{0} verified!".format(domain)) + os.remove(wellknown_path) + break + else: + raise ValueError("{0} challenge did not pass: {1}".format( + domain, challenge_status)) + + # get the new certificate + log.info("Signing certificate...") + proc = subprocess.Popen(["openssl", "req", "-in", csr, "-outform", "DER"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + csr_der, err = proc.communicate() + code, result = _send_signed_request(CA + "/acme/new-cert", { + "resource": "new-cert", + "csr": _b64(csr_der), + }) + if code != 201: + raise ValueError("Error signing certificate: {0} {1}".format(code, result)) + + # return signed certificate! + log.info("Certificate signed!") + return """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format( + "\n".join(textwrap.wrap(base64.b64encode(result).decode('utf8'), 64))) + +def main(argv): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent("""\ + This script automates the process of getting a signed TLS certificate from + Let's Encrypt using the ACME protocol. It will need to be run on your server + and have access to your private account key, so PLEASE READ THROUGH IT! It's + only ~200 lines, so it won't take long. + + ===Example Usage=== + python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /usr/share/nginx/html/.well-known/acme-challenge/ > signed.crt + =================== + + ===Example Crontab Renewal (once per month)=== + 0 0 1 * * python /path/to/acme_tiny.py --account-key /path/to/account.key --csr /path/to/domain.csr --acme-dir /usr/share/nginx/html/.well-known/acme-challenge/ > /path/to/signed.crt 2>> /var/log/acme_tiny.log + ============================================== + """) + ) + parser.add_argument("--account-key", required=True, help="path to your Let's Encrypt account private key") + parser.add_argument("--csr", required=True, help="path to your certificate signing request") + parser.add_argument("--acme-dir", required=True, help="path to the .well-known/acme-challenge/ directory") + parser.add_argument("--quiet", action="store_const", const=logging.ERROR, help="suppress output except for errors") + parser.add_argument("--ca", default=DEFAULT_CA, help="certificate authority, default is Let's Encrypt") + + args = parser.parse_args(argv) + LOGGER.setLevel(args.quiet or LOGGER.level) + signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, log=LOGGER, CA=args.ca) + sys.stdout.write(signed_crt) + +if __name__ == "__main__": # pragma: no cover + main(sys.argv[1:]) From f3461b36364a3613e5d50648ce52c355f5d466fa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Nov 2016 12:24:59 -0500 Subject: [PATCH 062/208] Fixing typo in arguments to match actionmap --- src/yunohost/domain.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 1a296ee97..aadd9086d 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -257,16 +257,16 @@ def domain_dns_conf(domain, ttl=None): return result -def domain_cert_status(auth, domainList, full=False): - return yunohost.certificate.certificate_status(auth, domainList, full) +def domain_cert_status(auth, domain_list, full=False): + return yunohost.certificate.certificate_status(auth, domain_list, full) -def domain_cert_install(auth, domainList, force=False, no_checks=False, self_signed=False): - return yunohost.certificate.certificate_install(auth, domainList, force, no_checks, self_signed) +def domain_cert_install(auth, domain_list, force=False, no_checks=False, self_signed=False): + return yunohost.certificate.certificate_install(auth, domain_list, force, no_checks, self_signed) -def domain_cert_renew(auth, domainList, force=False, no_checks=False, email=False): - return yunohost.certificate.certificate_renew(auth, domainList, force, no_checks, email) +def domain_cert_renew(auth, domain_list, force=False, no_checks=False, email=False): + return yunohost.certificate.certificate_renew(auth, domain_list, force, no_checks, email) def get_public_ip(protocol=4): From 56cd9610f353c030e6a3c62fcd34879ce907c9e9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Nov 2016 14:02:59 -0500 Subject: [PATCH 063/208] Improving CLI display of cert-status --- 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 636a5e72c..78c0c2ed5 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -100,7 +100,7 @@ def certificate_status(auth, domain_list, full=False): lines.append(status) - return lines + return { "certificates" : lines } def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False): From 90e63edcfe5af42b6655a582157962f02c7592bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Nov 2016 14:19:28 -0500 Subject: [PATCH 064/208] Improving/cleaning CLI display of cert-status --- src/yunohost/certificate.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 78c0c2ed5..d2a67495a 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -89,7 +89,7 @@ def certificate_status(auth, domain_list, full=False): if domain not in yunohost_domains_list: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) - lines = [] + certificates = {} for domain in domain_list: status = _get_status(domain) @@ -97,10 +97,13 @@ def certificate_status(auth, domain_list, full=False): if not full: del status["subject"] del status["CA_name"] + status["CA_type"] = status["CA_type"]["verbose"] + status["summary"] = status["summary"]["verbose"] - lines.append(status) + del status["domain"] + certificates[domain] = status - return { "certificates" : lines } + return { "certificates" : certificates } def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False): From 4ddc3aac369cb20ba672553748e04eb218c33b34 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Nov 2016 12:09:02 -0500 Subject: [PATCH 065/208] Display a warning message when letsencrypt is installed, suggesting commands to migrate --- locales/en.json | 3 ++- src/yunohost/certificate.py | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index bdfd1b14a..1a93b72ff 100644 --- a/locales/en.json +++ b/locales/en.json @@ -251,5 +251,6 @@ "certmanager_no_A_dns_record" : "No DNS record of type A found for {domain:s}. You need to configure the DNS for your domain before installing a certificate !", "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_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain:s} !" + "certmanager_cert_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain:s} !", + "certmanager_old_letsencrypt_app_detected" : "Command aborted because the letsencrypt app is conflicting with the yunohost certificate management features." } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index d2a67495a..f9f5784a0 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -40,7 +40,7 @@ from moulinette.utils.log import getActionLogger import yunohost.domain -from yunohost.app import app_ssowatconf +from yunohost.app import app_ssowatconf, app_list from yunohost.service import _run_service_command @@ -78,6 +78,9 @@ def certificate_status(auth, domain_list, full=False): full -- Display more info about the certificates """ + # Check if old letsencrypt_ynh is installed + _check_old_letsencrypt_app() + # If no domains given, consider all yunohost domains if domain_list == []: domain_list = yunohost.domain.domain_list(auth)['domains'] @@ -107,6 +110,7 @@ def certificate_status(auth, domain_list, full=False): def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False): + """ Install a Let's Encrypt certificate for given domains (all by default) @@ -117,6 +121,11 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si before attempting the install self-signed -- Instal self-signed certificates instead of Let's Encrypt """ + + # Check if old letsencrypt_ynh is installed + _check_old_letsencrypt_app() + + if self_signed: certificate_install_selfsigned(domain_list, force) else: @@ -234,6 +243,9 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal email -- Emails root if some renewing failed """ + # Check if old letsencrypt_ynh is installed + _check_old_letsencrypt_app() + # If no domains given, consider all yunohost domains with Let's Encrypt # certificates if domain_list == []: @@ -299,6 +311,29 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal # Back-end stuff # ############################################################################### +def _check_old_letsencrypt_app(): + + installedAppIds = [ app["id"] for app in yunohost.app.app_list(installed=True)["apps"] ] + if ("letsencrypt" not in installedAppIds) : + return + + logger.warning(" ") + logger.warning("Yunohost detected that the 'letsencrypt' app is installed, ") + logger.warning("which conflits with the new certificate management features") + logger.warning("directly integrated in Yunohost. If you wish to use these ") + logger.warning("new features, please run the following commands to migrate ") + logger.warning("your installation :") + logger.warning(" ") + logger.warning(" yunohost app remove letsencrypt") + logger.warning(" yunohost domain cert-install") + logger.warning(" ") + logger.warning("N.B. : this will attempt to re-install certificates for ") + logger.warning("all domains with a Let's Encrypt certificate or self-signed") + logger.warning("certificate.") + logger.warning(" ") + + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_old_letsencrypt_app_detected')) + def _install_cron(): cron_job_file = "/etc/cron.weekly/yunohost-certificate-renew" From cbc71f2530853f146cdebbd33498574869be1476 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Nov 2016 12:55:01 -0500 Subject: [PATCH 066/208] Adding a check for the presence of the ssowat header when checking domain is accessible through http --- src/yunohost/certificate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index f9f5784a0..19758c33d 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -685,7 +685,10 @@ def _dns_ip_match_public_ip(public_ip, domain): def _domain_is_accessible_through_HTTP(ip, domain): try: - requests.head("http://" + ip, headers={"Host": domain}) + r = requests.head("http://" + ip, headers={"Host": domain}) + # Check we got the ssowat header in the response + if ("x-sso-wat" not in r.headers.keys()) : + return False except Exception: return False From 6bfe1c80833423b0530f368fddcc05ccdf304629 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Nov 2016 20:59:55 -0500 Subject: [PATCH 067/208] Check that the DNS A record matches the global IP now using dnspython and FDN's DNS --- locales/en.json | 6 ++---- src/yunohost/certificate.py | 31 +++++++++---------------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/locales/en.json b/locales/en.json index 1a93b72ff..627c11b52 100644 --- a/locales/en.json +++ b/locales/en.json @@ -245,10 +245,8 @@ "certmanager_attempt_to_renew_nonLE_cert" : "The certificate of domain {domain:s} is not issued by Let's Encrypt. Cannot renew it automatically !", "certmanager_attempt_to_renew_valid_cert" : "The certificate of domain {domain:s} is not about to expire ! Use --force to bypass", "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_error_contacting_dns_api" : "Error contacting the DNS API ({api:s}), reason: {reason:s}. Use --no-checks to disable checks.", - "certmanager_error_parsing_dns" : "Error parsing the return value from the DNS API : {value:s}. Please verify your DNS configuration for domain {domain:s}. Use --no-checks to disable checks.", - "certmanager_domain_dns_ip_differs_from_public_ip" : "The DNS 'A' record for domain {domain:s} is different from this server IP. Give some time for the DNS to refresh, or use --no-checks to disable checks.", - "certmanager_no_A_dns_record" : "No DNS record of type A found for {domain:s}. You need to configure the DNS for your domain before installing a certificate !", + "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_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_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_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain:s} !", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 19758c33d..02ea73af8 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -31,6 +31,8 @@ import grp import smtplib import requests +import dns.resolver + from OpenSSL import crypto from datetime import datetime from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate @@ -656,29 +658,14 @@ def _check_domain_is_correctly_configured(domain): def _dns_ip_match_public_ip(public_ip, domain): try: - result = requests.get("https://dns-api.org/A/" + domain) - except Exception as exception: - import traceback - traceback.print_exc(file=sys.stdout) - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_contacting_dns_api', api="dns-api.org", reason=exception)) + resolver = dns.resolver.Resolver() + # These are FDN's DNS + resolver.nameservers = [ "80.67.169.12", "80.67.169.40" ] + answers = resolver.query(domain, "A") + except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN) : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_no_A_record', domain=domain)) - try: - dns_ip = result.json() - except Exception as exception: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=result.text)) - - if len(dns_ip) == 0: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=result.text)) - - dns_ip = dns_ip[0] - - if dns_ip.get("error") == "NXDOMAIN": - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_A_dns_record', domain=domain)) - - if "value" not in dns_ip: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=result.text)) - - dns_ip = dns_ip["value"] + dns_ip = answers[0] return dns_ip == public_ip From e2e1fce44e3f2ec79f2fbd31bde71e10b6230900 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Nov 2016 21:09:06 -0500 Subject: [PATCH 068/208] Fixing previous commit --- 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 02ea73af8..e648e5b13 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -665,7 +665,7 @@ def _dns_ip_match_public_ip(public_ip, domain): except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN) : raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_no_A_record', domain=domain)) - dns_ip = answers[0] + dns_ip = str(answers[0]) return dns_ip == public_ip From a57ebfc4e65897c0405fe21c434e5f4086c5d004 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Nov 2016 21:11:36 -0500 Subject: [PATCH 069/208] Use 3072 bits keys --- 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 e648e5b13..3e999c2d1 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -55,7 +55,7 @@ WEBROOT_FOLDER = "/tmp/acme-challenge-public/" SELF_CA_FILE = "/etc/ssl/certs/ca-yunohost_crt.pem" ACCOUNT_KEY_FILE = "/etc/yunohost/letsencrypt_account.pem" -KEY_SIZE = 2048 +KEY_SIZE = 3072 VALIDITY_LIMIT = 15 # days From 109cbf764147dba0c6dd1041d776cad8c74f20a9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Nov 2016 22:22:13 -0500 Subject: [PATCH 070/208] Backuping existing certificate (if any) also for self-signed generation --- locales/en.json | 1 + src/yunohost/certificate.py | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 627c11b52..2dec12706 100644 --- a/locales/en.json +++ b/locales/en.json @@ -248,6 +248,7 @@ "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_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_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_selfsigned" : "Successfully installed a self-signed certificate for domain {domain:s} !", "certmanager_cert_install_success" : "Successfully installed Let's Encrypt certificate for domain {domain:s} !", "certmanager_cert_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain:s} !", "certmanager_old_letsencrypt_app_detected" : "Command aborted because the letsencrypt app is conflicting with the yunohost certificate management features." diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 3e999c2d1..430f6d4d2 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -143,10 +143,18 @@ def certificate_install_selfsigned(domain_list, force=False): if status and status["summary"]["code"] in ('good', 'great') and not force: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) + cert_folder_domain = os.path.join(CERT_FOLDER, domain) - if not os.path.exists(cert_folder_domain): - os.makedirs(cert_folder_domain) + # Backup existing certificate / folder + if os.path.exists(cert_folder_domain) : + if not os.path.islink(cert_folder_domain): + _backup_current_cert(domain) + shutil.rmtree(cert_folder_domain) + else : + os.remove(cert_folder_domain) + + os.makedirs(cert_folder_domain) # Get serial ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' @@ -184,6 +192,16 @@ def certificate_install_selfsigned(domain_list, force=False): _set_permissions(os.path.join(cert_folder_domain, "crt.pem"), "root", "metronome", 0640) _set_permissions(os.path.join(cert_folder_domain, "openssl.cnf"), "root", "root", 0600) + # Check new status indicate a recently created self-signed certificate, + status = _get_status(domain) + + if status and status["CA_type"]["code"] == "self-signed" and status["validity"] > 3648: + logger.success(m18n.n("certmanager_cert_install_success_selfsigned", domain=domain)) + else : + logger.error("Installation of self-signed certificate installation for %s failed !", domain) + logger.error(str(e)) + + def certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False): if not os.path.exists(ACCOUNT_KEY_FILE): @@ -474,7 +492,7 @@ def _fetch_and_enable_new_certificate(domain): live_link = os.path.join(CERT_FOLDER, domain) if not os.path.islink(live_link): - shutil.rmtree(live_link) # Well, yep, hopefully that's not too dangerous (directory should have been backuped before calling this command) + shutil.rmtree(live_link) # Hopefully that's not too dangerous (directory should have been backuped before calling this command) elif os.path.lexists(live_link): os.remove(live_link) From e1539297a5d7c23024402e68ff560f57f98dd28c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Nov 2016 22:25:15 -0500 Subject: [PATCH 071/208] Ignore messy stderr from openssl commands during self-signed cert generation --- src/yunohost/certificate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 430f6d4d2..56f6773bd 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -167,9 +167,9 @@ def certificate_install_selfsigned(domain_list, force=False): # We should be able to do all this using OpenSSL.crypto and os/shutil command_list = [ 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, cert_folder_domain), - 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch' + 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch 2>/dev/null' % (cert_folder_domain, ssl_dir, ssl_dir), - 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch' + 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch 2>/dev/null' % (cert_folder_domain, ssl_dir, ssl_dir), ] From 80ebaa6895dd9025fa8b6d6bf19179e93b747456 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Nov 2016 22:34:19 -0500 Subject: [PATCH 072/208] Have a subdirectory for cert backups, to not flood /etc/yunohost/certs/ --- 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 56f6773bd..0788f9e14 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -657,7 +657,7 @@ def _backup_current_cert(domain): cert_folder_domain = os.path.join(CERT_FOLDER, domain) date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") - backup_folder = "%s-backup-%s" % (cert_folder_domain, date_tag) + backup_folder = "%s-backups/%s" % (cert_folder_domain, date_tag) shutil.copytree(cert_folder_domain, backup_folder) From 4e9a2c050de2b89c9d10016c09cc5c4975cc1bfd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Nov 2016 23:29:10 -0500 Subject: [PATCH 073/208] Cleaning / reorganizing the way certificates are stored and enabled --- src/yunohost/certificate.py | 91 +++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 0788f9e14..00ab3dbb8 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -144,53 +144,48 @@ def certificate_install_selfsigned(domain_list, force=False): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) - cert_folder_domain = os.path.join(CERT_FOLDER, domain) + date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") + new_cert_folder = "%s/%s-history/%s-selfsigned" % (CERT_FOLDER, domain, date_tag) - # Backup existing certificate / folder - if os.path.exists(cert_folder_domain) : - if not os.path.islink(cert_folder_domain): - _backup_current_cert(domain) - shutil.rmtree(cert_folder_domain) - else : - os.remove(cert_folder_domain) - - os.makedirs(cert_folder_domain) + os.makedirs(new_cert_folder) # Get serial ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' with open(os.path.join(ssl_dir, 'serial'), 'r') as f: serial = f.readline().rstrip() - shutil.copyfile(os.path.join(ssl_dir, "openssl.cnf"), os.path.join(cert_folder_domain, "openssl.cnf")) + shutil.copyfile(os.path.join(ssl_dir, "openssl.cnf"), os.path.join(new_cert_folder, "openssl.cnf")) # FIXME : should refactor this to avoid so many os.system() calls... # We should be able to do all this using OpenSSL.crypto and os/shutil command_list = [ - 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, cert_folder_domain), + 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, new_cert_folder), 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch 2>/dev/null' - % (cert_folder_domain, ssl_dir, ssl_dir), + % (new_cert_folder, ssl_dir, ssl_dir), 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch 2>/dev/null' - % (cert_folder_domain, ssl_dir, ssl_dir), + % (new_cert_folder, ssl_dir, ssl_dir), ] for command in command_list: if os.system(command) != 0: raise MoulinetteError(errno.EIO, m18n.n('certmanager_domain_cert_gen_failed')) - os.symlink('/etc/ssl/certs/ca-yunohost_crt.pem', os.path.join(cert_folder_domain, "ca.pem")) - shutil.copyfile(os.path.join(ssl_dir, "certs", "yunohost_key.pem"), os.path.join(cert_folder_domain, "key.pem")) - shutil.copyfile(os.path.join(ssl_dir, "newcerts", "%s.pem" % serial), os.path.join(cert_folder_domain, "crt.pem")) + os.symlink('/etc/ssl/certs/ca-yunohost_crt.pem', os.path.join(new_cert_folder, "ca.pem")) + shutil.copyfile(os.path.join(ssl_dir, "certs", "yunohost_key.pem"), os.path.join(new_cert_folder, "key.pem")) + shutil.copyfile(os.path.join(ssl_dir, "newcerts", "%s.pem" % serial), os.path.join(new_cert_folder, "crt.pem")) # append ca.pem at the end of crt.pem - with open(os.path.join(cert_folder_domain, "ca.pem"), "r") as ca_pem: - with open(os.path.join(cert_folder_domain, "crt.pem"), "a") as crt_pem: + with open(os.path.join(new_cert_folder, "ca.pem"), "r") as ca_pem: + with open(os.path.join(new_cert_folder, "crt.pem"), "a") as crt_pem: crt_pem.write("\n") crt_pem.write(ca_pem.read()) - _set_permissions(cert_folder_domain, "root", "root", 0755) - _set_permissions(os.path.join(cert_folder_domain, "key.pem"), "root", "metronome", 0640) - _set_permissions(os.path.join(cert_folder_domain, "crt.pem"), "root", "metronome", 0640) - _set_permissions(os.path.join(cert_folder_domain, "openssl.cnf"), "root", "root", 0600) + _set_permissions(new_cert_folder, "root", "root", 0755) + _set_permissions(os.path.join(new_cert_folder, "key.pem"), "root", "metronome", 0640) + _set_permissions(os.path.join(new_cert_folder, "crt.pem"), "root", "metronome", 0640) + _set_permissions(os.path.join(new_cert_folder, "openssl.cnf"), "root", "root", 0600) + + _enable_certificate(domain, new_cert_folder) # Check new status indicate a recently created self-signed certificate, status = _get_status(domain) @@ -239,7 +234,6 @@ def certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=Fa if not no_checks: _check_domain_is_correctly_configured(domain) - _backup_current_cert(domain) _configure_for_acme_challenge(auth, domain) _fetch_and_enable_new_certificate(domain) _install_cron() @@ -308,7 +302,6 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal try: if not no_checks: _check_domain_is_correctly_configured(domain) - _backup_current_cert(domain) _fetch_and_enable_new_certificate(domain) logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) @@ -469,7 +462,7 @@ def _fetch_and_enable_new_certificate(domain): # Create corresponding directory date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") - new_cert_folder = "%s/%s.%s" % (CERT_FOLDER, domain, date_tag) + new_cert_folder = "%s/%s-history/%s-letsencrypt" % (CERT_FOLDER, domain, date_tag) os.makedirs(new_cert_folder) _set_permissions(new_cert_folder, "root", "root", 0655) @@ -486,18 +479,7 @@ def _fetch_and_enable_new_certificate(domain): _set_permissions(domain_cert_file, "root", "metronome", 0640) - logger.info("Enabling the new certificate...") - - # Replace (if necessary) the link or folder for live cert - live_link = os.path.join(CERT_FOLDER, domain) - - if not os.path.islink(live_link): - shutil.rmtree(live_link) # Hopefully that's not too dangerous (directory should have been backuped before calling this command) - - elif os.path.lexists(live_link): - os.remove(live_link) - - os.symlink(new_cert_folder, live_link) + _enable_certificate(domain, new_cert_folder) # Check the status of the certificate is now good status_summary = _get_status(domain)["summary"] @@ -505,13 +487,6 @@ def _fetch_and_enable_new_certificate(domain): if status_summary["code"] != "great": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_certificate_fetching_or_enabling_failed', domain=domain)) - logger.info("Restarting services...") - - for service in ("postfix", "dovecot", "metronome"): - _run_service_command("restart", service) - - _run_service_command("reload", "nginx") - def _prepare_certificate_signing_request(domain, key_file, output_folder): # Init a request @@ -651,6 +626,32 @@ def _set_permissions(path, user, group, permissions): os.chmod(path, permissions) +def _enable_certificate(domain, new_cert_folder) : + logger.info("Enabling the certificate for domain %s ...", domain) + + live_link = os.path.join(CERT_FOLDER, domain) + + # If a live link (or folder) already exists + if os.path.exists(live_link) : + # If it's not a link ... expect if to be a folder + if not os.path.islink(live_link): + # Backup it and remove it + _backup_current_cert(domain) + shutil.rmtree(live_link) + # Else if it's a link, simply delete it + elif os.path.lexists(live_link): + os.remove(live_link) + + os.symlink(new_cert_folder, live_link) + + logger.info("Restarting services...") + + for service in ("postfix", "dovecot", "metronome"): + _run_service_command("restart", service) + + _run_service_command("reload", "nginx") + + def _backup_current_cert(domain): logger.info("Backuping existing certificate for domain %s", domain) From 937cccf8131aa48158722dcb58dd0182174b342c Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 9 Nov 2016 08:38:52 +0100 Subject: [PATCH 074/208] [mod] remove useless import --- 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 00ab3dbb8..02ccbd380 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -42,7 +42,7 @@ from moulinette.utils.log import getActionLogger import yunohost.domain -from yunohost.app import app_ssowatconf, app_list +from yunohost.app import app_ssowatconf from yunohost.service import _run_service_command From 9e5b2743db03e94c8c6cb45d6693823425e0127e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 9 Nov 2016 08:38:58 +0100 Subject: [PATCH 075/208] [mod] pep8 --- src/yunohost/certificate.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 02ccbd380..81a459482 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -108,11 +108,11 @@ def certificate_status(auth, domain_list, full=False): del status["domain"] certificates[domain] = status - return { "certificates" : certificates } + return {"certificates" : certificates} def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False): - + """ Install a Let's Encrypt certificate for given domains (all by default) @@ -126,7 +126,7 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si # Check if old letsencrypt_ynh is installed _check_old_letsencrypt_app() - + if self_signed: certificate_install_selfsigned(domain_list, force) @@ -325,9 +325,9 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal ############################################################################### def _check_old_letsencrypt_app(): + installedAppIds = [app["id"] for app in yunohost.app.app_list(installed=True)["apps"]] - installedAppIds = [ app["id"] for app in yunohost.app.app_list(installed=True)["apps"] ] - if ("letsencrypt" not in installedAppIds) : + if "letsencrypt" not in installedAppIds: return logger.warning(" ") @@ -344,9 +344,10 @@ def _check_old_letsencrypt_app(): logger.warning("all domains with a Let's Encrypt certificate or self-signed") logger.warning("certificate.") logger.warning(" ") - + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_old_letsencrypt_app_detected')) + def _install_cron(): cron_job_file = "/etc/cron.weekly/yunohost-certificate-renew" @@ -679,9 +680,9 @@ def _dns_ip_match_public_ip(public_ip, domain): try: resolver = dns.resolver.Resolver() # These are FDN's DNS - resolver.nameservers = [ "80.67.169.12", "80.67.169.40" ] + resolver.nameservers = ["80.67.169.12", "80.67.169.40"] answers = resolver.query(domain, "A") - except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN) : + except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_no_A_record', domain=domain)) dns_ip = str(answers[0]) @@ -693,7 +694,7 @@ def _domain_is_accessible_through_HTTP(ip, domain): try: r = requests.head("http://" + ip, headers={"Host": domain}) # Check we got the ssowat header in the response - if ("x-sso-wat" not in r.headers.keys()) : + if "x-sso-wat" not in r.headers.keys(): return False except Exception: return False From a85c79ef320190d7f80136002798eb9adbfa8192 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Nov 2016 19:41:23 -0500 Subject: [PATCH 076/208] Refactored the self-signed cert generation, some steps were overly complicated for no reason --- src/yunohost/certificate.py | 75 +++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 81a459482..d03d7d55b 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -30,6 +30,7 @@ import pwd import grp import smtplib import requests +import subprocess import dns.resolver @@ -143,58 +144,74 @@ def certificate_install_selfsigned(domain_list, force=False): if status and status["summary"]["code"] in ('good', 'great') and not force: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) - + # Paths of files and folder we'll need date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") new_cert_folder = "%s/%s-history/%s-selfsigned" % (CERT_FOLDER, domain, date_tag) - os.makedirs(new_cert_folder) - - # Get serial + original_ca_file = '/etc/ssl/certs/ca-yunohost_crt.pem' ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' - with open(os.path.join(ssl_dir, 'serial'), 'r') as f: - serial = f.readline().rstrip() + conf_template = os.path.join(ssl_dir, "openssl.cnf") - shutil.copyfile(os.path.join(ssl_dir, "openssl.cnf"), os.path.join(new_cert_folder, "openssl.cnf")) + csr_file = os.path.join(ssl_dir, "certs", "yunohost_csr.pem") + conf_file = os.path.join(new_cert_folder, "openssl.cnf") + key_file = os.path.join(new_cert_folder, "key.pem") + crt_file = os.path.join(new_cert_folder, "crt.pem") + ca_file = os.path.join(new_cert_folder, "ca.pem") + + # Create output folder for new certificate stuff + os.makedirs(new_cert_folder) + + # Create our conf file, based on template, replacing the occurences of + # "yunohost.org" with the given domain + with open(conf_file, "w") as f : + with open(conf_template, "r") as template : + for line in template : + f.write(line.replace("yunohost.org", domain)) - # FIXME : should refactor this to avoid so many os.system() calls... - # We should be able to do all this using OpenSSL.crypto and os/shutil - command_list = [ - 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, new_cert_folder), - 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch 2>/dev/null' - % (new_cert_folder, ssl_dir, ssl_dir), - 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch 2>/dev/null' - % (new_cert_folder, ssl_dir, ssl_dir), - ] + # Use OpenSSL command line to create a certificate signing request, + # and self-sign the cert + commands = [] + commands.append("openssl req -new -config %s -days 3650 -out %s -keyout %s -nodes -batch" + % (conf_file, csr_file, key_file)) + commands.append("openssl ca -config %s -days 3650 -in %s -out %s -batch" + % (conf_file, csr_file, crt_file)) - for command in command_list: - if os.system(command) != 0: + for command in commands : + p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, _ = p.communicate() + if p.returncode != 0: + logger.warning(out) raise MoulinetteError(errno.EIO, m18n.n('certmanager_domain_cert_gen_failed')) + else : + logger.info(out) - os.symlink('/etc/ssl/certs/ca-yunohost_crt.pem', os.path.join(new_cert_folder, "ca.pem")) - shutil.copyfile(os.path.join(ssl_dir, "certs", "yunohost_key.pem"), os.path.join(new_cert_folder, "key.pem")) - shutil.copyfile(os.path.join(ssl_dir, "newcerts", "%s.pem" % serial), os.path.join(new_cert_folder, "crt.pem")) + # Link the CA cert (not sure it's actually needed in practice though, + # since we append it at the end of crt.pem. For instance for Let's + # Encrypt certs, we only need the crt.pem and key.pem) + os.symlink(original_ca_file, ca_file) - # append ca.pem at the end of crt.pem - with open(os.path.join(new_cert_folder, "ca.pem"), "r") as ca_pem: - with open(os.path.join(new_cert_folder, "crt.pem"), "a") as crt_pem: + # Append ca.pem at the end of crt.pem + with open(ca_file, "r") as ca_pem: + with open(crt_file, "a") as crt_pem: crt_pem.write("\n") crt_pem.write(ca_pem.read()) + # Set appropriate permissions _set_permissions(new_cert_folder, "root", "root", 0755) - _set_permissions(os.path.join(new_cert_folder, "key.pem"), "root", "metronome", 0640) - _set_permissions(os.path.join(new_cert_folder, "crt.pem"), "root", "metronome", 0640) - _set_permissions(os.path.join(new_cert_folder, "openssl.cnf"), "root", "root", 0600) + _set_permissions(key_file, "root", "metronome", 0640) + _set_permissions(crt_file, "root", "metronome", 0640) + _set_permissions(conf_file, "root", "root", 0600) + # Actually enable the certificate we created _enable_certificate(domain, new_cert_folder) - # Check new status indicate a recently created self-signed certificate, + # Check new status indicate a recently created self-signed certificate status = _get_status(domain) if status and status["CA_type"]["code"] == "self-signed" and status["validity"] > 3648: logger.success(m18n.n("certmanager_cert_install_success_selfsigned", domain=domain)) else : logger.error("Installation of self-signed certificate installation for %s failed !", domain) - logger.error(str(e)) From 11c626881a7ac0dcdf27d587899ba0662221d1a2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Nov 2016 20:19:56 -0500 Subject: [PATCH 077/208] Adding other DNS resolvers from FFDN --- src/yunohost/certificate.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index d03d7d55b..8feb9a771 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -696,8 +696,16 @@ def _check_domain_is_correctly_configured(domain): def _dns_ip_match_public_ip(public_ip, domain): try: resolver = dns.resolver.Resolver() - # These are FDN's DNS - resolver.nameservers = ["80.67.169.12", "80.67.169.40"] + resolver.nameservers = [] + # FFDN DNS resolvers + # See https://www.ffdn.org/wiki/doku.php?id=formations:dns + resolver.nameservers.append("80.67.169.12") # FDN + resolver.nameservers.append("80.67.169.40") # + resolver.nameservers.append("89.234.141.66") # ARN + resolver.nameservers.append("141.255.128.100") # Aquilenet + resolver.nameservers.append("141.255.128.101") # + resolver.nameservers.append("89.234.186.18") # Grifon + resolver.nameservers.append("80.67.188.188") # LDN answers = resolver.query(domain, "A") except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_no_A_record', domain=domain)) From da7728fe7458ca04d8df9095ab903aa68efdfe68 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 17 Nov 2016 12:54:39 +0100 Subject: [PATCH 078/208] [fix] Can't restore app on a root domain --- src/yunohost/app.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index caa38e95b..dc71642c4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -909,10 +909,6 @@ def app_checkurl(auth, url, app=None): raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) if domain in apps_map: - # Domain already has apps on sub path - if path == '/': - raise MoulinetteError(errno.EPERM, - m18n.n('app_location_install_failed')) # Loop through apps for p, a in apps_map[domain].items(): # Skip requested app checking @@ -922,7 +918,7 @@ def app_checkurl(auth, url, app=None): if path == p: raise MoulinetteError(errno.EINVAL, m18n.n('app_location_already_used')) - elif path.startswith(p): + elif path.startswith(p) or p.startswith(path): raise MoulinetteError(errno.EPERM, m18n.n('app_location_install_failed')) From bba92e4d4104a0ca5e81ad0c0582e5eac98bd6df Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 20 Nov 2016 20:36:58 -0500 Subject: [PATCH 079/208] Small tweaks for the web interface --- data/actionsmap/yunohost.yml | 8 ++++---- src/yunohost/certificate.py | 22 +++++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 8f08a98b8..3729e3aa3 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -308,7 +308,7 @@ domain: ### certificate_status() cert-status: action_help: List status of current certificates (all by default). - api: GET /certs/status/ + api: GET /domains/cert-status/ configuration: authenticate: all authenticator: ldap-anonymous @@ -323,7 +323,7 @@ domain: ### certificate_install() cert-install: action_help: Install Let's Encrypt certificates for given domains (all by default). - api: POST /certs/enable/ + api: POST /domains/cert-install/ configuration: authenticate: all authenticator: ldap-anonymous @@ -344,7 +344,7 @@ domain: ### certificate_renew() cert-renew: action_help: Renew the Let's Encrypt certificates for given domains (all by default). - api: POST /certs/renew/ + api: POST /domains/cert-renew/ configuration: authenticate: all authenticator: ldap-anonymous @@ -361,7 +361,7 @@ domain: --no-checks: help: Does not perform any check that your domain seems correcly configured (DNS, reachability) before attempting to renew. (Not recommended) action: store_true - + ### domain_info() # info: # action_help: Get domain informations diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 8feb9a771..8b3db0283 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -103,6 +103,7 @@ def certificate_status(auth, domain_list, full=False): if not full: del status["subject"] del status["CA_name"] + del status["ACME_eligible"] status["CA_type"] = status["CA_type"]["verbose"] status["summary"] = status["summary"]["verbose"] @@ -157,10 +158,10 @@ def certificate_install_selfsigned(domain_list, force=False): key_file = os.path.join(new_cert_folder, "key.pem") crt_file = os.path.join(new_cert_folder, "crt.pem") ca_file = os.path.join(new_cert_folder, "ca.pem") - + # Create output folder for new certificate stuff os.makedirs(new_cert_folder) - + # Create our conf file, based on template, replacing the occurences of # "yunohost.org" with the given domain with open(conf_file, "w") as f : @@ -168,10 +169,10 @@ def certificate_install_selfsigned(domain_list, force=False): for line in template : f.write(line.replace("yunohost.org", domain)) - # Use OpenSSL command line to create a certificate signing request, + # Use OpenSSL command line to create a certificate signing request, # and self-sign the cert commands = [] - commands.append("openssl req -new -config %s -days 3650 -out %s -keyout %s -nodes -batch" + commands.append("openssl req -new -config %s -days 3650 -out %s -keyout %s -nodes -batch" % (conf_file, csr_file, key_file)) commands.append("openssl ca -config %s -days 3650 -in %s -out %s -batch" % (conf_file, csr_file, crt_file)) @@ -249,7 +250,7 @@ def certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=Fa try: if not no_checks: - _check_domain_is_correctly_configured(domain) + _check_domain_is_ready_for_ACME(domain) _configure_for_acme_challenge(auth, domain) _fetch_and_enable_new_certificate(domain) @@ -318,7 +319,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal try: if not no_checks: - _check_domain_is_correctly_configured(domain) + _check_domain_is_ready_for_ACME(domain) _fetch_and_enable_new_certificate(domain) logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) @@ -608,6 +609,12 @@ def _get_status(domain): "verbose": "Unknown?", } + try : + _check_domain_is_ready_for_ACME(domain) + ACME_eligible = True + except : + ACME_eligible = False + return { "domain": domain, "subject": cert_subject, @@ -615,6 +622,7 @@ def _get_status(domain): "CA_type": CA_type, "validity": days_remaining, "summary": status_summary, + "ACME_eligible": ACME_eligible } ############################################################################### @@ -681,7 +689,7 @@ def _backup_current_cert(domain): shutil.copytree(cert_folder_domain, backup_folder) -def _check_domain_is_correctly_configured(domain): +def _check_domain_is_ready_for_ACME(domain): public_ip = yunohost.domain.get_public_ip() # Check if IP from DNS matches public IP From 5de006f18df96398658809fa6ad5b565f8f83bef Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 21 Nov 2016 10:58:57 -0500 Subject: [PATCH 080/208] Follow up of @julienmalik comments - misc typo/cosmetic fixes --- data/actionsmap/yunohost.yml | 6 +++--- locales/de.json | 2 +- locales/en.json | 8 ++++---- locales/es.json | 2 +- locales/fr.json | 2 +- locales/nl.json | 2 +- locales/pt.json | 2 +- src/yunohost/certificate.py | 26 ++++++++++++++++---------- src/yunohost/domain.py | 2 +- 9 files changed, 29 insertions(+), 23 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 3729e3aa3..18f470a61 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -335,7 +335,7 @@ domain: help: Install even if current certificate is not self-signed action: store_true --no-checks: - help: Does not perform any check that your domain seems correcly configured (DNS, reachability) before attempting to install. (Not recommended) + help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to install. (Not recommended) action: store_true --self-signed: help: Install self-signed certificate instead of Let's Encrypt @@ -353,13 +353,13 @@ domain: help: Domains for which to renew the certificates nargs: "*" --force: - help: Ignore the validity treshold (30 days) + help: Ignore the validity threshold (30 days) action: store_true --email: help: Send an email to root with logs if some renewing fails action: store_true --no-checks: - help: Does not perform any check that your domain seems correcly configured (DNS, reachability) before attempting to renew. (Not recommended) + help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to renew. (Not recommended) action: store_true ### domain_info() diff --git a/locales/de.json b/locales/de.json index e57315caa..1331c56b4 100644 --- a/locales/de.json +++ b/locales/de.json @@ -57,7 +57,7 @@ "custom_app_url_required": "Es muss eine URL angegeben um deine benutzerdefinierte App {app:s} zu aktualisieren", "custom_appslist_name_required": "Du musst einen Namen für deine benutzerdefinierte Appliste angeben", "dnsmasq_isnt_installed": "dnsmasq scheint nicht installiert zu sein. Bitte führe 'apt-get remove bind9 && apt-get install dnsmasq' aus", - "certmanager_domain_cert_gen_failed": "Zertifikat konnte nicht erzeugt werden", + "domain_cert_gen_failed": "Zertifikat konnte nicht erzeugt werden", "domain_created": "Domain erfolgreich erzeugt", "domain_creation_failed": "Konnte Domain nicht erzeugen", "domain_deleted": "Domain erfolgreich gelöscht", diff --git a/locales/en.json b/locales/en.json index 2dec12706..b3723810e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -237,13 +237,13 @@ "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'.", - "certmanager_domain_cert_gen_failed": "Unable to generate certificate", + "domain_cert_gen_failed": "Unable to generate certificate", "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_domain_unknown": "Unknown domain {domain:s}", - "certmanager_domain_cert_not_selfsigned" : "The certificate of domain {domain:s} is not self-signed. Are you sure you want to replace it ? (Use --force)", + "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_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow...", - "certmanager_attempt_to_renew_nonLE_cert" : "The certificate of domain {domain:s} is not issued by Let's Encrypt. Cannot renew it automatically !", - "certmanager_attempt_to_renew_valid_cert" : "The certificate of domain {domain:s} is not about to expire ! Use --force to bypass", + "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_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_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_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.)", diff --git a/locales/es.json b/locales/es.json index fdd04d10f..549cbe29a 100644 --- a/locales/es.json +++ b/locales/es.json @@ -72,7 +72,7 @@ "diagnostic_monitor_system_error": "No se puede monitorizar el sistema: {error}", "diagnostic_no_apps": "Aplicación no instalada", "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecuta 'apt-get remove bind9 && apt-get install dnsmasq'", - "certmanager_domain_cert_gen_failed": "No se pudo crear el certificado", + "domain_cert_gen_failed": "No se pudo crear el certificado", "domain_created": "El dominio ha sido creado", "domain_creation_failed": "No se pudo crear el dominio", "domain_deleted": "El dominio ha sido eliminado", diff --git a/locales/fr.json b/locales/fr.json index 6691b2f28..7898de57f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -73,7 +73,7 @@ "diagnostic_monitor_system_error": "Impossible de superviser le système : {error}", "diagnostic_no_apps": "Aucune application installée", "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer « apt-get remove bind9 && apt-get install dnsmasq »", - "certmanager_domain_cert_gen_failed": "Impossible de générer le certificat", + "domain_cert_gen_failed": "Impossible de générer le certificat", "domain_created": "Le domaine a été créé", "domain_creation_failed": "Impossible de créer le domaine", "domain_deleted": "Le domaine a été supprimé", diff --git a/locales/nl.json b/locales/nl.json index 57b05e309..c2bfed31e 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -37,7 +37,7 @@ "custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {app:s} bij te werken", "custom_appslist_name_required": "U moet een naam opgeven voor uw aangepaste app-lijst", "dnsmasq_isnt_installed": "dnsmasq lijkt niet geïnstalleerd te zijn, voer alstublieft het volgende commando uit: 'apt-get remove bind9 && apt-get install dnsmasq'", - "certmanager_domain_cert_gen_failed": "Kan certificaat niet genereren", + "domain_cert_gen_failed": "Kan certificaat niet genereren", "domain_created": "Domein succesvol aangemaakt", "domain_creation_failed": "Kan domein niet aanmaken", "domain_deleted": "Domein succesvol verwijderd", diff --git a/locales/pt.json b/locales/pt.json index b9c9e4bce..d3796d2e9 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -36,7 +36,7 @@ "backup_output_directory_not_empty": "A pasta de destino não se encontra vazia", "custom_app_url_required": "Deve proporcionar uma URL para atualizar a sua aplicação personalizada {app:s}", "custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada", - "certmanager_domain_cert_gen_failed": "Não foi possível gerar o certificado", + "domain_cert_gen_failed": "Não foi possível gerar o certificado", "domain_created": "Domínio criado com êxito", "domain_creation_failed": "Não foi possível criar o domínio", "domain_deleted": "Domínio removido com êxito", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 8b3db0283..961cf18fb 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -82,6 +82,8 @@ def certificate_status(auth, domain_list, full=False): """ # Check if old letsencrypt_ynh is installed + # TODO / FIXME - Remove this in the future once the letsencrypt app is + # not used anymore _check_old_letsencrypt_app() # If no domains given, consider all yunohost domains @@ -127,16 +129,18 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si """ # Check if old letsencrypt_ynh is installed + # TODO / FIXME - Remove this in the future once the letsencrypt app is + # not used anymore _check_old_letsencrypt_app() if self_signed: - certificate_install_selfsigned(domain_list, force) + _certificate_install_selfsigned(domain_list, force) else: - certificate_install_letsencrypt(auth, domain_list, force, no_checks) + _certificate_install_letsencrypt(auth, domain_list, force, no_checks) -def certificate_install_selfsigned(domain_list, force=False): +def _certificate_install_selfsigned(domain_list, force=False): for domain in domain_list: # Check we ain't trying to overwrite a good cert ! @@ -182,7 +186,7 @@ def certificate_install_selfsigned(domain_list, force=False): out, _ = p.communicate() if p.returncode != 0: logger.warning(out) - raise MoulinetteError(errno.EIO, m18n.n('certmanager_domain_cert_gen_failed')) + raise MoulinetteError(errno.EIO, m18n.n('domain_cert_gen_failed')) else : logger.info(out) @@ -216,7 +220,7 @@ def certificate_install_selfsigned(domain_list, force=False): -def certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False): +def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False): if not os.path.exists(ACCOUNT_KEY_FILE): _generate_account_key() @@ -276,6 +280,8 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal """ # Check if old letsencrypt_ynh is installed + # TODO / FIXME - Remove this in the future once the letsencrypt app is + # not used anymore _check_old_letsencrypt_app() # If no domains given, consider all yunohost domains with Let's Encrypt @@ -283,12 +289,12 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal if domain_list == []: for domain in yunohost.domain.domain_list(auth)['domains']: - # Does it has a Let's Encrypt cert ? + # Does it have a Let's Encrypt cert ? status = _get_status(domain) if status["CA_type"]["code"] != "lets-encrypt": continue - # Does it expires soon ? + # Does it expire soon ? if force or status["validity"] <= VALIDITY_LIMIT: domain_list.append(domain) @@ -305,11 +311,11 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal status = _get_status(domain) - # Does it expires soon ? + # Does it expire soon ? if not force or status["validity"] <= VALIDITY_LIMIT: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain)) - # Does it has a Let's Encrypt cert ? + # Does it have a Let's Encrypt cert ? if status["CA_type"]["code"] != "lets-encrypt": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain)) @@ -383,7 +389,7 @@ def _email_renewing_failed(domain, exception_message, stack): logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") text = """ -At attempt for renewing the certificate for domain %s failed with the following +An attempt for renewing the certificate for domain %s failed with the following error : %s diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index aadd9086d..7d34aff53 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -114,7 +114,7 @@ def domain_add(auth, domain, dyndns=False): m18n.n('domain_dyndns_root_unknown')) try: - yunohost.certificate.certificate_install_selfsigned([domain], False) + yunohost.certificate._certificate_install_selfsigned([domain], False) try: auth.validate_uniqueness({'virtualdomain': domain}) From c57e343d45bc0f1b0b8f40947c857e8e0a50b415 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 21 Nov 2016 11:24:55 -0500 Subject: [PATCH 081/208] Looking for ssowat header in https (workaround for when app is installed on root domain) --- src/yunohost/certificate.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 961cf18fb..cca582ed7 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -731,8 +731,11 @@ def _dns_ip_match_public_ip(public_ip, domain): def _domain_is_accessible_through_HTTP(ip, domain): try: - r = requests.head("http://" + ip, headers={"Host": domain}) - # Check we got the ssowat header in the response + # Check HTTP reachability + requests.head("http://" + ip, headers={"Host": domain}) + + # Check we got the ssowat header (in HTTPS) + r = requests.head("https://" + ip, headers={"Host": domain}, verify=False) if "x-sso-wat" not in r.headers.keys(): return False except Exception: From dc731c3af56736e97e4713456f5239aeba66ed40 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Nov 2016 20:13:39 -0500 Subject: [PATCH 082/208] Using a single generic skipped regex for acme challenge in ssowat conf --- src/yunohost/app.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 83fc8614c..a658a0c34 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1031,22 +1031,8 @@ def app_ssowatconf(auth): for domain in domains: skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) - # Authorize ACME challenge url if a domain seems configured for it... - for domain in domains: - - # Check ACME challenge file is present in nginx conf - nginx_acme_challenge_conf_file = "/etc/nginx/conf.d/%s.d/000-acmechallenge.conf" % domain - - if not os.path.isfile(nginx_acme_challenge_conf_file): - continue - - # Check the file contains the ACME challenge uri - if not '/.well-known/acme-challenge' in open(nginx_acme_challenge_conf_file).read(): - continue - - # If so, then authorize the ACME challenge uri to unprotected regex - unprotected_regex.append(domain + "/%.well%-known/acme%-challenge/.*$") - + # Authorize ACME challenge url + skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$") conf_dict = { 'portal_domain': main_domain, From fb29bb879b860de7c214ccacac9d164b647d5773 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Nov 2016 20:23:14 -0500 Subject: [PATCH 083/208] Removing check for ssowat header when testing HTTP reachability --- src/yunohost/certificate.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index cca582ed7..22346a460 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -731,13 +731,7 @@ def _dns_ip_match_public_ip(public_ip, domain): def _domain_is_accessible_through_HTTP(ip, domain): try: - # Check HTTP reachability requests.head("http://" + ip, headers={"Host": domain}) - - # Check we got the ssowat header (in HTTPS) - r = requests.head("https://" + ip, headers={"Host": domain}, verify=False) - if "x-sso-wat" not in r.headers.keys(): - return False except Exception: return False From be061522e6818a14d4d0d23280292b45a593cc93 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Nov 2016 20:32:33 -0500 Subject: [PATCH 084/208] Moving full letsencrypt app conflict warning to locales/en.json --- locales/en.json | 2 +- src/yunohost/certificate.py | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/locales/en.json b/locales/en.json index b3723810e..10dc41edb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -251,5 +251,5 @@ "certmanager_cert_install_success_selfsigned" : "Successfully installed a self-signed certificate for domain {domain:s} !", "certmanager_cert_install_success" : "Successfully installed Let's Encrypt certificate for domain {domain:s} !", "certmanager_cert_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain:s} !", - "certmanager_old_letsencrypt_app_detected" : "Command aborted because the letsencrypt app is conflicting with the yunohost certificate management features." + "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." } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 22346a460..7a3ded247 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -354,21 +354,6 @@ def _check_old_letsencrypt_app(): if "letsencrypt" not in installedAppIds: return - logger.warning(" ") - logger.warning("Yunohost detected that the 'letsencrypt' app is installed, ") - logger.warning("which conflits with the new certificate management features") - logger.warning("directly integrated in Yunohost. If you wish to use these ") - logger.warning("new features, please run the following commands to migrate ") - logger.warning("your installation :") - logger.warning(" ") - logger.warning(" yunohost app remove letsencrypt") - logger.warning(" yunohost domain cert-install") - logger.warning(" ") - logger.warning("N.B. : this will attempt to re-install certificates for ") - logger.warning("all domains with a Let's Encrypt certificate or self-signed") - logger.warning("certificate.") - logger.warning(" ") - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_old_letsencrypt_app_detected')) From 0132cf037f05955055b22b9d91909b9b8c3a8f87 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Nov 2016 20:38:33 -0500 Subject: [PATCH 085/208] Moving DNS resolvers IP to constant var at beginning of file --- src/yunohost/certificate.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 7a3ded247..ec733f657 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -67,6 +67,18 @@ CERTIFICATION_AUTHORITY = "https://acme-v01.api.letsencrypt.org" INTERMEDIATE_CERTIFICATE_URL = "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem" +DNS_RESOLVERS = [ + # FFDN DNS resolvers + # See https://www.ffdn.org/wiki/doku.php?id=formations:dns + "80.67.169.12", # FDN + "80.67.169.40", # + "89.234.141.66", # ARN + "141.255.128.100", # Aquilenet + "141.255.128.101", # + "89.234.186.18", # Grifon + "80.67.188.188" # LDN +] + ############################################################################### # Front-end stuff # ############################################################################### @@ -695,16 +707,7 @@ def _check_domain_is_ready_for_ACME(domain): def _dns_ip_match_public_ip(public_ip, domain): try: resolver = dns.resolver.Resolver() - resolver.nameservers = [] - # FFDN DNS resolvers - # See https://www.ffdn.org/wiki/doku.php?id=formations:dns - resolver.nameservers.append("80.67.169.12") # FDN - resolver.nameservers.append("80.67.169.40") # - resolver.nameservers.append("89.234.141.66") # ARN - resolver.nameservers.append("141.255.128.100") # Aquilenet - resolver.nameservers.append("141.255.128.101") # - resolver.nameservers.append("89.234.186.18") # Grifon - resolver.nameservers.append("80.67.188.188") # LDN + resolver.nameservers = DNS_RESOLVERS answers = resolver.query(domain, "A") except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_no_A_record', domain=domain)) From a6353703bd9a6bb3a12edb72ad052d97608b841a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Nov 2016 22:24:54 -0500 Subject: [PATCH 086/208] Catching exceptions from acme-tiny --- locales/en.json | 4 +++- src/yunohost/certificate.py | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/locales/en.json b/locales/en.json index 10dc41edb..85b25efe9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -251,5 +251,7 @@ "certmanager_cert_install_success_selfsigned" : "Successfully installed a self-signed certificate for domain {domain:s} !", "certmanager_cert_install_success" : "Successfully installed Let's Encrypt certificate for domain {domain:s} !", "certmanager_cert_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain: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_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_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_cert_signing_failed" : "Signing the new certificate failed." } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index ec733f657..99e6bd014 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -31,7 +31,6 @@ import grp import smtplib import requests import subprocess - import dns.resolver from OpenSSL import crypto @@ -470,11 +469,20 @@ def _fetch_and_enable_new_certificate(domain): domain_csr_file = "%s/%s.csr" % (TMP_FOLDER, domain) - signed_certificate = sign_certificate(ACCOUNT_KEY_FILE, - domain_csr_file, - WEBROOT_FOLDER, - log=logger, - CA=CERTIFICATION_AUTHORITY) + try: + signed_certificate = sign_certificate(ACCOUNT_KEY_FILE, + domain_csr_file, + WEBROOT_FOLDER, + log=logger, + CA=CERTIFICATION_AUTHORITY) + except ValueError as e: + if ("urn:acme:error:rateLimited" in str(e)) : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_hit_rate_limit', domain=domain)) + else : + raise + except Exception as e: + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cert_signing_failed')) + logger.error(str(e)) intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL).text @@ -612,10 +620,10 @@ def _get_status(domain): "verbose": "Unknown?", } - try : + try: _check_domain_is_ready_for_ACME(domain) ACME_eligible = True - except : + except: ACME_eligible = False return { From ed16cd7f5aed448cd76a3da99f82f75732ec2f18 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Nov 2016 23:44:23 -0500 Subject: [PATCH 087/208] Adding an option to use the staging Let's Encrypt CA, sort of a dry-run --- data/actionsmap/yunohost.yml | 7 ++++++ src/yunohost/certificate.py | 47 +++++++++++++++++++++++++----------- src/yunohost/domain.py | 8 +++--- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 18f470a61..c1126ece0 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -340,6 +340,9 @@ domain: --self-signed: help: Install self-signed certificate instead of Let's Encrypt action: store_true + --staging: + help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure. + action: store_true ### certificate_renew() cert-renew: @@ -361,6 +364,10 @@ domain: --no-checks: help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to renew. (Not recommended) action: store_true + --staging: + help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure. + action: store_true + ### domain_info() # info: diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 99e6bd014..3db94e8b8 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -60,9 +60,9 @@ KEY_SIZE = 3072 VALIDITY_LIMIT = 15 # days # For tests -#CERTIFICATION_AUTHORITY = "https://acme-staging.api.letsencrypt.org" +STAGING_CERTIFICATION_AUTHORITY = "https://acme-staging.api.letsencrypt.org" # For prod -CERTIFICATION_AUTHORITY = "https://acme-v01.api.letsencrypt.org" +PRODUCTION_CERTIFICATION_AUTHORITY = "https://acme-v01.api.letsencrypt.org" INTERMEDIATE_CERTIFICATE_URL = "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem" @@ -126,7 +126,7 @@ def certificate_status(auth, domain_list, full=False): return {"certificates" : certificates} -def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False): +def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False, staging=False): """ Install a Let's Encrypt certificate for given domains (all by default) @@ -148,7 +148,7 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si if self_signed: _certificate_install_selfsigned(domain_list, force) else: - _certificate_install_letsencrypt(auth, domain_list, force, no_checks) + _certificate_install_letsencrypt(auth, domain_list, force, no_checks, staging) def _certificate_install_selfsigned(domain_list, force=False): @@ -231,7 +231,7 @@ def _certificate_install_selfsigned(domain_list, force=False): -def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False): +def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False, staging=False): if not os.path.exists(ACCOUNT_KEY_FILE): _generate_account_key() @@ -258,6 +258,9 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F if not force and status["CA_type"]["code"] != "self-signed": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_cert_not_selfsigned', domain=domain)) + if (staging): + logger.warning("Please note that you used the --staging option, and that no new certificate will actually be enabled !") + # Actual install steps for domain in domain_list: @@ -268,7 +271,7 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F _check_domain_is_ready_for_ACME(domain) _configure_for_acme_challenge(auth, domain) - _fetch_and_enable_new_certificate(domain) + _fetch_and_enable_new_certificate(domain, staging) _install_cron() logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) @@ -278,7 +281,7 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F logger.error(str(e)) -def certificate_renew(auth, domain_list, force=False, no_checks=False, email=False): +def certificate_renew(auth, domain_list, force=False, no_checks=False, email=False, staging=False): """ Renew Let's Encrypt certificate for given domains (all by default) @@ -330,6 +333,9 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal if status["CA_type"]["code"] != "lets-encrypt": raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain)) + if (staging): + logger.warning("Please note that you used the --staging option, and that no new certificate will actually be enabled !") + # Actual renew steps for domain in domain_list: logger.info("Now attempting renewing of certificate for domain %s !", domain) @@ -337,7 +343,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal try: if not no_checks: _check_domain_is_ready_for_ACME(domain) - _fetch_and_enable_new_certificate(domain) + _fetch_and_enable_new_certificate(domain, staging) logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) @@ -442,7 +448,7 @@ location '/.well-known/acme-challenge' app_ssowatconf(auth) -def _fetch_and_enable_new_certificate(domain): +def _fetch_and_enable_new_certificate(domain, staging=False): # Make sure tmp folder exists logger.debug("Making sure tmp folders exists...") @@ -469,20 +475,25 @@ def _fetch_and_enable_new_certificate(domain): domain_csr_file = "%s/%s.csr" % (TMP_FOLDER, domain) + if (staging): + certification_authority = STAGING_CERTIFICATION_AUTHORITY + else: + certification_authority = PRODUCTION_CERTIFICATION_AUTHORITY + try: signed_certificate = sign_certificate(ACCOUNT_KEY_FILE, domain_csr_file, WEBROOT_FOLDER, log=logger, - CA=CERTIFICATION_AUTHORITY) + CA=certification_authority) except ValueError as e: - if ("urn:acme:error:rateLimited" in str(e)) : + if ("urn:acme:error:rateLimited" in str(e)): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_hit_rate_limit', domain=domain)) - else : + else: raise except Exception as e: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cert_signing_failed')) logger.error(str(e)) + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cert_signing_failed')) intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL).text @@ -492,7 +503,12 @@ def _fetch_and_enable_new_certificate(domain): # Create corresponding directory date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") - new_cert_folder = "%s/%s-history/%s-letsencrypt" % (CERT_FOLDER, domain, date_tag) + if (staging): + folder_flag = "staging" + else: + folder_flag = "letsencrypt" + + new_cert_folder = "%s/%s-history/%s-%s" % (CERT_FOLDER, domain, date_tag, folder_flag) os.makedirs(new_cert_folder) _set_permissions(new_cert_folder, "root", "root", 0655) @@ -509,6 +525,9 @@ def _fetch_and_enable_new_certificate(domain): _set_permissions(domain_cert_file, "root", "metronome", 0640) + if (staging): + return + _enable_certificate(domain, new_cert_folder) # Check the status of the certificate is now good diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 7d34aff53..415585087 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -261,12 +261,12 @@ def domain_cert_status(auth, domain_list, full=False): return yunohost.certificate.certificate_status(auth, domain_list, full) -def domain_cert_install(auth, domain_list, force=False, no_checks=False, self_signed=False): - return yunohost.certificate.certificate_install(auth, domain_list, force, no_checks, self_signed) +def domain_cert_install(auth, domain_list, force=False, no_checks=False, self_signed=False, staging=False): + return yunohost.certificate.certificate_install(auth, domain_list, force, no_checks, self_signed, staging) -def domain_cert_renew(auth, domain_list, force=False, no_checks=False, email=False): - return yunohost.certificate.certificate_renew(auth, domain_list, force, no_checks, email) +def domain_cert_renew(auth, domain_list, force=False, no_checks=False, email=False, staging=False): + return yunohost.certificate.certificate_renew(auth, domain_list, force, no_checks, email, staging) def get_public_ip(protocol=4): From e66a7085202a33b9f4688a28a994b02974a28ee3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 23 Nov 2016 11:46:52 -0500 Subject: [PATCH 088/208] Misc tweaks on exceptions --- locales/en.json | 3 ++- src/yunohost/certificate.py | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 85b25efe9..637671a2d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -253,5 +253,6 @@ "certmanager_cert_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain: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_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_cert_signing_failed" : "Signing the new certificate failed." + "certmanager_cert_signing_failed" : "Signing the new certificate failed.", + "certmanager_no_cert_file" : "Unable to read certificate file for domain {domain:s} (file : {file:s})" } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 3db94e8b8..d892e6f21 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -155,10 +155,11 @@ def _certificate_install_selfsigned(domain_list, force=False): for domain in domain_list: # Check we ain't trying to overwrite a good cert ! - status = _get_status(domain) + if (not force) : + status = _get_status(domain) - if status and status["summary"]["code"] in ('good', 'great') and not force: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) + if status["summary"]["code"] in ('good', 'great') : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) # Paths of files and folder we'll need date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") @@ -490,7 +491,8 @@ def _fetch_and_enable_new_certificate(domain, staging=False): if ("urn:acme:error:rateLimited" in str(e)): raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_hit_rate_limit', domain=domain)) else: - raise + logger.error(str(e)) + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cert_signing_failed')) except Exception as e: logger.error(str(e)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cert_signing_failed')) @@ -565,7 +567,7 @@ def _get_status(domain): cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem") if not os.path.isfile(cert_file): - return {} + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_cert_file', domain=domain, file=cert_file)) try: cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file).read()) From dfbfc0cfc7ab35e8a1188b647c7f8c3a3915e62a Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 24 Nov 2016 02:55:59 +0100 Subject: [PATCH 089/208] [fix] Can't get mailbos used space if dovecot is down --- locales/en.json | 1 + src/yunohost/user.py | 46 +++++++++++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/locales/en.json b/locales/en.json index e939b26fa..a87a459b7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -118,6 +118,7 @@ "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", "monitor_disabled": "The server monitoring has been disabled", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ec7dd539c..348a23d32 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -92,7 +92,7 @@ def user_list(auth, fields=None, filter=None, limit=None, offset=None): def user_create(auth, username, firstname, lastname, mail, password, - mailbox_quota=0): + mailbox_quota="0"): """ Create user @@ -398,20 +398,36 @@ def user_info(auth, username): result_dict['mail-forward'] = user['maildrop'][1:] if 'mailuserquota' in user: - if user['mailuserquota'][0] != '0': - cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] - userquota = subprocess.check_output(cmd,stderr=subprocess.STDOUT, - shell=True) - quotavalue = re.findall(r'\d+', userquota) - result = '%s (%s%s)' % ( _convertSize(eval(quotavalue[0])), - quotavalue[2], '%') - result_dict['mailbox-quota'] = { - 'limit' : user['mailuserquota'][0], - 'use' : result - } - else: - result_dict['mailbox-quota'] = m18n.n('unlimit') - + userquota = user['mailuserquota'][0] + if isinstance( userquota, int ): + userquota = str(userquota) + + # Test if userquota is '0' or '0M' ( quota pattern is ^(\d+[bkMGT])|0$ ) + is_limited = not re.match('0[bkMGT]?', userquota) + storage_use = '?' + cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] + try: + cmd_result = subprocess.check_output(cmd,stderr=subprocess.STDOUT, + shell=True) + # Exemple of return value for cmd: + # """Quota name=User quota Type=STORAGE Value=0 Limit=- %=0 + # Quota name=User quota Type=MESSAGE Value=0 Limit=- %=0""" + has_value=re.search(r'Value=(\d+)', cmd_result) + if has_value: + storage_use = int(has_value.group(1)) + storage_use = _convertSize(storage_use) + if is_limited: + has_percent=re.search(r'%=(\d+)', cmd_result) + if has_percent: + percentage = int(has_percent.group(1)) + storage_use += ' (%s%s)' % (percentage, '%') + except subprocess.CalledProcessError: + logger.warning(m18n.n('mailbox_used_space_dovecot_down')) + result_dict['mailbox-quota'] = { + 'limit' : userquota if is_limited else m18n.n('unlimit'), + 'use' : storage_use + } + if result: return result_dict else: From 195c675c590b5b260883f5ad90e0958e5e9f3ec7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 23 Nov 2016 21:36:34 -0500 Subject: [PATCH 090/208] More exception handling, this time for previous acme challenge conf already existing in nginx --- locales/en.json | 3 ++- src/yunohost/certificate.py | 35 +++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/locales/en.json b/locales/en.json index 637671a2d..efeb66e69 100644 --- a/locales/en.json +++ b/locales/en.json @@ -254,5 +254,6 @@ "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_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_cert_signing_failed" : "Signing the new certificate failed.", - "certmanager_no_cert_file" : "Unable to read certificate file for domain {domain:s} (file : {file:s})" + "certmanager_no_cert_file" : "Unable to read certificate file for domain {domain:s} (file : {file:s})", + "certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge : the nginx configuration file {filepath:s} is conflicting and should be removed first." } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index d892e6f21..d9110b79b 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -32,6 +32,7 @@ import smtplib import requests import subprocess import dns.resolver +import glob from OpenSSL import crypto from datetime import datetime @@ -152,15 +153,9 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si def _certificate_install_selfsigned(domain_list, force=False): + for domain in domain_list: - # Check we ain't trying to overwrite a good cert ! - if (not force) : - status = _get_status(domain) - - if status["summary"]["code"] in ('good', 'great') : - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) - # Paths of files and folder we'll need date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") new_cert_folder = "%s/%s-history/%s-selfsigned" % (CERT_FOLDER, domain, date_tag) @@ -175,6 +170,14 @@ def _certificate_install_selfsigned(domain_list, force=False): crt_file = os.path.join(new_cert_folder, "crt.pem") ca_file = os.path.join(new_cert_folder, "ca.pem") + # Check we ain't trying to overwrite a good cert ! + current_cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem") + if (not force) and (os.path.isfile(current_cert_file)): + status = _get_status(domain) + + if status["summary"]["code"] in ('good', 'great') : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) + # Create output folder for new certificate stuff os.makedirs(new_cert_folder) @@ -261,7 +264,7 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F if (staging): logger.warning("Please note that you used the --staging option, and that no new certificate will actually be enabled !") - + # Actual install steps for domain in domain_list: @@ -336,7 +339,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal if (staging): logger.warning("Please note that you used the --staging option, and that no new certificate will actually be enabled !") - + # Actual renew steps for domain in domain_list: logger.info("Now attempting renewing of certificate for domain %s !", domain) @@ -421,7 +424,9 @@ Subject: %s def _configure_for_acme_challenge(auth, domain): - nginx_conf_file = "/etc/nginx/conf.d/%s.d/000-acmechallenge.conf" % domain + + nginx_conf_folder = "/etc/nginx/conf.d/%s.d" % domain + nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder nginx_configuration = ''' location '/.well-known/acme-challenge' @@ -431,6 +436,15 @@ location '/.well-known/acme-challenge' } ''' % WEBROOT_FOLDER + # Check there isn't a conflicting file for the acme-challenge well-known uri + for path in glob.glob('%s/*.conf' % nginx_conf_folder): + if (path == nginx_conf_file) : + continue + with open(path) as f: + contents = f.read() + if ('/.well-known/acme-challenge' in contents) : + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_conflicting_nginx_file', filepath=path)) + # Write the conf if os.path.exists(nginx_conf_file): logger.info("Nginx configuration file for ACME challenge already exists for domain, skipping.") @@ -564,6 +578,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): def _get_status(domain): + cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem") if not os.path.isfile(cert_file): From 1b20899f0efeb81f945f28fce83f77e5d0afb5f0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 24 Nov 2016 10:40:01 -0500 Subject: [PATCH 091/208] Daily renew cron job instead of weekly --- 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 d9110b79b..81f2f0e2b 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -379,7 +379,7 @@ def _check_old_letsencrypt_app(): def _install_cron(): - cron_job_file = "/etc/cron.weekly/yunohost-certificate-renew" + cron_job_file = "/etc/cron.daily/yunohost-certificate-renew" with open(cron_job_file, "w") as f: f.write("#!/bin/bash\n") From ddcc57eb9db20e55fcb2bc91ba6b70054dd3182f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 24 Nov 2016 10:59:25 -0500 Subject: [PATCH 092/208] pep8 --- src/yunohost/certificate.py | 144 ++++++++++++++++++++++-------------- 1 file changed, 89 insertions(+), 55 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 81f2f0e2b..b9fcb9b3e 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -73,8 +73,8 @@ DNS_RESOLVERS = [ "80.67.169.12", # FDN "80.67.169.40", # "89.234.141.66", # ARN - "141.255.128.100", # Aquilenet - "141.255.128.101", # + "141.255.128.100", # Aquilenet + "141.255.128.101", "89.234.186.18", # Grifon "80.67.188.188" # LDN ] @@ -107,7 +107,8 @@ def certificate_status(auth, domain_list, full=False): for domain in domain_list: # Is it in Yunohost domain list ? if domain not in yunohost_domains_list: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_domain_unknown', domain=domain)) certificates = {} @@ -124,11 +125,10 @@ def certificate_status(auth, domain_list, full=False): del status["domain"] certificates[domain] = status - return {"certificates" : certificates} + return {"certificates": certificates} def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False, staging=False): - """ Install a Let's Encrypt certificate for given domains (all by default) @@ -145,11 +145,11 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si # not used anymore _check_old_letsencrypt_app() - if self_signed: _certificate_install_selfsigned(domain_list, force) else: - _certificate_install_letsencrypt(auth, domain_list, force, no_checks, staging) + _certificate_install_letsencrypt( + auth, domain_list, force, no_checks, staging) def _certificate_install_selfsigned(domain_list, force=False): @@ -158,7 +158,8 @@ def _certificate_install_selfsigned(domain_list, force=False): # Paths of files and folder we'll need date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") - new_cert_folder = "%s/%s-history/%s-selfsigned" % (CERT_FOLDER, domain, date_tag) + new_cert_folder = "%s/%s-history/%s-selfsigned" % ( + CERT_FOLDER, domain, date_tag) original_ca_file = '/etc/ssl/certs/ca-yunohost_crt.pem' ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' @@ -175,17 +176,18 @@ def _certificate_install_selfsigned(domain_list, force=False): if (not force) and (os.path.isfile(current_cert_file)): status = _get_status(domain) - if status["summary"]["code"] in ('good', 'great') : - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) + if status["summary"]["code"] in ('good', 'great'): + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_attempt_to_replace_valid_cert', domain=domain)) # Create output folder for new certificate stuff os.makedirs(new_cert_folder) # Create our conf file, based on template, replacing the occurences of # "yunohost.org" with the given domain - with open(conf_file, "w") as f : - with open(conf_template, "r") as template : - for line in template : + with open(conf_file, "w") as f: + with open(conf_template, "r") as template: + for line in template: f.write(line.replace("yunohost.org", domain)) # Use OpenSSL command line to create a certificate signing request, @@ -196,13 +198,15 @@ def _certificate_install_selfsigned(domain_list, force=False): commands.append("openssl ca -config %s -days 3650 -in %s -out %s -batch" % (conf_file, csr_file, crt_file)) - for command in commands : - p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for command in commands: + p = subprocess.Popen( + command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out, _ = p.communicate() if p.returncode != 0: logger.warning(out) - raise MoulinetteError(errno.EIO, m18n.n('domain_cert_gen_failed')) - else : + raise MoulinetteError( + errno.EIO, m18n.n('domain_cert_gen_failed')) + else: logger.info(out) # Link the CA cert (not sure it's actually needed in practice though, @@ -229,10 +233,11 @@ def _certificate_install_selfsigned(domain_list, force=False): status = _get_status(domain) if status and status["CA_type"]["code"] == "self-signed" and status["validity"] > 3648: - logger.success(m18n.n("certmanager_cert_install_success_selfsigned", domain=domain)) - else : - logger.error("Installation of self-signed certificate installation for %s failed !", domain) - + logger.success( + m18n.n("certmanager_cert_install_success_selfsigned", domain=domain)) + else: + logger.error( + "Installation of self-signed certificate installation for %s failed !", domain) def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False, staging=False): @@ -255,20 +260,24 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F for domain in domain_list: yunohost_domains_list = yunohost.domain.domain_list(auth)['domains'] if domain not in yunohost_domains_list: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_domain_unknown', domain=domain)) # Is it self-signed ? status = _get_status(domain) if not force and status["CA_type"]["code"] != "self-signed": - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_cert_not_selfsigned', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_domain_cert_not_selfsigned', domain=domain)) if (staging): - logger.warning("Please note that you used the --staging option, and that no new certificate will actually be enabled !") + logger.warning( + "Please note that you used the --staging option, and that no new certificate will actually be enabled !") # Actual install steps for domain in domain_list: - logger.info("Now attempting install of certificate for domain %s!", domain) + logger.info( + "Now attempting install of certificate for domain %s!", domain) try: if not no_checks: @@ -278,7 +287,8 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F _fetch_and_enable_new_certificate(domain, staging) _install_cron() - logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) + logger.success( + m18n.n("certmanager_cert_install_success", domain=domain)) except Exception as e: logger.error("Certificate installation for %s failed !", domain) @@ -325,31 +335,37 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal # Is it in Yunohost dmomain list ? if domain not in yunohost.domain.domain_list(auth)['domains']: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_domain_unknown', domain=domain)) status = _get_status(domain) # Does it expire soon ? if not force or status["validity"] <= VALIDITY_LIMIT: - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_attempt_to_renew_valid_cert', domain=domain)) # Does it have a Let's Encrypt cert ? if status["CA_type"]["code"] != "lets-encrypt": - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_attempt_to_renew_nonLE_cert', domain=domain)) if (staging): - logger.warning("Please note that you used the --staging option, and that no new certificate will actually be enabled !") + logger.warning( + "Please note that you used the --staging option, and that no new certificate will actually be enabled !") # Actual renew steps for domain in domain_list: - logger.info("Now attempting renewing of certificate for domain %s !", domain) + logger.info( + "Now attempting renewing of certificate for domain %s !", domain) try: if not no_checks: _check_domain_is_ready_for_ACME(domain) _fetch_and_enable_new_certificate(domain, staging) - logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) + logger.success( + m18n.n("certmanager_cert_renew_success", domain=domain)) except Exception as e: import traceback @@ -375,7 +391,8 @@ def _check_old_letsencrypt_app(): if "letsencrypt" not in installedAppIds: return - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_old_letsencrypt_app_detected')) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_old_letsencrypt_app_detected')) def _install_cron(): @@ -436,21 +453,25 @@ location '/.well-known/acme-challenge' } ''' % WEBROOT_FOLDER - # Check there isn't a conflicting file for the acme-challenge well-known uri + # Check there isn't a conflicting file for the acme-challenge well-known + # uri for path in glob.glob('%s/*.conf' % nginx_conf_folder): - if (path == nginx_conf_file) : + if (path == nginx_conf_file): continue with open(path) as f: contents = f.read() - if ('/.well-known/acme-challenge' in contents) : - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_conflicting_nginx_file', filepath=path)) + if ('/.well-known/acme-challenge' in contents): + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_conflicting_nginx_file', filepath=path)) # Write the conf if os.path.exists(nginx_conf_file): - logger.info("Nginx configuration file for ACME challenge already exists for domain, skipping.") + logger.info( + "Nginx configuration file for ACME challenge already exists for domain, skipping.") return - logger.info("Adding Nginx configuration file for Acme challenge for domain %s.", domain) + logger.info( + "Adding Nginx configuration file for Acme challenge for domain %s.", domain) with open(nginx_conf_file, "w") as f: f.write(nginx_configuration) @@ -477,7 +498,8 @@ def _fetch_and_enable_new_certificate(domain, staging=False): _set_permissions(TMP_FOLDER, "root", "root", 0640) # Prepare certificate signing request - logger.info("Prepare key and certificate signing request (CSR) for %s...", domain) + logger.info( + "Prepare key and certificate signing request (CSR) for %s...", domain) domain_key_file = "%s/%s.pem" % (TMP_FOLDER, domain) _generate_key(domain_key_file) @@ -503,13 +525,16 @@ def _fetch_and_enable_new_certificate(domain, staging=False): CA=certification_authority) except ValueError as e: if ("urn:acme:error:rateLimited" in str(e)): - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_hit_rate_limit', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_hit_rate_limit', domain=domain)) else: logger.error(str(e)) - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cert_signing_failed')) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_cert_signing_failed')) except Exception as e: logger.error(str(e)) - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cert_signing_failed')) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_cert_signing_failed')) intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL).text @@ -524,7 +549,8 @@ def _fetch_and_enable_new_certificate(domain, staging=False): else: folder_flag = "letsencrypt" - new_cert_folder = "%s/%s-history/%s-%s" % (CERT_FOLDER, domain, date_tag, folder_flag) + new_cert_folder = "%s/%s-history/%s-%s" % ( + CERT_FOLDER, domain, date_tag, folder_flag) os.makedirs(new_cert_folder) _set_permissions(new_cert_folder, "root", "root", 0655) @@ -550,7 +576,8 @@ def _fetch_and_enable_new_certificate(domain, staging=False): status_summary = _get_status(domain)["summary"] if status_summary["code"] != "great": - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_certificate_fetching_or_enabling_failed', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_certificate_fetching_or_enabling_failed', domain=domain)) def _prepare_certificate_signing_request(domain, key_file, output_folder): @@ -582,14 +609,17 @@ def _get_status(domain): cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem") if not os.path.isfile(cert_file): - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_cert_file', domain=domain, file=cert_file)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_no_cert_file', domain=domain, file=cert_file)) try: - cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file).read()) + cert = crypto.load_certificate( + crypto.FILETYPE_PEM, open(cert_file).read()) except Exception as exception: import traceback traceback.print_exc(file=sys.stdout) - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception)) cert_subject = cert.get_subject().CN cert_issuer = cert.get_issuer().CN @@ -626,7 +656,7 @@ def _get_status(domain): "verbose": "CRITICAL", } - elif CA_type["code"] in ("self-signed","fake-lets-encrypt"): + elif CA_type["code"] in ("self-signed", "fake-lets-encrypt"): status_summary = { "code": "warning", "verbose": "WARNING", @@ -699,13 +729,13 @@ def _set_permissions(path, user, group, permissions): os.chmod(path, permissions) -def _enable_certificate(domain, new_cert_folder) : +def _enable_certificate(domain, new_cert_folder): logger.info("Enabling the certificate for domain %s ...", domain) live_link = os.path.join(CERT_FOLDER, domain) # If a live link (or folder) already exists - if os.path.exists(live_link) : + if os.path.exists(live_link): # If it's not a link ... expect if to be a folder if not os.path.islink(live_link): # Backup it and remove it @@ -741,11 +771,13 @@ def _check_domain_is_ready_for_ACME(domain): # Check if IP from DNS matches public IP if not _dns_ip_match_public_ip(public_ip, domain): - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_dns_ip_differs_from_public_ip', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_domain_dns_ip_differs_from_public_ip', domain=domain)) # Check if domain seems to be accessible through HTTP ? if not _domain_is_accessible_through_HTTP(public_ip, domain): - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_http_not_working', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_domain_http_not_working', domain=domain)) def _dns_ip_match_public_ip(public_ip, domain): @@ -754,7 +786,8 @@ def _dns_ip_match_public_ip(public_ip, domain): resolver.nameservers = DNS_RESOLVERS answers = resolver.query(domain, "A") except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): - raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_no_A_record', domain=domain)) + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_error_no_A_record', domain=domain)) dns_ip = str(answers[0]) @@ -771,7 +804,8 @@ def _domain_is_accessible_through_HTTP(ip, domain): def _name_self_CA(): - cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(SELF_CA_FILE).read()) + cert = crypto.load_certificate( + crypto.FILETYPE_PEM, open(SELF_CA_FILE).read()) return cert.get_subject().CN From 1f6a57bc274a7a9c355206615e1ae674061d53b2 Mon Sep 17 00:00:00 2001 From: M5oul Date: Thu, 24 Nov 2016 22:21:58 +0100 Subject: [PATCH 093/208] [enh] Complete readme (#183) Add: - how to contribute. - repository content. - how this part of YunoHost works and interface with other YunoHost components. - dependencies - the fact that we could implement other modules from API. - actionmap and hooks reference. - links toward contents of the repository: - sort contents by kind and importance. - website project link - explanation about what the repo is about. - license section. --- README.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e928e9ce..714fa0980 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,38 @@ -Please report issues here (no registration needed): -https://dev.yunohost.org/projects/yunohost/issues +# YunoHost core + +- [YunoHost project website](https://yunohost.org) + +This repository is the core of YunoHost code. + +## Issues +- [Please report issues on YunoHost bugtracker](https://dev.yunohost.org/projects/yunohost/issues) (no registration needed). + +## Contribute +- You can develop on this repository using [ynh-dev tool](https://github.com/YunoHost/ynh-dev) with `use-git` sub-command. +- On this repository we are [following this workflow](https://yunohost.org/#/build_system_en): `stable <— testing <— branch`. +- Note: if you modify python scripts, you will have to modifiy the actions map. + +## Repository content +- [YunoHost core Python 2.7 scripts](https://github.com/YunoHost/yunohost/tree/stable/src/yunohost). +- [An actionsmap](https://github.com/YunoHost/yunohost/blob/stable/data/actionsmap/yunohost.yml) used by moulinette. +- [Services configuration templates](https://github.com/YunoHost/yunohost/tree/stable/data/templates). +- [Hooks](https://github.com/YunoHost/yunohost/tree/stable/data/hooks). +- [Locales](https://github.com/YunoHost/yunohost/tree/stable/locales) for translations of `yunohost` command. +- [Shell helpers](https://github.com/YunoHost/yunohost/tree/stable/data/helpers.d) for [application packaging](https://yunohost.org/#/packaging_apps_helpers_en). +- [Modules for the XMPP server Metronome](https://github.com/YunoHost/yunohost/tree/stable/lib/metronome/modules). +- [Debian files](https://github.com/YunoHost/yunohost/tree/stable/debian) for package creation. + +## How does it works? +- Python core scripts are accessible through two interfaces thanks to the [moulinette framework](https://github.com/YunoHost/moulinette): + - [CLI](https://en.wikipedia.org/wiki/Command-line_interface) for `yunohost` command. + - [API](https://en.wikipedia.org/wiki/Application_programming_interface) for [web administration module](https://github.com/YunoHost/yunohost-admin) (other modules could be implemented). +- You can find more details about how YunoHost works on this [documentation (in french)](https://yunohost.org/#/package_list_fr). + +## Dependencies +- [Python 2.7](https://www.python.org/download/releases/2.7) +- [Moulinette](https://github.com/YunoHost/moulinette) +- [Bash](https://www.gnu.org/software/bash/bash.html) +- [Debian Jessie](https://www.debian.org/releases/jessie) + +## License +As [other components of YunoHost core code](https://yunohost.org/#/faq_en), this repository is under GNU AGPL v.3 license. From 952040183e4111374fdf0e3b754425a815632745 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 27 Nov 2016 01:16:22 +0100 Subject: [PATCH 094/208] [mod] give instructions on how to solve the conf.json.persistant parsing error --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index dc801a908..bbb33e937 100644 --- a/locales/en.json +++ b/locales/en.json @@ -209,8 +209,8 @@ "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}", - "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}", + "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. To fix this you need to fix the json syntax error in the SSOwat persistent configuration file which is located there on your filesystem: /etc/ssowat/conf.json.persistent", + "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. To fix this you need to fix the json syntax error in the SSOwat persistent configuration file which is located there on your filesystem: /etc/ssowat/conf.json.persistent", "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", From 504baefd87a45edaf59ccb91da4f4c502f7019bc Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 28 Nov 2016 02:06:45 +0100 Subject: [PATCH 095/208] [fix] avoid random bug on post-install due to nscd cache\n\nThanks to the work of Alex Aubin who've managed to discover why this was happening and propose a solution. --- src/yunohost/tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index f78e32363..7d7d8d0cc 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -32,6 +32,7 @@ import requests import json import errno import logging +import subprocess from collections import OrderedDict import apt @@ -82,6 +83,7 @@ def tools_ldapinit(auth): } auth.update('cn=admin', admin_dict) + subprocess.call(['nscd', '-i', 'passwd']) logger.success(m18n.n('ldap_initialized')) From f956fa7161bb855d165e2ab6b9b013c15cc6e5b2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 28 Nov 2016 02:35:19 +0100 Subject: [PATCH 096/208] [enh] Adding check that user is actually created + minor refactor of ldap/auth init --- locales/en.json | 1 + src/yunohost/tools.py | 31 +++++++++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/locales/en.json b/locales/en.json index e939b26fa..5bc3b7bdb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -114,6 +114,7 @@ "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_initialized": "LDAP has been initialized", + "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin. Aborting.", "license_undefined": "undefined", "mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'", "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 7d7d8d0cc..671a7304c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -33,6 +33,7 @@ import json import errno import logging import subprocess +import pwd from collections import OrderedDict import apt @@ -53,12 +54,20 @@ apps_setting_path= '/etc/yunohost/apps/' logger = getActionLogger('yunohost.tools') -def tools_ldapinit(auth): +def tools_ldapinit(): """ YunoHost LDAP initialization """ + + # Instantiate LDAP Authenticator + auth = init_authenticator(('ldap', 'default'), + {'uri': "ldap://localhost:389", + 'base_dn': "dc=yunohost,dc=org", + 'user_rdn': "cn=admin" }) + auth.authenticate('yunohost') + with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: ldap_map = yaml.load(f) @@ -83,10 +92,19 @@ def tools_ldapinit(auth): } auth.update('cn=admin', admin_dict) + + # Force nscd to refresh cache to take admin creation into account subprocess.call(['nscd', '-i', 'passwd']) - logger.success(m18n.n('ldap_initialized')) + # Check admin actually exists now + try: + pwd.getpwnam("admin") + except KeyError: + raise MoulinetteError(errno.EINVAL, + m18n.n('ldap_init_failed_to_create_admin')) + logger.success(m18n.n('ldap_initialized')) + return auth def tools_adminpw(auth, new_password): """ @@ -193,16 +211,9 @@ def tools_postinstall(domain, password, ignore_dyndns=False): logger.info(m18n.n('yunohost_installing')) - # Instantiate LDAP Authenticator - auth = init_authenticator(('ldap', 'default'), - {'uri': "ldap://localhost:389", - 'base_dn': "dc=yunohost,dc=org", - 'user_rdn': "cn=admin" }) - auth.authenticate('yunohost') - # Initialize LDAP for YunoHost # TODO: Improve this part by integrate ldapinit into conf_regen hook - tools_ldapinit(auth) + auth = tools_ldapinit() # Create required folders folders_to_create = [ From 7f046f088003ed9effe56ad90abac833f35fefc9 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 28 Nov 2016 14:32:07 +0100 Subject: [PATCH 097/208] [mod] no space before a ? in english --- src/yunohost/certificate.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index b9fcb9b3e..3660698ea 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -105,7 +105,7 @@ def certificate_status(auth, domain_list, full=False): else: yunohost_domains_list = yunohost.domain.domain_list(auth)['domains'] for domain in domain_list: - # Is it in Yunohost domain list ? + # Is it in Yunohost domain list? if domain not in yunohost_domains_list: raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_domain_unknown', domain=domain)) @@ -263,13 +263,13 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_domain_unknown', domain=domain)) - # Is it self-signed ? + # Is it self-signed? status = _get_status(domain) if not force and status["CA_type"]["code"] != "self-signed": raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_domain_cert_not_selfsigned', domain=domain)) - if (staging): + if staging: logger.warning( "Please note that you used the --staging option, and that no new certificate will actually be enabled !") @@ -317,12 +317,12 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal if domain_list == []: for domain in yunohost.domain.domain_list(auth)['domains']: - # Does it have a Let's Encrypt cert ? + # Does it have a Let's Encrypt cert? status = _get_status(domain) if status["CA_type"]["code"] != "lets-encrypt": continue - # Does it expire soon ? + # Does it expire soon? if force or status["validity"] <= VALIDITY_LIMIT: domain_list.append(domain) @@ -333,19 +333,19 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal else: for domain in domain_list: - # Is it in Yunohost dmomain list ? + # Is it in Yunohost dmomain list? if domain not in yunohost.domain.domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_domain_unknown', domain=domain)) status = _get_status(domain) - # Does it expire soon ? + # Does it expire soon? if not force or status["validity"] <= VALIDITY_LIMIT: raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_attempt_to_renew_valid_cert', domain=domain)) - # Does it have a Let's Encrypt cert ? + # Does it have a Let's Encrypt cert? if status["CA_type"]["code"] != "lets-encrypt": raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_attempt_to_renew_nonLE_cert', domain=domain)) @@ -774,7 +774,7 @@ def _check_domain_is_ready_for_ACME(domain): raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_domain_dns_ip_differs_from_public_ip', domain=domain)) - # Check if domain seems to be accessible through HTTP ? + # Check if domain seems to be accessible through HTTP? if not _domain_is_accessible_through_HTTP(public_ip, domain): raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_domain_http_not_working', domain=domain)) From 5b73ab448f33194ced9a8b5e3afcb74490f05186 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 28 Nov 2016 14:32:24 +0100 Subject: [PATCH 098/208] [mod] create list in one step --- src/yunohost/certificate.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 3660698ea..0133a3387 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -192,11 +192,12 @@ def _certificate_install_selfsigned(domain_list, force=False): # Use OpenSSL command line to create a certificate signing request, # and self-sign the cert - commands = [] - commands.append("openssl req -new -config %s -days 3650 -out %s -keyout %s -nodes -batch" - % (conf_file, csr_file, key_file)) - commands.append("openssl ca -config %s -days 3650 -in %s -out %s -batch" - % (conf_file, csr_file, crt_file)) + commands = [ + "openssl req -new -config %s -days 3650 -out %s -keyout %s -nodes -batch" + % (conf_file, csr_file, key_file), + "openssl ca -config %s -days 3650 -in %s -out %s -batch" + % (conf_file, csr_file, crt_file), + ] for command in commands: p = subprocess.Popen( From 86eb9a2405324cab5d9a84335d52aab21ca46580 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 28 Nov 2016 14:34:12 +0100 Subject: [PATCH 099/208] [fix] avoid reverse order log display on web admin --- src/yunohost/certificate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 0133a3387..eec261f20 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -292,8 +292,7 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F m18n.n("certmanager_cert_install_success", domain=domain)) except Exception as e: - logger.error("Certificate installation for %s failed !", domain) - logger.error(str(e)) + logger.error("Certificate installation for %s failed !\nException: %s", domain, e) def certificate_renew(auth, domain_list, force=False, no_checks=False, email=False, staging=False): From 2f4f2546127cf497b1f2511437c98f6a9acd5f65 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 28 Nov 2016 14:42:22 +0100 Subject: [PATCH 100/208] [mod] pep8 --- src/yunohost/certificate.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index eec261f20..b9e37dc79 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -173,7 +173,7 @@ def _certificate_install_selfsigned(domain_list, force=False): # Check we ain't trying to overwrite a good cert ! current_cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem") - if (not force) and (os.path.isfile(current_cert_file)): + if not force and os.path.isfile(current_cert_file): status = _get_status(domain) if status["summary"]["code"] in ('good', 'great'): @@ -350,7 +350,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_attempt_to_renew_nonLE_cert', domain=domain)) - if (staging): + if staging: logger.warning( "Please note that you used the --staging option, and that no new certificate will actually be enabled !") @@ -456,11 +456,14 @@ location '/.well-known/acme-challenge' # Check there isn't a conflicting file for the acme-challenge well-known # uri for path in glob.glob('%s/*.conf' % nginx_conf_folder): - if (path == nginx_conf_file): + + if path == nginx_conf_file: continue + with open(path) as f: contents = f.read() - if ('/.well-known/acme-challenge' in contents): + + if '/.well-known/acme-challenge' in contents: raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_conflicting_nginx_file', filepath=path)) @@ -512,7 +515,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False): domain_csr_file = "%s/%s.csr" % (TMP_FOLDER, domain) - if (staging): + if staging: certification_authority = STAGING_CERTIFICATION_AUTHORITY else: certification_authority = PRODUCTION_CERTIFICATION_AUTHORITY @@ -524,7 +527,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False): log=logger, CA=certification_authority) except ValueError as e: - if ("urn:acme:error:rateLimited" in str(e)): + if "urn:acme:error:rateLimited" in str(e): raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_hit_rate_limit', domain=domain)) else: @@ -544,7 +547,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False): # Create corresponding directory date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") - if (staging): + if staging: folder_flag = "staging" else: folder_flag = "letsencrypt" @@ -567,7 +570,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False): _set_permissions(domain_cert_file, "root", "metronome", 0640) - if (staging): + if staging: return _enable_certificate(domain, new_cert_folder) From 4a9b89d12e0ff22379e74bb7dfa85cd4b441de69 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 28 Nov 2016 14:42:36 +0100 Subject: [PATCH 101/208] [mod] merge double with --- src/yunohost/certificate.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index b9e37dc79..fb982980b 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -185,10 +185,9 @@ def _certificate_install_selfsigned(domain_list, force=False): # Create our conf file, based on template, replacing the occurences of # "yunohost.org" with the given domain - with open(conf_file, "w") as f: - with open(conf_template, "r") as template: - for line in template: - f.write(line.replace("yunohost.org", domain)) + with open(conf_file, "w") as f, open(conf_template, "r") as template: + for line in template: + f.write(line.replace("yunohost.org", domain)) # Use OpenSSL command line to create a certificate signing request, # and self-sign the cert @@ -216,10 +215,9 @@ def _certificate_install_selfsigned(domain_list, force=False): os.symlink(original_ca_file, ca_file) # Append ca.pem at the end of crt.pem - with open(ca_file, "r") as ca_pem: - with open(crt_file, "a") as crt_pem: - crt_pem.write("\n") - crt_pem.write(ca_pem.read()) + with open(ca_file, "r") as ca_pem, open(crt_file, "a") as crt_pem: + crt_pem.write("\n") + crt_pem.write(ca_pem.read()) # Set appropriate permissions _set_permissions(new_cert_folder, "root", "root", 0755) From f0c29147dd65c5f819f3ade4e5238549cc9c0fe4 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 28 Nov 2016 14:42:42 +0100 Subject: [PATCH 102/208] [mod] style --- src/yunohost/certificate.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index fb982980b..f8a927e08 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -201,7 +201,9 @@ def _certificate_install_selfsigned(domain_list, force=False): for command in commands: p = subprocess.Popen( command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, _ = p.communicate() + if p.returncode != 0: logger.warning(out) raise MoulinetteError( @@ -532,8 +534,10 @@ def _fetch_and_enable_new_certificate(domain, staging=False): logger.error(str(e)) raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_cert_signing_failed')) + except Exception as e: logger.error(str(e)) + raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_cert_signing_failed')) @@ -552,6 +556,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False): new_cert_folder = "%s/%s-history/%s-%s" % ( CERT_FOLDER, domain, date_tag, folder_flag) + os.makedirs(new_cert_folder) _set_permissions(new_cert_folder, "root", "root", 0655) From a7289c447adab0ab170e684b4866d322994b2778 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 28 Nov 2016 14:54:13 +0100 Subject: [PATCH 103/208] [fix] typo --- data/actionsmap/yunohost.yml | 2 +- src/yunohost/app.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 56b28cf38..e5af4ee6b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -437,7 +437,7 @@ app: full: --args help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path") -n: - full: --no-remove-on-fail + full: --no-remove-on-failure help: Debug option to avoid removing the app on a failed installation ### app_remove() TODO: Write help diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f85e9f191..b70b6388a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -440,7 +440,7 @@ def app_upgrade(auth, app=[], url=None, file=None): logger.success(m18n.n('upgrade_complete')) -def app_install(auth, app, label=None, args=None, no_remove_on_fail=False): +def app_install(auth, app, label=None, args=None, no_remove_on_failure=False): """ Install apps @@ -448,7 +448,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_fail=False): app -- Name, local path or git URL of the app to install label -- Custom name for the app args -- Serialize arguments for app installation - no_remove_on_fail -- Debug option to avoid removing the app on a failed installation + 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 @@ -542,7 +542,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_fail=False): logger.exception(m18n.n('unexpected_error')) finally: if install_retcode != 0: - if not no_remove_on_fail: + if not no_remove_on_failure: # Setup environment for remove script env_dict_remove = {} env_dict_remove["YNH_APP_ID"] = app_id From 8bf8534a9a68c9ca324559c1aede7a939d495a52 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Nov 2016 00:02:52 -0500 Subject: [PATCH 104/208] [fix] Need to create archives_path even for custom output directory --- src/yunohost/backup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index dd7c73852..7d2e9d445 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -120,8 +120,10 @@ def backup_create(name=None, description=None, output_directory=None, env_var['CAN_BIND'] = 0 else: output_directory = archives_path - if not os.path.isdir(archives_path): - os.mkdir(archives_path, 0750) + + # Create archives directory if it does not exists + if not os.path.isdir(archives_path): + os.mkdir(archives_path, 0750) def _clean_tmp_dir(retcode=0): ret = hook_callback('post_backup_create', args=[tmp_dir, retcode]) From 6171fbdb0302cf524f1e17517b27f2a16294306b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Nov 2016 00:04:17 -0500 Subject: [PATCH 105/208] Keep track of backups with custom directory using symlinks --- locales/en.json | 1 + src/yunohost/backup.py | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index efeb66e69..c50f3d3de 100644 --- a/locales/en.json +++ b/locales/en.json @@ -43,6 +43,7 @@ "backup_action_required": "You must specify something to save", "backup_app_failed": "Unable to back up the app '{app:s}'", "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_hook_not_exec": "Hook '{hook:s}' not executed in this backup", "backup_archive_name_exists": "The backup's archive name already exists", "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 7d2e9d445..0e991e8fc 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -290,7 +290,7 @@ def backup_create(name=None, description=None, output_directory=None, raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed')) - # Add files to the arvhice + # Add files to the archive try: tar.add(tmp_dir, arcname='') tar.close() @@ -300,10 +300,22 @@ def backup_create(name=None, description=None, output_directory=None, raise MoulinetteError(errno.EIO, m18n.n('backup_creation_failed')) + # FIXME : it looks weird that the "move info file" is not enabled if + # user activated "no_compress" ... or does it really means + # "dont_keep_track_of_this_backup_in_history" ? + # Move info file shutil.move(tmp_dir + '/info.json', '{:s}/{:s}.info.json'.format(archives_path, name)) + # If backuped to a non-default location, keep a symlink of the archive + # to that location + if output_directory != archives_path: + link = "%s/%s.tar.gz" % (archives_path, name) + os.symlink(archive_file, link) + + + # Clean temporary directory if tmp_dir != output_directory: _clean_tmp_dir() @@ -604,10 +616,21 @@ def backup_info(name, with_details=False, human_readable=False): """ archive_file = '%s/%s.tar.gz' % (archives_path, name) - if not os.path.isfile(archive_file): + + # Check file exist (even if it's a broken symlink) + if not os.path.lexists(archive_file): raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown', name=name)) + # If symlink, retrieve the real path + if os.path.islink(archive_file) : + archive_file = os.path.realpath(archive_file) + + # Raise exception if link is broken (e.g. on unmounted external storage) + if (not os.path.exists(archive_file)) : + raise MoulinetteError(errno.EIO, + m18n.n('backup_archive_broken_link', path=archive_file)) + info_file = "%s/%s.info.json" % (archives_path, name) try: with open(info_file) as f: From 2cd18d878615997e59ea39cedddfa0386e3f64e6 Mon Sep 17 00:00:00 2001 From: Zamentur aka ljf Date: Wed, 30 Nov 2016 18:26:40 +0100 Subject: [PATCH 106/208] [fix] Support git ynh app with submodules #533 (#174) --- src/yunohost/app.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0be58f71c..019bac899 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1258,8 +1258,13 @@ def _fetch_app_from_git(app): url = url[:tree_index] branch = app[tree_index+6:] try: + # We use currently git 2.1 so we can't use --shallow-submodules + # option. When git will be in 2.9 (with the new debian version) + # we will be able to use it. Without this option all the history + # of the submodules repo is downloaded. subprocess.check_call([ - 'git', 'clone', '--depth=1', url, extracted_app_folder]) + 'git', 'clone', '--depth=1', '--recursive', url, + extracted_app_folder]) subprocess.check_call([ 'git', 'reset', '--hard', branch ], cwd=extracted_app_folder) From 777c4833fdf6d2b7ec19d26f69dd99a0e2dc4741 Mon Sep 17 00:00:00 2001 From: opi Date: Thu, 1 Dec 2016 21:33:50 +0100 Subject: [PATCH 107/208] Update changelog for 2.5.0 release --- debian/changelog | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/debian/changelog b/debian/changelog index 94ddd58ec..7467b4ff2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,43 @@ +yunohost (2.5.0) testing; urgency=low + + * Certificate management integration (e.g. Let's Encrypt certificate install) + * [fix] Support git ynh app with submodules #533 (#174) + * [enh] display file path on file_not_exist error + * [mod] move a part of os.system calls to native shutil/os + * [fix] Can't restore app on a root domain + + Miscellaneous + + * Update backup.py + * [mod] autopep8 + * [mod] trailing spaces + * [mod] pep8 + * [mod] remove useless imports + * [mod] more pythonic and explicit tests with more verbose errors + * [fix] correctly handle all cases + * [mod] simplier condition + * [fix] uses https + * [mod] uses logger string concatenation api + * [mod] small opti, getting domain list can be slow + * [mod] pylint + * [mod] os.path.join + * [mod] remove useless assign + * [enh] include tracebak into error email + * [mod] remove the summary code concept and switch to code/verbose duet instead + * [mod] I only need to reload nginx, not restart it + * [mod] top level constants should be upper case (pep8) + * Check that the DNS A record matches the global IP now using dnspython and FDN's DNS + * Refactored the self-signed cert generation, some steps were overly complicated for no reason + * Using a single generic skipped regex for acme challenge in ssowat conf + * Adding an option to use the staging Let's Encrypt CA, sort of a dry-run + * [enh] Complete readme (#183) + * [fix] avoid reverse order log display on web admin + + Thanks to all contributors: Aleks, Bram, JimboJoe, ljf, M5oul + Kudos to Aleks for leading the Let's Encrypt integration to YunoHost core \o/ + + -- opi Thu, 01 Dec 2016 21:22:19 +0100 + yunohost (2.4.2) stable; urgency=low [ Laurent Peuch ] From f6188405bc2914583500e4b912397117367248fc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Dec 2016 23:09:02 -0500 Subject: [PATCH 108/208] [fix] Fix the way name of self-CA is determined --- locales/en.json | 3 ++- src/yunohost/certificate.py | 27 +++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/locales/en.json b/locales/en.json index efeb66e69..806def22a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -255,5 +255,6 @@ "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_cert_signing_failed" : "Signing the new certificate failed.", "certmanager_no_cert_file" : "Unable to read certificate file for domain {domain:s} (file : {file:s})", - "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_conflicting_nginx_file": "Unable to prepare domain for ACME challenge : the nginx configuration file {filepath:s} is conflicting and should be removed first.", + "certmanager_unable_to_determine_self_CA_name": "Unable to determine name of self-signing authority." } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index f8a927e08..db99da733 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -56,6 +56,8 @@ WEBROOT_FOLDER = "/tmp/acme-challenge-public/" SELF_CA_FILE = "/etc/ssl/certs/ca-yunohost_crt.pem" ACCOUNT_KEY_FILE = "/etc/yunohost/letsencrypt_account.pem" +SSL_DIR = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' + KEY_SIZE = 3072 VALIDITY_LIMIT = 15 # days @@ -161,11 +163,9 @@ def _certificate_install_selfsigned(domain_list, force=False): new_cert_folder = "%s/%s-history/%s-selfsigned" % ( CERT_FOLDER, domain, date_tag) - original_ca_file = '/etc/ssl/certs/ca-yunohost_crt.pem' - ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' - conf_template = os.path.join(ssl_dir, "openssl.cnf") + conf_template = os.path.join(SSL_DIR, "openssl.cnf") - csr_file = os.path.join(ssl_dir, "certs", "yunohost_csr.pem") + csr_file = os.path.join(SSL_DIR, "certs", "yunohost_csr.pem") conf_file = os.path.join(new_cert_folder, "openssl.cnf") key_file = os.path.join(new_cert_folder, "key.pem") crt_file = os.path.join(new_cert_folder, "crt.pem") @@ -214,7 +214,7 @@ def _certificate_install_selfsigned(domain_list, force=False): # Link the CA cert (not sure it's actually needed in practice though, # since we append it at the end of crt.pem. For instance for Let's # Encrypt certs, we only need the crt.pem and key.pem) - os.symlink(original_ca_file, ca_file) + os.symlink(SELF_CA_FILE, ca_file) # Append ca.pem at the end of crt.pem with open(ca_file, "r") as ca_pem, open(crt_file, "a") as crt_pem: @@ -810,9 +810,20 @@ def _domain_is_accessible_through_HTTP(ip, domain): def _name_self_CA(): - cert = crypto.load_certificate( - crypto.FILETYPE_PEM, open(SELF_CA_FILE).read()) - return cert.get_subject().CN + ca_conf = os.path.join(SSL_DIR, "openssl.ca.cnf") + + try : + with open("%s/openssl.ca.cnf" % SSL_DIR) as f: + lines = f.readlines() + + for line in lines: + if (line.startswith("commonName_default")): + return line.split()[2] + except : + pass + + logger.warning(m18n.n('certmanager_unable_to_determine_self_CA_name')) + return "" def _tail(n, file_path): From 4eaefe5145a41453a6109c81972c31d52bbe43d8 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 2 Dec 2016 14:19:20 +0100 Subject: [PATCH 109/208] [mod] use unused variable --- 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 db99da733..c8f2598a2 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -813,7 +813,7 @@ def _name_self_CA(): ca_conf = os.path.join(SSL_DIR, "openssl.ca.cnf") try : - with open("%s/openssl.ca.cnf" % SSL_DIR) as f: + with open(ca_conf) as f: lines = f.readlines() for line in lines: From 005d624f2fd3bb9133158a2fd1dd2992eb0dd6cc Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 2 Dec 2016 14:19:28 +0100 Subject: [PATCH 110/208] [mod] pep8 --- src/yunohost/certificate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index c8f2598a2..74e93314c 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -817,9 +817,9 @@ def _name_self_CA(): lines = f.readlines() for line in lines: - if (line.startswith("commonName_default")): + if line.startswith("commonName_default"): return line.split()[2] - except : + except: pass logger.warning(m18n.n('certmanager_unable_to_determine_self_CA_name')) From 595f897261371d4d76a4725aacab59ea634a93cf Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 3 Dec 2016 11:34:46 +0100 Subject: [PATCH 111/208] [fix] implement opi's feedback --- locales/en.json | 2 +- src/yunohost/tools.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5bc3b7bdb..acbd0ed98 100644 --- a/locales/en.json +++ b/locales/en.json @@ -114,7 +114,7 @@ "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_initialized": "LDAP has been initialized", - "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin. Aborting.", + "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user.", "license_undefined": "undefined", "mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'", "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 671a7304c..e92729129 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -100,8 +100,8 @@ def tools_ldapinit(): try: pwd.getpwnam("admin") except KeyError: - raise MoulinetteError(errno.EINVAL, - m18n.n('ldap_init_failed_to_create_admin')) + logger.error(m18n.n('ldap_init_failed_to_create_admin')) + raise MoulinetteError(errno.EINVAL, m18n.n('installation_failed')) logger.success(m18n.n('ldap_initialized')) return auth From 7ee6ab2fbacf42e19cb2abb5d1b14e6fd0885c9b Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 3 Dec 2016 11:38:37 +0100 Subject: [PATCH 112/208] [mod] implement opi's feedback --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index bbb33e937..700df8db4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -209,8 +209,8 @@ "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}. To fix this you need to fix the json syntax error in the SSOwat persistent configuration file which is located there on your filesystem: /etc/ssowat/conf.json.persistent", - "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. To fix this you need to fix the json syntax error in the SSOwat persistent configuration file which is located there on your filesystem: /etc/ssowat/conf.json.persistent", + "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", From 5fee9c13534190888ae7c863bfad1854fa4bd1a3 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 3 Dec 2016 11:48:29 +0100 Subject: [PATCH 113/208] [mod] remove unused imports --- src/yunohost/tools.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e92729129..93d50e2a8 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -24,10 +24,7 @@ Specific tools """ import os -import sys import yaml -import re -import getpass import requests import json import errno @@ -44,9 +41,9 @@ from moulinette.utils.log import getActionLogger from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list from yunohost.domain import domain_add, domain_list, get_public_ip from yunohost.dyndns import dyndns_subscribe -from yunohost.firewall import firewall_upnp, firewall_reload +from yunohost.firewall import firewall_upnp from yunohost.service import service_status, service_regen_conf, service_log -from yunohost.monitor import monitor_disk, monitor_network, monitor_system +from yunohost.monitor import monitor_disk, monitor_system from yunohost.utils.packages import ynh_packages_version apps_setting_path= '/etc/yunohost/apps/' From e348c6214ac3b95e4c993f1700d7970a3c93cba1 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 3 Dec 2016 11:52:24 +0100 Subject: [PATCH 114/208] [mod] pep8 --- src/yunohost/tools.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 93d50e2a8..ce0ced1ab 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -69,12 +69,16 @@ def tools_ldapinit(): ldap_map = yaml.load(f) for rdn, attr_dict in ldap_map['parents'].items(): - try: auth.add(rdn, attr_dict) - except: pass + try: + auth.add(rdn, attr_dict) + except: + pass for rdn, attr_dict in ldap_map['children'].items(): - try: auth.add(rdn, attr_dict) - except: pass + try: + auth.add(rdn, attr_dict) + except: + pass admin_dict = { 'cn': 'admin', @@ -170,7 +174,8 @@ def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False): try: with open('/etc/yunohost/installed', 'r') as f: service_regen_conf() - except IOError: pass + except IOError: + pass logger.success(m18n.n('maindomain_changed')) @@ -199,6 +204,7 @@ def tools_postinstall(domain, password, ignore_dyndns=False): else: dyndomains = json.loads(r.text) dyndomain = '.'.join(domain.split('.')[1:]) + if dyndomain in dyndomains: if requests.get('https://dyndns.yunohost.org/test/%s' % domain).status_code == 200: dyndns = True @@ -222,8 +228,10 @@ def tools_postinstall(domain, password, ignore_dyndns=False): ] for folder in folders_to_create: - try: os.listdir(folder) - except OSError: os.makedirs(folder) + try: + os.listdir(folder) + except OSError: + os.makedirs(folder) # Change folders permissions os.system('chmod 755 /home/yunohost.app') @@ -308,6 +316,7 @@ def tools_update(ignore_apps=False, ignore_packages=False): logger.info(m18n.n('updating_apt_cache')) if not cache.update(): raise MoulinetteError(errno.EPERM, m18n.n('update_cache_failed')) + logger.info(m18n.n('done')) cache.open(None) @@ -355,7 +364,7 @@ def tools_update(ignore_apps=False, ignore_packages=False): if len(apps) == 0 and len(packages) == 0: logger.info(m18n.n('packages_no_upgrade')) - return { 'packages': packages, 'apps': apps } + return {'packages': packages, 'apps': apps} def tools_upgrade(auth, ignore_apps=False, ignore_packages=False): @@ -388,6 +397,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False): critical_upgrades.add(pkg.name) # Temporarily keep package ... pkg.mark_keep() + # ... and set a hourly cron up to upgrade critical packages if critical_upgrades: logger.info(m18n.n('packages_upgrade_critical_later', @@ -397,6 +407,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False): if cache.get_changes(): logger.info(m18n.n('upgrading_packages')) + try: # Apply APT changes # TODO: Logs output for the API @@ -424,7 +435,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False): # Return API logs if it is an API call if is_api: - return { "log": service_log('yunohost-api', number="100").values()[0] } + return {"log": service_log('yunohost-api', number="100").values()[0]} def tools_diagnosis(auth, private=False): @@ -483,6 +494,7 @@ def tools_diagnosis(auth, private=False): # Services status services = service_status() diagnosis['services'] = {} + for service in services: diagnosis['services'][service] = "%s (%s)" % (services[service]['status'], services[service]['loaded']) From 110d481cd12ffa05407eaa58ae3af24b2b289e0e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 3 Dec 2016 12:00:55 +0100 Subject: [PATCH 115/208] [mod] remove unused imports --- src/yunohost/app.py | 2 -- src/yunohost/backup.py | 1 - src/yunohost/hook.py | 3 --- src/yunohost/monitor.py | 2 +- src/yunohost/service.py | 2 +- src/yunohost/user.py | 1 - 6 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 019bac899..a30690018 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -24,10 +24,8 @@ Manage apps """ import os -import sys import json import shutil -import stat import yaml import time import re diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index dd7c73852..f2a48735c 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -25,7 +25,6 @@ """ import os import re -import sys import json import errno import time diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 2d46cfcd5..db7cd9504 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -24,11 +24,8 @@ Manage hooks """ import os -import sys import re -import json import errno -import subprocess from glob import iglob from moulinette.core import MoulinetteError diff --git a/src/yunohost/monitor.py b/src/yunohost/monitor.py index d0fe224e9..315fb3a08 100644 --- a/src/yunohost/monitor.py +++ b/src/yunohost/monitor.py @@ -35,7 +35,7 @@ import errno import os import dns.resolver import cPickle as pickle -from datetime import datetime, timedelta +from datetime import datetime from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger diff --git a/src/yunohost/service.py b/src/yunohost/service.py index ab26dd2bc..b1b776326 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -36,7 +36,7 @@ from difflib import unified_diff from moulinette.core import MoulinetteError from moulinette.utils import log, filesystem -from yunohost.hook import hook_list, hook_callback +from yunohost.hook import hook_callback base_conf_path = '/home/yunohost.conf' diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ec7dd539c..f5ac25ea8 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -30,7 +30,6 @@ import string import json import errno import subprocess -import math import re from moulinette.core import MoulinetteError From f43750b4d6f69c08bd1772510b6c788dd7a4a465 Mon Sep 17 00:00:00 2001 From: julienmalik Date: Sun, 4 Dec 2016 11:55:06 +0100 Subject: [PATCH 116/208] [fix] Fix up indentation in actionmap Reported by beudbeud on the chan --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b410159a8..26543721e 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -500,7 +500,7 @@ app: full: --args help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path") -n: - full: --no-remove-on-failure + full: --no-remove-on-failure help: Debug option to avoid removing the app on a failed installation ### app_remove() TODO: Write help From c2065f0a7222f9d00111e4ed47a35b3b15d2f33f Mon Sep 17 00:00:00 2001 From: opi Date: Sun, 4 Dec 2016 15:32:34 +0100 Subject: [PATCH 117/208] [fix] Improve dnssec key generation on low entropy devices See https://bugs.launchpad.net/ubuntu/+source/bind9/+bug/963368 --- 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 878bc577e..b32cde0da 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -95,7 +95,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 -n USER %s' % domain) + 'dnssec-keygen -a hmac-md5 -b 128 -r /dev/urandom -n USER %s' % domain) os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private') key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0] From 397aeb2498425d07e9ca60d56e536fe07500bdc9 Mon Sep 17 00:00:00 2001 From: opi Date: Mon, 5 Dec 2016 10:42:38 +0100 Subject: [PATCH 118/208] [enh] Add haveged as dependency Increase entropy on virtual servers. Speed up key generation by openssl and dnssec See https://github.com/YunoHost/yunohost/pull/201 & https://bugs.launchpad.net/ubuntu/+source/bind9/+bug/963368 --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 80f562e76..ec44a06b1 100644 --- a/debian/control +++ b/debian/control @@ -25,6 +25,7 @@ Depends: ${python:Depends}, ${misc:Depends} , dnsmasq, openssl, avahi-daemon , ssowat, metronome , rspamd (>= 1.2.0), rmilter (>=1.7.0), redis-server, opendkim-tools + , haveged Recommends: yunohost-admin , openssh-server, ntp, inetutils-ping | iputils-ping , bash-completion, rsyslog, etckeeper From 87bdc5a5ce46cc7c9c38c90444c558f2ca76c32f Mon Sep 17 00:00:00 2001 From: opi Date: Mon, 5 Dec 2016 10:45:55 +0100 Subject: [PATCH 119/208] [fix] Add missing dependency to nscd package #656 See https://dev.yunohost.org/issues/656 nscd is called during user_create and user_delete, but this package is only in Recommends for libnss-ldapd. --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 80f562e76..5fb816c61 100644 --- a/debian/control +++ b/debian/control @@ -17,7 +17,7 @@ Depends: ${python:Depends}, ${misc:Depends} , dnsutils, bind9utils, unzip, git, curl, cron , ca-certificates, netcat-openbsd, iproute , mariadb-server | mysql-server, php5-mysql | php5-mysqlnd - , slapd, ldap-utils, sudo-ldap, libnss-ldapd + , slapd, ldap-utils, sudo-ldap, libnss-ldapd, nscd , postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail , dovecot-ldap, dovecot-lmtpd, dovecot-managesieved , dovecot-antispam, fail2ban From 8bd130dc86eaf35239bdd362697f7d9f63abffa9 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 5 Dec 2016 18:35:47 +0100 Subject: [PATCH 120/208] [mod] remove unused variable (pyflake) --- src/yunohost/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3702eb1b4..153a13575 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -995,7 +995,6 @@ def app_ssowatconf(auth): redirected_regex = { main_domain +'/yunohost[\/]?$': 'https://'+ main_domain +'/yunohost/sso/' } redirected_urls ={} - apps = {} try: apps_list = app_list()['apps'] except: From 731ff6f906bea5ed9543ed49d0180e8360dc7fbd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 23 Nov 2016 19:36:57 -0500 Subject: [PATCH 121/208] [fix] Refactoring tools_maindomain and disabling removal of main domain to avoid breaking things --- data/actionsmap/yunohost.yml | 7 ++-- locales/en.json | 3 +- src/yunohost/domain.py | 14 ++++++++ src/yunohost/tools.py | 62 ++++++++++++++++-------------------- 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 26543721e..91c5b2aad 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1266,7 +1266,7 @@ tools: ### tools_maindomain() maindomain: - action_help: Main domain change tool + action_help: Check the current main domain, or change it api: - GET /domains/main - PUT /domains/main @@ -1274,12 +1274,9 @@ tools: authenticate: all lock: false arguments: - -o: - full: --old-domain - extra: - pattern: *pattern_domain -n: full: --new-domain + help: Change the current main domain extra: pattern: *pattern_domain diff --git a/locales/en.json b/locales/en.json index e44f87778..6d0ec34bd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -258,5 +258,6 @@ "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_cert_signing_failed" : "Signing the new certificate failed.", "certmanager_no_cert_file" : "Unable to read certificate file for domain {domain:s} (file : {file:s})", - "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_conflicting_nginx_file": "Unable to prepare domain for ACME challenge : the nginx configuration file {filepath:s} is conflicting and should be removed first.", + "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first" } diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 415585087..9c1563423 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -113,6 +113,7 @@ def domain_add(auth, domain, dyndns=False): raise MoulinetteError(errno.EINVAL, m18n.n('domain_dyndns_root_unknown')) + try: yunohost.certificate._certificate_install_selfsigned([domain], False) @@ -158,6 +159,10 @@ def domain_remove(auth, domain, force=False): if not force and domain not in domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) + # Check domain is not the main domain + if (domain == _get_maindomain()) : + raise MoulinetteError(errno.EINVAL, m18n.n('domain_cannot_remove_main')) + # Check if apps are installed on the domain for app in os.listdir('/etc/yunohost/apps/'): with open('/etc/yunohost/apps/' + app +'/settings.yml') as f: @@ -284,3 +289,12 @@ def get_public_ip(protocol=4): logger.debug('cannot retrieve public IPv%d' % protocol, exc_info=1) raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) + +def _get_maindomain(): + with open('/etc/yunohost/current_host', 'r') as f: + maindomain = f.readline().rstrip() + return maindomain + +def _set_maindomain(domain): + with open('/etc/yunohost/current_host', 'w') as f: + f.write(domain) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 475bf9b7f..51acc410d 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -39,7 +39,7 @@ import apt.progress from moulinette.core import MoulinetteError, init_authenticator from moulinette.utils.log import getActionLogger from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list -from yunohost.domain import domain_add, domain_list, get_public_ip +from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain from yunohost.dyndns import dyndns_subscribe from yunohost.firewall import firewall_upnp from yunohost.service import service_status, service_regen_conf, service_log @@ -127,50 +127,43 @@ def tools_adminpw(auth, new_password): def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False): """ - Main domain change tool + Main domain consultaton or change tool Keyword argument: - new_domain - old_domain + new_domain -- The new domain to be set as the main domain """ - if not old_domain: - with open('/etc/yunohost/current_host', 'r') as f: - old_domain = f.readline().rstrip() - - if not new_domain: - return { 'current_main_domain': old_domain } + # If no new domain specified, we return the current main domain if not new_domain: - raise MoulinetteError(errno.EINVAL, m18n.n('new_domain_required')) + return { 'current_main_domain': _get_maindomain() } + + # Check domain exists if new_domain not in domain_list(auth)['domains']: - domain_add(auth, new_domain) + raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) - os.system('rm /etc/ssl/private/yunohost_key.pem') - os.system('rm /etc/ssl/certs/yunohost_crt.pem') + # Apply changes to ssl certs + ssl_key = "/etc/ssl/private/yunohost_key.pem" + ssl_crt = "/etc/ssl/private/yunohost_crt.pem" + new_ssl_key = "/etc/yunohost/certs/%s/key.pem" % new_domain + new_ssl_crt = "/etc/yunohost/certs/%s/crt.pem" % new_domain - command_list = [ - 'ln -s /etc/yunohost/certs/%s/key.pem /etc/ssl/private/yunohost_key.pem' % new_domain, - 'ln -s /etc/yunohost/certs/%s/crt.pem /etc/ssl/certs/yunohost_crt.pem' % new_domain, - 'echo %s > /etc/yunohost/current_host' % new_domain, - ] + try: - for command in command_list: - if os.system(command) != 0: - raise MoulinetteError(errno.EPERM, - m18n.n('maindomain_change_failed')) + if os.path.exists(ssl_key) or os.path.lexists(ssl_key) : + os.remove(ssl_key) + if os.path.exists(ssl_crt) or os.path.lexists(ssl_crt) : + os.remove(ssl_crt) - if dyndns and len(new_domain.split('.')) >= 3: - try: - r = requests.get('https://dyndns.yunohost.org/domains') - except requests.ConnectionError: - pass - else: - dyndomains = json.loads(r.text) - dyndomain = '.'.join(new_domain.split('.')[1:]) - if dyndomain in dyndomains: - dyndns_subscribe(domain=new_domain) + os.symlink(new_ssl_key, ssl_key) + os.symlink(new_ssl_crt, ssl_crt) + _set_maindomain(new_domain) + except Exception as e: + print e + raise MoulinetteError(errno.EPERM, m18n.n('maindomain_change_failed')) + + # Regen configurations try: with open('/etc/yunohost/installed', 'r') as f: service_regen_conf() @@ -285,7 +278,8 @@ def tools_postinstall(domain, password, ignore_dyndns=False): m18n.n('yunohost_ca_creation_failed')) # New domain config - tools_maindomain(auth, old_domain='yunohost.org', new_domain=domain, dyndns=dyndns) + domain_add(auth, new_domain, dyndns) + tools_maindomain(auth, old_domain='yunohost.org', new_domain=domain) # Generate SSOwat configuration file app_ssowatconf(auth) From 597da8be465309356b7b16f9766ba8f3449d924c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 25 Nov 2016 03:03:41 +0100 Subject: [PATCH 122/208] Fixing previous commit after postinstall tests --- src/yunohost/tools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 51acc410d..8e1603d83 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -125,7 +125,7 @@ def tools_adminpw(auth, new_password): logger.success(m18n.n('admin_password_changed')) -def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False): +def tools_maindomain(auth, new_domain=None): """ Main domain consultaton or change tool @@ -278,8 +278,8 @@ def tools_postinstall(domain, password, ignore_dyndns=False): m18n.n('yunohost_ca_creation_failed')) # New domain config - domain_add(auth, new_domain, dyndns) - tools_maindomain(auth, old_domain='yunohost.org', new_domain=domain) + domain_add(auth, domain, dyndns) + tools_maindomain(auth, domain) # Generate SSOwat configuration file app_ssowatconf(auth) From 9c798e5d62c0230823c3b1a317974ce32a79ebd6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Dec 2016 23:40:37 -0500 Subject: [PATCH 123/208] [fix] Fixing message --- src/yunohost/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 8e1603d83..d8b5f7f7e 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -127,7 +127,7 @@ def tools_adminpw(auth, new_password): def tools_maindomain(auth, new_domain=None): """ - Main domain consultaton or change tool + Check the current main domain, or change it Keyword argument: new_domain -- The new domain to be set as the main domain From c468a6975446a839ad1e3c12acbf82494959fae8 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 2 Dec 2016 23:23:06 +0100 Subject: [PATCH 124/208] [mod] pep8 --- src/yunohost/domain.py | 3 ++- src/yunohost/tools.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 9c1563423..1aeab33ef 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -160,7 +160,7 @@ def domain_remove(auth, domain, force=False): raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) # Check domain is not the main domain - if (domain == _get_maindomain()) : + if domain == _get_maindomain(): raise MoulinetteError(errno.EINVAL, m18n.n('domain_cannot_remove_main')) # Check if apps are installed on the domain @@ -290,6 +290,7 @@ def get_public_ip(protocol=4): raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) + def _get_maindomain(): with open('/etc/yunohost/current_host', 'r') as f: maindomain = f.readline().rstrip() diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index d8b5f7f7e..2c1c55d77 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -136,7 +136,7 @@ def tools_maindomain(auth, new_domain=None): # If no new domain specified, we return the current main domain if not new_domain: - return { 'current_main_domain': _get_maindomain() } + return {'current_main_domain': _get_maindomain()} # Check domain exists if new_domain not in domain_list(auth)['domains']: @@ -149,7 +149,6 @@ def tools_maindomain(auth, new_domain=None): new_ssl_crt = "/etc/yunohost/certs/%s/crt.pem" % new_domain try: - if os.path.exists(ssl_key) or os.path.lexists(ssl_key) : os.remove(ssl_key) if os.path.exists(ssl_crt) or os.path.lexists(ssl_crt) : From c763b74fea5db85a92daf8c5c5e11598b42abc90 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 2 Dec 2016 23:23:16 +0100 Subject: [PATCH 125/208] [mod] use logger instead of print, a bit better bot not perfect --- src/yunohost/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 2c1c55d77..bd3bb9696 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -159,7 +159,7 @@ def tools_maindomain(auth, new_domain=None): _set_maindomain(new_domain) except Exception as e: - print e + logger.warning("%s" % e, exc_info=1) raise MoulinetteError(errno.EPERM, m18n.n('maindomain_change_failed')) # Regen configurations From 801ceca32b26d07be9a7e976518538bf4419c6c2 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 2 Dec 2016 23:50:22 +0100 Subject: [PATCH 126/208] [mod] pep8 --- 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 bd3bb9696..7cc221683 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -149,9 +149,9 @@ def tools_maindomain(auth, new_domain=None): new_ssl_crt = "/etc/yunohost/certs/%s/crt.pem" % new_domain try: - if os.path.exists(ssl_key) or os.path.lexists(ssl_key) : + if os.path.exists(ssl_key) or os.path.lexists(ssl_key): os.remove(ssl_key) - if os.path.exists(ssl_crt) or os.path.lexists(ssl_crt) : + if os.path.exists(ssl_crt) or os.path.lexists(ssl_crt): os.remove(ssl_crt) os.symlink(new_ssl_key, ssl_key) From d85657a5c60afc8c9387e830d8250dd0e950dd18 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 7 Dec 2016 21:54:13 +0100 Subject: [PATCH 127/208] [mod] style --- src/yunohost/user.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 348a23d32..6c73c5ba0 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -399,30 +399,38 @@ def user_info(auth, username): if 'mailuserquota' in user: userquota = user['mailuserquota'][0] - if isinstance( userquota, int ): + + if isinstance(userquota, int): userquota = str(userquota) # Test if userquota is '0' or '0M' ( quota pattern is ^(\d+[bkMGT])|0$ ) is_limited = not re.match('0[bkMGT]?', userquota) storage_use = '?' + cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] + try: - cmd_result = subprocess.check_output(cmd,stderr=subprocess.STDOUT, + cmd_result = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) # Exemple of return value for cmd: # """Quota name=User quota Type=STORAGE Value=0 Limit=- %=0 # Quota name=User quota Type=MESSAGE Value=0 Limit=- %=0""" - has_value=re.search(r'Value=(\d+)', cmd_result) + has_value = re.search(r'Value=(\d+)', cmd_result) + if has_value: storage_use = int(has_value.group(1)) storage_use = _convertSize(storage_use) + if is_limited: - has_percent=re.search(r'%=(\d+)', cmd_result) + has_percent = re.search(r'%=(\d+)', cmd_result) + if has_percent: percentage = int(has_percent.group(1)) - storage_use += ' (%s%s)' % (percentage, '%') + storage_use += ' (%s%%)' % percentage + except subprocess.CalledProcessError: logger.warning(m18n.n('mailbox_used_space_dovecot_down')) + result_dict['mailbox-quota'] = { 'limit' : userquota if is_limited else m18n.n('unlimit'), 'use' : storage_use From 6aa64a071ffd4cf16447ee1ed9f492a5906315ea Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 7 Dec 2016 16:14:01 -0500 Subject: [PATCH 128/208] Improving exception handling --- locales/en.json | 3 ++- src/yunohost/certificate.py | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/locales/en.json b/locales/en.json index 806def22a..7de00f395 100644 --- a/locales/en.json +++ b/locales/en.json @@ -256,5 +256,6 @@ "certmanager_cert_signing_failed" : "Signing the new certificate failed.", "certmanager_no_cert_file" : "Unable to read certificate file for domain {domain:s} (file : {file:s})", "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_unable_to_determine_self_CA_name": "Unable to determine name of self-signing authority." + "certmanager_self_ca_conf_file_not_found" : "Configuration file not found for self-signing CA ({file:s})", + "certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority in {file:s}." } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 74e93314c..8209160a4 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -812,17 +812,18 @@ def _domain_is_accessible_through_HTTP(ip, domain): def _name_self_CA(): ca_conf = os.path.join(SSL_DIR, "openssl.ca.cnf") - try : - with open(ca_conf) as f: - lines = f.readlines() + if not os.path.exists(ca_conf) : + logger.warning(m18n.n('certmanager_self_ca_conf_file_not_found', file=ca_conf)) + return "" - for line in lines: - if line.startswith("commonName_default"): - return line.split()[2] - except: - pass + with open(ca_conf) as f: + lines = f.readlines() - logger.warning(m18n.n('certmanager_unable_to_determine_self_CA_name')) + for line in lines: + if line.startswith("commonName_default"): + return line.split()[2] + + logger.warning(m18n.n('certmanager_unable_to_parse_self_CA_name', file=ca_conf)) return "" From 1b6d8b64637bbebf630139315c5674e5733b57ad Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 9 Dec 2016 00:50:28 +0100 Subject: [PATCH 129/208] [mod] pep8 --- src/yunohost/backup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 0e991e8fc..4aec59598 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -623,15 +623,16 @@ def backup_info(name, with_details=False, human_readable=False): m18n.n('backup_archive_name_unknown', name=name)) # If symlink, retrieve the real path - if os.path.islink(archive_file) : + if os.path.islink(archive_file): archive_file = os.path.realpath(archive_file) # Raise exception if link is broken (e.g. on unmounted external storage) - if (not os.path.exists(archive_file)) : + if not os.path.exists(archive_file): raise MoulinetteError(errno.EIO, m18n.n('backup_archive_broken_link', path=archive_file)) info_file = "%s/%s.info.json" % (archives_path, name) + try: with open(info_file) as f: # Retrieve backup info From 0d6cf499a0b858e7009245fd11a9210188047c13 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Fri, 9 Dec 2016 07:34:31 +0100 Subject: [PATCH 130/208] [fix] Bracket in passwd from ynh_string_random With bracket we get string like "3KFEc[xm4[udnZ6pDJ8LvP0n" Without "El0nMO3thacDwrjeRzgKnb30" --- 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 a2bf0d463..1a848d239 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -6,6 +6,6 @@ # | arg: length - the string length to generate (default: 24) ynh_string_random() { dd if=/dev/urandom bs=1 count=200 2> /dev/null \ - | tr -c -d '[A-Za-z0-9]' \ + | tr -c -d 'A-Za-z0-9' \ | sed -n 's/\(.\{'"${1:-24}"'\}\).*/\1/p' } From ba1357fb949c9a24fc25faf0ecf56695a837ac4d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 11 Dec 2016 14:15:39 +0100 Subject: [PATCH 131/208] [mod] uniformise strings following jibec comment --- locales/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 587dd34ae..d996a5c7a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -257,9 +257,9 @@ "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_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_cert_signing_failed" : "Signing the new certificate failed.", - "certmanager_no_cert_file" : "Unable to read certificate file for domain {domain:s} (file : {file:s})", + "certmanager_no_cert_file" : "Unable to read certificate file for domain {domain:s} (file: {file:s})", "certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge : the nginx configuration file {filepath:s} is conflicting and should be removed first.", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", - "certmanager_self_ca_conf_file_not_found" : "Configuration file not found for self-signing CA ({file:s})", - "certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority in {file:s}." + "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})" } From c9bd7e86332c12dcb15ab7b44212343f1bbc4d96 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 11 Dec 2016 14:18:58 +0100 Subject: [PATCH 132/208] [mod] english typography/json format --- locales/en.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/locales/en.json b/locales/en.json index d996a5c7a..977ea26c1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -241,25 +241,25 @@ "yunohost_installing": "Installing YunoHost...", "yunohost_not_installed": "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'.", "domain_cert_gen_failed": "Unable to generate certificate", - "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_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", "certmanager_domain_unknown": "Unknown domain {domain:s}", - "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_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use --force)", "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow...", - "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_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_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_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_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_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_selfsigned" : "Successfully installed a self-signed certificate for domain {domain:s} !", - "certmanager_cert_install_success" : "Successfully installed Let's Encrypt certificate for domain {domain:s} !", - "certmanager_cert_renew_success" : "Successfully renewed Let's Encrypt certificate for domain {domain: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_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_cert_signing_failed" : "Signing the new certificate failed.", - "certmanager_no_cert_file" : "Unable to read certificate file for domain {domain:s} (file: {file:s})", - "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_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_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_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_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", + "certmanager_cert_install_success": "Successfully installed Let's Encrypt certificate for domain {domain:s}!", + "certmanager_cert_renew_success": "Successfully renewed Let's Encrypt certificate for domain {domain: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_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_cert_signing_failed": "Signing the new certificate failed.", + "certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})", + "certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge: the nginx configuration file {filepath:s} is conflicting and should be removed first.", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", - "certmanager_self_ca_conf_file_not_found" : "Configuration file not found for self-signing authority (file: {file:s})", + "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})" } From fb8394a68e5800000fecb758591e260c89eb3721 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 11 Dec 2016 14:21:20 +0100 Subject: [PATCH 133/208] [mod] remove '.' at the end of a string --- locales/en.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/locales/en.json b/locales/en.json index 977ea26c1..b7ecf2325 100644 --- a/locales/en.json +++ b/locales/en.json @@ -57,7 +57,7 @@ "backup_hook_unknown": "Backup hook '{hook:s}' unknown", "backup_invalid_archive": "Invalid backup archive", "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_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_running_app_script": "Running backup script of app '{app:s}'...", @@ -79,11 +79,11 @@ "domain_dyndns_invalid": "Invalid domain to use with DynDNS", "domain_dyndns_root_unknown": "Unknown DynDNS root domain", "domain_exists": "Domain already exists", - "domain_uninstall_app_first": "One or more apps are installed on this domain. Please uninstall them before proceeding to domain removal.", + "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}", - "done": "Done.", + "done": "Done", "downloading": "Downloading...", "dyndns_cron_installed": "The DynDNS cron job has been installed", "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job", @@ -102,7 +102,7 @@ "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.", + "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", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn’t terminated: {path:s}", @@ -110,10 +110,10 @@ "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation complete", "installation_failed": "Installation failed", - "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.", + "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_initialized": "LDAP has been initialized", - "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user.", + "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user", "license_undefined": "undefined", "mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'", "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", @@ -209,8 +209,8 @@ "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.", + "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", @@ -239,7 +239,7 @@ "yunohost_ca_creation_failed": "Unable to create certificate authority", "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'.", + "yunohost_not_installed": "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'", "domain_cert_gen_failed": "Unable to generate certificate", "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_domain_unknown": "Unknown domain {domain:s}", @@ -247,18 +247,18 @@ "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow...", "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_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_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_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_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_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_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", "certmanager_cert_install_success": "Successfully installed Let's Encrypt certificate for domain {domain:s}!", "certmanager_cert_renew_success": "Successfully renewed Let's Encrypt certificate for domain {domain: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_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_cert_signing_failed": "Signing the new certificate failed.", + "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_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_cert_signing_failed": "Signing the new certificate failed", "certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})", - "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_conflicting_nginx_file": "Unable to prepare domain for ACME challenge: the nginx configuration file {filepath:s} is conflicting and should be removed first", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "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})" From 3464415a77a3feb4e8a934fe6ec15aceec4c2b94 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 11 Dec 2016 15:34:33 +0100 Subject: [PATCH 134/208] Update changelog for 2.5.1 release --- debian/changelog | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/debian/changelog b/debian/changelog index 7467b4ff2..34d883ca1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,22 @@ +yunohost (2.5.1) testing; urgency=low + + * [fix] Raise error on malformed SSOwat persistent conf. + * [enh] Catch SSOwat persistent configuration write error. + * [fix] Write SSOwat configuration file only if needed. + * [enh] Display full exception error message. + * [enh] cli option to avoid removing an application on installation failure + * [mod] give instructions on how to solve the conf.json.persistant parsing error + * [fix] avoid random bug on post-install due to nscd cache + * [enh] Adding check that user is actually created + minor refactor of ldap/auth init + * [fix] Fix the way name of self-CA is determined + * [fix] Add missing dependency to nscd package #656 + * [fix] Refactoring tools_maindomain and disabling removal of main domain to avoid breaking things + * [fix] Bracket in passwd from ynh_string_random + + Thanks to all contributors: Aleks, Bram, ju, jibec, ljf, M5oul, opi + + -- Laurent Peuch Sun, 11 Dec 2016 15:26:21 +0100 + yunohost (2.5.0) testing; urgency=low * Certificate management integration (e.g. Let's Encrypt certificate install) From 4897c39edfcdb2e30e8855fbc17b31966c33cb8c Mon Sep 17 00:00:00 2001 From: Moul Date: Sun, 11 Dec 2016 18:14:42 +0100 Subject: [PATCH 135/208] =?UTF-8?q?[fix]=20[#662](https://dev.yunohost.org?= =?UTF-8?q?/issues/662):=C2=A0missing=20'python-openssl'=20dependency=20fo?= =?UTF-8?q?r=20Let's=20Encrypt=20integration.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 5fb816c61..480cde40b 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Package: yunohost Architecture: all Depends: ${python:Depends}, ${misc:Depends} , moulinette (>= 2.3.5.1) - , python-psutil, python-requests, python-dnspython + , python-psutil, python-requests, python-dnspython, python-openssl , python-apt, python-miniupnpc , glances , dnsutils, bind9utils, unzip, git, curl, cron From 71672d371e1626955afd9fdb796b23dc77fd2a30 Mon Sep 17 00:00:00 2001 From: opi Date: Sun, 11 Dec 2016 22:58:56 +0100 Subject: [PATCH 136/208] [fix] --no-remove-on-failure for app install should behave as a flag. --- data/actionsmap/yunohost.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 91c5b2aad..25eb6cf6d 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -502,6 +502,7 @@ app: -n: full: --no-remove-on-failure help: Debug option to avoid removing the app on a failed installation + action: store_true ### app_remove() TODO: Write help remove: From f6648f360cf7d7088cba04f4fac996ce10be1789 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 5 Dec 2016 18:44:31 +0100 Subject: [PATCH 137/208] [fix] don't remove trailing char if it's not a slash --- src/yunohost/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 153a13575..95146ad64 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1011,19 +1011,19 @@ def app_ssowatconf(auth): for item in _get_setting(app_settings, 'skipped_uris'): if item[-1:] == '/': item = item[:-1] - skipped_urls.append(app_settings['domain'] + app_settings['path'][:-1] + item) + skipped_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item) for item in _get_setting(app_settings, 'skipped_regex'): skipped_regex.append(item) for item in _get_setting(app_settings, 'unprotected_uris'): if item[-1:] == '/': item = item[:-1] - unprotected_urls.append(app_settings['domain'] + app_settings['path'][:-1] + item) + unprotected_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item) for item in _get_setting(app_settings, 'unprotected_regex'): unprotected_regex.append(item) for item in _get_setting(app_settings, 'protected_uris'): if item[-1:] == '/': item = item[:-1] - protected_urls.append(app_settings['domain'] + app_settings['path'][:-1] + item) + protected_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item) for item in _get_setting(app_settings, 'protected_regex'): protected_regex.append(item) if 'redirected_urls' in app_settings: From 0e2cae4f1b473b6734fda28066b80ce38c884c7c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 11 Dec 2016 17:59:39 -0500 Subject: [PATCH 138/208] Use service_status to check if dovecot is running --- src/yunohost/user.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 6c73c5ba0..65cfddae2 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -35,6 +35,7 @@ import re from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger +from yunohost.service import service_status logger = getActionLogger('yunohost.user') @@ -407,9 +408,10 @@ def user_info(auth, username): is_limited = not re.match('0[bkMGT]?', userquota) storage_use = '?' - cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] - - try: + if (service_status("dovecot")["status"] != "running"): + logger.warning(m18n.n('mailbox_used_space_dovecot_down')) + else: + cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] cmd_result = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) # Exemple of return value for cmd: @@ -428,9 +430,6 @@ def user_info(auth, username): percentage = int(has_percent.group(1)) storage_use += ' (%s%%)' % percentage - except subprocess.CalledProcessError: - logger.warning(m18n.n('mailbox_used_space_dovecot_down')) - result_dict['mailbox-quota'] = { 'limit' : userquota if is_limited else m18n.n('unlimit'), 'use' : storage_use From 6f313d943bb7ce47598b9c61336f3bc34d967966 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 12 Dec 2016 00:14:22 +0100 Subject: [PATCH 139/208] [mod] pep8 --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 65cfddae2..08ab4c649 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -408,7 +408,7 @@ def user_info(auth, username): is_limited = not re.match('0[bkMGT]?', userquota) storage_use = '?' - if (service_status("dovecot")["status"] != "running"): + if service_status("dovecot")["status"] != "running": logger.warning(m18n.n('mailbox_used_space_dovecot_down')) else: cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] From 656bb809d137fb5e4613d1ad767a9af7db28b0f3 Mon Sep 17 00:00:00 2001 From: Moul Date: Mon, 12 Dec 2016 00:22:50 +0100 Subject: [PATCH 140/208] [mod] certificate: pep8. --- 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 8209160a4..04d755692 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -812,7 +812,7 @@ def _domain_is_accessible_through_HTTP(ip, domain): def _name_self_CA(): ca_conf = os.path.join(SSL_DIR, "openssl.ca.cnf") - if not os.path.exists(ca_conf) : + if not os.path.exists(ca_conf): logger.warning(m18n.n('certmanager_self_ca_conf_file_not_found', file=ca_conf)) return "" From 64af6a1dbf6ad61bd7c5e19c49fe9721a53f5aa8 Mon Sep 17 00:00:00 2001 From: likeitneverwentaway Date: Mon, 12 Dec 2016 09:03:50 +0100 Subject: [PATCH 141/208] [enh] List available domains when installing an app by CLI. --- locales/en.json | 1 + src/yunohost/app.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index dfb8ffa4f..a8a55dd26 100644 --- a/locales/en.json +++ b/locales/en.json @@ -84,6 +84,7 @@ "domain_zone_exists": "DNS zone file already exists", "domain_zone_not_found": "DNS zone file not found for domain {:s}", "done": "Done", + "domains_available": "Available domains:", "downloading": "Downloading...", "dyndns_cron_installed": "The DynDNS cron job has been installed", "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 95146ad64..85693a0dd 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1497,7 +1497,7 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): args -- A dictionnary of arguments to parse """ - from yunohost.domain import domain_list + from yunohost.domain import domain_list, _get_maindomain from yunohost.user import user_info args_dict = OrderedDict() @@ -1537,6 +1537,13 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): # Check for a password argument is_password = True if arg_type == 'password' else False + if arg_type == 'domain': + arg_default = _get_maindomain() + ask_string += ' (default: {0})'.format(arg_default) + msignals.display(m18n.n('domains_available')) + for domain in domain_list(auth)['domains']: + msignals.display("- {}".format(domain)) + try: input_string = msignals.prompt(ask_string, is_password) except NotImplementedError: From a8a682ac7e2ac4bacafccc98f83e3b1c046c1726 Mon Sep 17 00:00:00 2001 From: Moul Date: Mon, 12 Dec 2016 19:18:03 +0100 Subject: [PATCH 142/208] [enh] add empty file for esperanto to enable it on weblate --- locales/eo.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/eo.json diff --git a/locales/eo.json b/locales/eo.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/eo.json @@ -0,0 +1 @@ +{} From 7717c58de4c8cda615165d84a12a30dd73eea58a Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 25 Nov 2016 00:15:03 +0100 Subject: [PATCH 143/208] Revert "[fix] Can't restore app on a root domain" --- src/yunohost/app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 95146ad64..b6657539e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -917,6 +917,10 @@ def app_checkurl(auth, url, app=None): raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) if domain in apps_map: + # Domain already has apps on sub path + if path == '/': + raise MoulinetteError(errno.EPERM, + m18n.n('app_location_install_failed')) # Loop through apps for p, a in apps_map[domain].items(): # Skip requested app checking @@ -926,7 +930,7 @@ def app_checkurl(auth, url, app=None): if path == p: raise MoulinetteError(errno.EINVAL, m18n.n('app_location_already_used')) - elif path.startswith(p) or p.startswith(path): + elif path.startswith(p): raise MoulinetteError(errno.EPERM, m18n.n('app_location_install_failed')) From 67cd9d9b22390579c3fa84439856d2a02c86879c Mon Sep 17 00:00:00 2001 From: Philip Gatzka Date: Fri, 29 Jul 2016 11:05:25 +0200 Subject: [PATCH 144/208] [i18n] Translated using Weblate (German) Currently translated at 63.5% (152 of 239 strings) --- locales/de.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 1331c56b4..0fff3e0b4 100644 --- a/locales/de.json +++ b/locales/de.json @@ -209,5 +209,14 @@ "yunohost_ca_creation_failed": "Zertifikatsstelle konnte nicht erstellt werden", "yunohost_configured": "YunoHost wurde erfolgreich konfiguriert", "yunohost_installing": "YunoHost wird installiert...", - "yunohost_not_installed": "Die YunoHost ist unvollständig. Bitte 'yunohost tools postinstall' ausführen." + "yunohost_not_installed": "Die YunoHost ist unvollständig. Bitte 'yunohost tools postinstall' ausführen.", + "app_not_properly_removed": "{app:s} wurde nicht ordnungsgemäß entfernt", + "service_regenconf_failed": "Konnte die Konfiguration für folgende Dienste nicht neu erzeugen: {services}", + "not_enough_disk_space": "Zu wenig Speicherplatz unter '{path:s}' verfügbar", + "backup_creation_failed": "Erzeugung des Backups fehlgeschlagen", + "service_conf_up_to_date": "Die Konfiguration für den Dienst '{service}' ist bereits aktuell", + "package_not_installed": "Paket '{pkgname}' ist nicht installiert", + "pattern_positive_number": "Muss eine positive Zahl sein", + "diagnostic_kernel_version_error": "Kann Kernelversion nicht abrufen: {error}", + "package_unexpected_error": "Ein unerwarteter Fehler trat bei der Verarbeitung des Pakets '{pkgname}' auf" } From 8c1c5f6ce85db2fc40888ff76f8f64c512191d71 Mon Sep 17 00:00:00 2001 From: Anmol Date: Fri, 29 Jul 2016 13:58:09 +0200 Subject: [PATCH 145/208] [i18n] Translated using Weblate (Hindi) Currently translated at 4.1% (10 of 239 strings) --- locales/hi.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/locales/hi.json b/locales/hi.json index 0967ef424..e202d8378 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -1 +1,12 @@ -{} +{ + "action_invalid": "अवैध कार्रवाई '{action:s}'", + "admin_password": "व्यवस्थापक पासवर्ड", + "admin_password_change_failed": "पासवर्ड बदलने में असमर्थ", + "admin_password_changed": "व्यवस्थापक पासवर्ड बदल दिया गया है", + "app_already_installed": "'{app:s}' पहले से ही इंस्टाल्ड है", + "app_argument_choice_invalid": "गलत तर्क का चयन किया गया '{name:s}' , तर्क इन विकल्पों में से होने चाहिए {choices:s}", + "app_argument_invalid": "तर्क के लिए अमान्य मान '{name:s}': {error:s}", + "app_argument_required": "तर्क '{name:s}' की आवश्यकता है", + "app_extraction_failed": "इन्सटाल्ड फ़ाइलों को निकालने में असमर्थ", + "app_id_invalid": "अवैध एप्लिकेशन id" +} From 5a12996017ba087402510cf49b18111a379d2e9a Mon Sep 17 00:00:00 2001 From: Anmol Date: Tue, 2 Aug 2016 22:31:47 +0200 Subject: [PATCH 146/208] [i18n] Translated using Weblate (Hindi) Currently translated at 33.0% (79 of 239 strings) --- locales/hi.json | 71 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/locales/hi.json b/locales/hi.json index e202d8378..2ed328282 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -8,5 +8,74 @@ "app_argument_invalid": "तर्क के लिए अमान्य मान '{name:s}': {error:s}", "app_argument_required": "तर्क '{name:s}' की आवश्यकता है", "app_extraction_failed": "इन्सटाल्ड फ़ाइलों को निकालने में असमर्थ", - "app_id_invalid": "अवैध एप्लिकेशन id" + "app_id_invalid": "अवैध एप्लिकेशन id", + "app_incompatible": "यह एप्लिकेशन युनोहोस्ट की इस वर्जन के लिए नहीं है", + "app_install_files_invalid": "फाइलों की अमान्य स्थापना", + "app_location_already_used": "इस लोकेशन पे पहले से ही कोई एप्लीकेशन इन्सटाल्ड है", + "app_location_install_failed": "इस लोकेशन पे एप्लीकेशन इंस्टाल करने में असमर्थ", + "app_manifest_invalid": "एप्लीकेशन का मैनिफेस्ट अमान्य", + "app_no_upgrade": "कोई भी एप्लीकेशन को अपडेट की जरूरत नहीं", + "app_not_correctly_installed": "{app:s} ठीक ढंग से इनस्टॉल नहीं हुई", + "app_not_installed": "{app:s} इनस्टॉल नहीं हुई", + "app_not_properly_removed": "{app:s} ठीक ढंग से नहीं अनइन्सटॉल की गई", + "app_package_need_update": "इस एप्लीकेशन पैकेज को युनोहोस्ट के नए बदलावों/गाइडलिनेज़ के कारण उपडटेशन की जरूरत", + "app_removed": "{app:s} को अनइन्सटॉल कर दिया गया", + "app_requirements_checking": "जरूरी पैकेजेज़ की जाँच हो रही है ....", + "app_requirements_failed": "आवश्यकताओं को पूरा करने में असमर्थ: {error}", + "app_requirements_unmeet": "आवश्यकताए पूरी नहीं हो सकी, पैकेज {pkgname}({version})यह होना चाहिए {spec}", + "app_sources_fetch_failed": "सोर्स फाइल्स प्राप्त करने में असमर्थ", + "app_unknown": "अनजान एप्लीकेशन", + "app_unsupported_remote_type": "एप्लीकेशन के लिए उन्सुपपोर्टेड रिमोट टाइप इस्तेमाल किया गया", + "app_upgrade_failed": "{app:s} अपडेट करने में असमर्थ", + "app_upgraded": "{app:s} अपडेट हो गयी हैं", + "appslist_fetched": "एप्लीकेशन की सूचि अपडेट हो गयी", + "appslist_removed": "एप्लीकेशन की सूचि निकल दी गयी है", + "appslist_retrieve_error": "दूरस्थ एप्लिकेशन सूची प्राप्त करने में असमर्थ", + "appslist_unknown": "अनजान एप्लिकेशन सूची", + "ask_current_admin_password": "वर्तमान व्यवस्थापक पासवर्ड", + "ask_email": "ईमेल का पता", + "ask_firstname": "नाम", + "ask_lastname": "अंतिम नाम", + "ask_list_to_remove": "सूचि जिसको हटाना है", + "ask_main_domain": "मुख्य डोमेन", + "ask_new_admin_password": "नया व्यवस्थापक पासवर्ड", + "ask_password": "पासवर्ड", + "backup_action_required": "आप को सेव करने के लिए कुछ लिखना होगा", + "backup_app_failed": "एप्लीकेशन का बैकअप करने में असमर्थ '{app:s}'", + "backup_archive_app_not_found": "'{app:s}' बैकअप आरचिव में नहीं मिला", + "backup_archive_hook_not_exec": "हुक '{hook:s}' इस बैकअप में एक्सेक्युट नहीं किया गया", + "backup_archive_name_exists": "इस बैकअप आरचिव का नाम पहले से ही मौजूद है", + "backup_archive_name_unknown": "'{name:s}' इस नाम की लोकल बैकअप आरचिव मौजूद नहीं", + "backup_archive_open_failed": "बैकअप आरचिव को खोलने में असमर्थ", + "backup_cleaning_failed": "टेम्पोरेरी बैकअप डायरेक्टरी को उड़ने में असमर्थ", + "backup_created": "बैकअप सफलतापूर्वक किया गया", + "backup_creating_archive": "बैकअप आरचिव बनाई जा रही है ...", + "backup_creation_failed": "बैकअप बनाने में विफल", + "backup_delete_error": "'{path:s}' डिलीट करने में असमर्थ", + "backup_deleted": "इस बैकअप को डिलीट दिया गया है", + "backup_extracting_archive": "बैकअप आरचिव को एक्सट्रेक्ट किया जा रहा है ...", + "backup_hook_unknown": "'{hook:s}' यह बैकअप हुक नहीं मिला", + "backup_invalid_archive": "अवैध बैकअप आरचिव", + "backup_nothings_done": "सेव करने के लिए कुछ नहीं", + "backup_output_directory_forbidden": "निषिद्ध आउटपुट डायरेक्टरी। निम्न दिए गए डायरेक्टरी में बैकअप नहीं बन सकता /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var और /home/yunohost.backup/archives के सब-फोल्डर।", + "backup_output_directory_not_empty": "आउटपुट डायरेक्टरी खाली नहीं है", + "backup_output_directory_required": "बैकअप करने के लिए आउट पुट डायरेक्टरी की आवश्यकता है", + "backup_running_app_script": "'{app:s}' एप्लीकेशन की बैकअप स्क्रिप्ट चल रही है...", + "backup_running_hooks": "बैकअप हुक्स चल रहे है...", + "custom_app_url_required": "आप को अपनी कस्टम एप्लिकेशन '{app:s}' को अपग्रेड करने के लिए यूआरएल(URL) देने की आवश्यकता है", + "custom_appslist_name_required": "आप को अपनी कस्टम एप्लीकेशन के लिए नाम देने की आवश्यकता है", + "diagnostic_debian_version_error": "डेबियन वर्जन प्राप्त करने में असफलता {error}", + "diagnostic_kernel_version_error": "कर्नेल वर्जन प्राप्त नहीं की जा पा रही : {error}", + "diagnostic_monitor_disk_error": "डिस्क की मॉनिटरिंग नहीं की जा पा रही: {error}", + "diagnostic_monitor_network_error": "नेटवर्क की मॉनिटरिंग नहीं की जा पा रही: {error}", + "diagnostic_monitor_system_error": "सिस्टम की मॉनिटरिंग नहीं की जा पा रही: {error}", + "diagnostic_no_apps": "कोई एप्लीकेशन इन्सटाल्ड नहीं है", + "dnsmasq_isnt_installed": "dnsmasq इन्सटाल्ड नहीं लगता,इनस्टॉल करने के लिए किप्या ये कमांड चलाये 'apt-get remove bind9 && apt-get install dnsmasq'", + "domain_cert_gen_failed": "सर्टिफिकेट उत्पन करने में असमर्थ", + "domain_created": "डोमेन बनाया गया", + "domain_creation_failed": "डोमेन बनाने में असमर्थ", + "domain_deleted": "डोमेन डिलीट कर दिया गया है", + "domain_deletion_failed": "डोमेन डिलीट करने में असमर्थ", + "domain_dyndns_already_subscribed": "DynDNS डोमेन पहले ही सब्स्क्राइड है", + "domain_dyndns_invalid": "DynDNS के साथ इनवैलिड डोमिन इस्तेमाल किया गया" } From d22f0012355d42e1bd22a503ab5cbd5499302975 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Tue, 29 Nov 2016 22:25:36 +0100 Subject: [PATCH 147/208] [i18n] Translated using Weblate (French) Currently translated at 99.6% (256 of 257 strings) --- locales/fr.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 7898de57f..6017e3737 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -248,5 +248,22 @@ "yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification", "yunohost_configured": "YunoHost a été configuré", "yunohost_installing": "Installation de YunoHost...", - "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall »." + "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall ».", + "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner)", + "certmanager_domain_unknown": "Domaine inconnu {domain:s}", + "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force)", + "certmanager_certificate_fetching_or_enabling_failed": "Il semble que l'activation du nouveau certificat pour {domain:s} a échoué…", + "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain:s} n’est pas fourni par Let’s Encrypt. Impossible de le renouveler automatiquement !", + "certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} est sur le point d’expirer ! Utilisez --force pour contourner", + "certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} n’est pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et nginx sont correctes.", + "certmanager_error_no_A_record": "Aucun enregistrement DNS « A » n’a été trouvé pour {domain:s}. De devez faire pointer votre nom de domaine vers votre machine pour être capable d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", + "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS « A » du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez modifié récemment votre enregistrement « A », veuillez attendre sa propagation (quelques vérificateur de propagation sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", + "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), cause : {reason:s}", + "certmanager_cert_install_success_selfsigned": "Installation avec succès d’un certificat auto-signé pour le domaine {domain:s} !", + "certmanager_cert_install_success": "Installation avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", + "certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", + "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés.", + "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué.", + "certmanager_no_cert_file": "Impossible de lire le fichier de certificat pour le domaine {domain:s} (fichier : {file:s})", + "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour de défi ACME : le fichier de configuration nginx {filepath:s} est en conflit et doit être retiré au préalable." } From 62a5e5c5df1cb73a0f947bff9d706ee5a61854dd Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Tue, 29 Nov 2016 22:25:36 +0100 Subject: [PATCH 148/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (257 of 257 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 6017e3737..01f1cb184 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -265,5 +265,6 @@ "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés.", "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué.", "certmanager_no_cert_file": "Impossible de lire le fichier de certificat pour le domaine {domain:s} (fichier : {file:s})", - "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour de défi ACME : le fichier de configuration nginx {filepath:s} est en conflit et doit être retiré au préalable." + "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour de défi ACME : le fichier de configuration nginx {filepath:s} est en conflit et doit être retiré au préalable.", + "certmanager_hit_rate_limit": "Trop de certificats ont déjà été demandés récemment pour cet ensemble précis de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails." } From d6e76d2891ae11935be8b1bf369dbd2c5c5e3ca8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 1 Dec 2016 22:46:40 +0100 Subject: [PATCH 149/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (258 of 258 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 01f1cb184..f0afe0597 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -266,5 +266,6 @@ "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué.", "certmanager_no_cert_file": "Impossible de lire le fichier de certificat pour le domaine {domain:s} (fichier : {file:s})", "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour de défi ACME : le fichier de configuration nginx {filepath:s} est en conflit et doit être retiré au préalable.", - "certmanager_hit_rate_limit": "Trop de certificats ont déjà été demandés récemment pour cet ensemble précis de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails." + "certmanager_hit_rate_limit": "Trop de certificats ont déjà été demandés récemment pour cet ensemble précis de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails.", + "ldap_init_failed_to_create_admin": "L’initialisation de LDAP n’a pas réussi à créer l’utilisateur admin." } From 83e9e10123364044f512382edcd0c4a978c46781 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Mon, 5 Dec 2016 07:29:35 +0100 Subject: [PATCH 150/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (260 of 260 strings) --- locales/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index f0afe0597..ca2d7fe50 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -267,5 +267,7 @@ "certmanager_no_cert_file": "Impossible de lire le fichier de certificat pour le domaine {domain:s} (fichier : {file:s})", "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour de défi ACME : le fichier de configuration nginx {filepath:s} est en conflit et doit être retiré au préalable.", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été demandés récemment pour cet ensemble précis de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails.", - "ldap_init_failed_to_create_admin": "L’initialisation de LDAP n’a pas réussi à créer l’utilisateur admin." + "ldap_init_failed_to_create_admin": "L’initialisation de LDAP n’a pas réussi à créer l’utilisateur admin.", + "ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON.", + "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON." } From 6e9f305e8381ce1c0734b7fc4cf4acb5ca07b617 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 8 Dec 2016 07:09:57 +0100 Subject: [PATCH 151/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (261 of 261 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index ca2d7fe50..2a05dbd58 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -269,5 +269,6 @@ "certmanager_hit_rate_limit": "Trop de certificats ont déjà été demandés récemment pour cet ensemble précis de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails.", "ldap_init_failed_to_create_admin": "L’initialisation de LDAP n’a pas réussi à créer l’utilisateur admin.", "ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON.", - "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON." + "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON.", + "domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable." } From 41f70f5d56770c44af1cf883308786a1c8ee55e8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Sun, 11 Dec 2016 22:56:27 +0100 Subject: [PATCH 152/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (263 of 263 strings) --- locales/fr.json | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 2a05dbd58..ed299daf1 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -59,7 +59,7 @@ "backup_hook_unknown": "Script de sauvegarde « {hook:s} » inconnu", "backup_invalid_archive": "Archive de sauvegarde incorrecte", "backup_nothings_done": "Il n'y a rien à sauvegarder", - "backup_output_directory_forbidden": "Dossier de sortie interdit. Les sauvegardes ne peuvent être créées dans les dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives.", + "backup_output_directory_forbidden": "Dossier de destination interdit. Les sauvegardes ne peuvent être créées dans les dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide", "backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde", "backup_running_app_script": "Lancement du script de sauvegarde de l'application « {app:s} »...", @@ -82,11 +82,11 @@ "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", "domain_exists": "Le domaine existe déjà", - "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.", + "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine", "domain_unknown": "Domaine inconnu", "domain_zone_exists": "Le fichier de zone DNS existe déjà", "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", - "done": "Terminé.", + "done": "Terminé", "downloading": "Téléchargement...", "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été installée", "dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour le domaine DynDNS", @@ -105,7 +105,7 @@ "field_invalid": "Champ incorrect : « {:s} »", "firewall_reload_failed": "Impossible de recharger le pare-feu", "firewall_reloaded": "Le pare-feu a été rechargé", - "firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.", + "firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal", "format_datetime_short": "%d/%m/%Y %H:%M", "hook_argument_missing": "Argument manquant : '{:s}'", "hook_choice_invalid": "Choix incorrect : '{:s}'", @@ -115,8 +115,8 @@ "hook_name_unknown": "Nom de script « {name:s} » inconnu", "installation_complete": "Installation terminée", "installation_failed": "Échec de l'installation", - "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes sûrement dans un conteneur, ou alors votre noyau ne le supporte pas.", - "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", + "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le supporte pas", + "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le supporte pas", "ldap_initialized": "L'annuaire LDAP a été initialisé", "license_undefined": "indéfinie", "mail_alias_remove_failed": "Impossible de supprimer l'adresse courriel supplémentaire « {mail:s} »", @@ -248,27 +248,29 @@ "yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification", "yunohost_configured": "YunoHost a été configuré", "yunohost_installing": "Installation de YunoHost...", - "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall ».", + "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall »", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner)", "certmanager_domain_unknown": "Domaine inconnu {domain:s}", "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force)", "certmanager_certificate_fetching_or_enabling_failed": "Il semble que l'activation du nouveau certificat pour {domain:s} a échoué…", "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain:s} n’est pas fourni par Let’s Encrypt. Impossible de le renouveler automatiquement !", "certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} est sur le point d’expirer ! Utilisez --force pour contourner", - "certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} n’est pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et nginx sont correctes.", + "certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} n’est pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et nginx sont correctes", "certmanager_error_no_A_record": "Aucun enregistrement DNS « A » n’a été trouvé pour {domain:s}. De devez faire pointer votre nom de domaine vers votre machine pour être capable d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS « A » du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez modifié récemment votre enregistrement « A », veuillez attendre sa propagation (quelques vérificateur de propagation sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), cause : {reason:s}", "certmanager_cert_install_success_selfsigned": "Installation avec succès d’un certificat auto-signé pour le domaine {domain:s} !", "certmanager_cert_install_success": "Installation avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", "certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", - "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés.", - "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué.", + "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", + "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué", "certmanager_no_cert_file": "Impossible de lire le fichier de certificat pour le domaine {domain:s} (fichier : {file:s})", - "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour de défi ACME : le fichier de configuration nginx {filepath:s} est en conflit et doit être retiré au préalable.", - "certmanager_hit_rate_limit": "Trop de certificats ont déjà été demandés récemment pour cet ensemble précis de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails.", - "ldap_init_failed_to_create_admin": "L’initialisation de LDAP n’a pas réussi à créer l’utilisateur admin.", - "ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON.", - "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON.", - "domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable." + "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour de défi ACME : le fichier de configuration nginx {filepath:s} est en conflit et doit être retiré au préalable", + "certmanager_hit_rate_limit": "Trop de certificats ont déjà été demandés récemment pour cet ensemble précis de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails", + "ldap_init_failed_to_create_admin": "L’initialisation de LDAP n’a pas réussi à créer l’utilisateur admin", + "ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", + "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", + "domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable.", + "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", + "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})" } From 64a831721323c079ddc9ff86a0fe00515040068a Mon Sep 17 00:00:00 2001 From: Genma Date: Mon, 12 Dec 2016 12:22:44 +0100 Subject: [PATCH 153/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (264 of 264 strings) --- locales/fr.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index ed299daf1..c7fc1a130 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -111,7 +111,7 @@ "hook_choice_invalid": "Choix incorrect : '{:s}'", "hook_exec_failed": "Échec de l'exécution du script « {path:s} »", "hook_exec_not_terminated": "L'exécution du script « {path:s} » ne s'est pas terminée", - "hook_list_by_invalid": "Propriété pour lister les scripts incorrecte", + "hook_list_by_invalid": "Propriété pour lister les scripts incorrects", "hook_name_unknown": "Nom de script « {name:s} » inconnu", "installation_complete": "Installation terminée", "installation_failed": "Échec de l'installation", @@ -272,5 +272,6 @@ "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable.", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", - "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})" + "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", + "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la boîte mail." } From a3f2e62c11b0b842bf89dc57f65622de9da482c6 Mon Sep 17 00:00:00 2001 From: Moul Date: Mon, 12 Dec 2016 21:34:07 +0100 Subject: [PATCH 154/208] [enh] readme: add translation badge status. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 714fa0980..9aed880ac 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ This repository is the core of YunoHost code. + +Translation status + + ## Issues - [Please report issues on YunoHost bugtracker](https://dev.yunohost.org/projects/yunohost/issues) (no registration needed). From e3b7db71f2fa6ee5a70efcd31e087172988b1bcf Mon Sep 17 00:00:00 2001 From: Juanu Date: Tue, 13 Dec 2016 14:00:04 +0100 Subject: [PATCH 155/208] [i18n] Translated using Weblate (Spanish) Currently translated at 100.0% (264 of 264 strings) --- locales/es.json | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/locales/es.json b/locales/es.json index 549cbe29a..093fb17fa 100644 --- a/locales/es.json +++ b/locales/es.json @@ -58,7 +58,7 @@ "backup_hook_unknown": "Hook de copia de seguridad desconocido '{hook:s}'", "backup_invalid_archive": "La copia de seguridad no es válida", "backup_nothings_done": "No hay nada que guardar", - "backup_output_directory_forbidden": "Directorio de salida no permitido. Las copias de seguridad no pueden ser creadas en /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o en los subdirectorios /home/yunohost.backup.", + "backup_output_directory_forbidden": "Directorio de salida no permitido. Las copias de seguridad no pueden ser creadas en /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o en los subdirectorios de /home/yunohost.backup/archives", "backup_output_directory_not_empty": "El directorio de salida no está vacío", "backup_output_directory_required": "Debe proporcionar un directorio de salida para la copia de seguridad", "backup_running_app_script": "Ejecutando la script de copia de seguridad de la aplicación '{app:s}'...", @@ -114,8 +114,8 @@ "hook_name_unknown": "Nombre de hook desconocido '{name:s}'", "installation_complete": "Instalación finalizada", "installation_failed": "No pudo realizar la instalación", - "ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción.", - "iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción.", + "ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción", + "iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción", "ldap_initialized": "LDAP iniciado", "license_undefined": "indefinido", "mail_alias_remove_failed": "No se pudo eliminar el alias de correo '{mail:s}'", @@ -240,5 +240,30 @@ "yunohost_ca_creation_failed": "No se pudo crear el certificado de autoridad", "yunohost_configured": "YunoHost ha sido configurado", "yunohost_installing": "Instalando YunoHost...", - "yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'." + "yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'", + "ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador", + "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón", + "ssowat_persistent_conf_read_error": "Error al leer la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", + "ssowat_persistent_conf_write_error": "Error al guardar la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", + "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s} 1! (Use --force para omitir este mensaje)", + "certmanager_domain_unknown": "Dominio desconocido {domain:s} 1", + "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} 1 no es autofirmado. ¿Estás seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", + "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para {domain:s} 1 falla de alguna manera...", + "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} 1 no está emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", + "certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} 1 no está a punto de expirar! Utilice --force para omitir este mensaje", + "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} 1 a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", + "certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s} 1. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} 1 es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", + "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} 1 (archivo: {file:s} 2), razón: {reason:s} 3", + "certmanager_cert_install_success_selfsigned": "¡Se ha instalado correctamente un certificado autofirmado para el dominio {domain:s} 1!", + "certmanager_cert_install_success": "Se ha instalado correctamente un certificado Let's Encrypt para el dominio {domain:s} 1!", + "certmanager_cert_renew_success": "¡Se ha renovado correctamente el certificado Let's Encrypt para el dominio {domain:s} 1!", + "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada y esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar certificados para todos los dominios con un certificado Let's Encrypt o un certificado autofirmado", + "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para el conjunto de dominios {domain:s} 1. Por favor, inténtelo de nuevo más tarde. Consulte https://letsencrypt.org/docs/rate-limits/ para obtener más detalles", + "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", + "certmanager_no_cert_file": "No se puede leer el certificado para el dominio {domain:s} 1 (archivo: {file:s} 2)", + "certmanager_conflicting_nginx_file": "No se puede preparar el dominio para el desafío ACME: el archivo de configuración nginx {filepath:s} 1 está en conflicto y debe ser eliminado primero", + "domain_cannot_remove_main": "No se puede eliminar el dominio principal. Primero debe establecer un nuevo dominio principal", + "certmanager_self_ca_conf_file_not_found": "No se ha encontrado el archivo de configuración para la autoridad de autofirma (file: {file:s} 1)", + "certmanager_unable_to_parse_self_CA_name": "No se puede procesar el nombre de la autoridad de autofirma (file: {file:s} 1)" } From 72789875347a49c8be9ad90791cdac58426636e2 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Tue, 13 Dec 2016 07:20:51 +0100 Subject: [PATCH 156/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (264 of 264 strings) --- locales/fr.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index c7fc1a130..35749030c 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -119,9 +119,9 @@ "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le supporte pas", "ldap_initialized": "L'annuaire LDAP a été initialisé", "license_undefined": "indéfinie", - "mail_alias_remove_failed": "Impossible de supprimer l'adresse courriel supplémentaire « {mail:s} »", - "mail_domain_unknown": "Le domaine « {domain:s} » de l'adresse courriel est inconnu", - "mail_forward_remove_failed": "Impossible de supprimer l'adresse courriel de transfert « {mail:s} »", + "mail_alias_remove_failed": "Impossible de supprimer l'alias courriel « {mail:s} »", + "mail_domain_unknown": "Le domaine « {domain:s} » du courriel est inconnu", + "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert « {mail:s} »", "maindomain_change_failed": "Impossible de modifier le domaine principal", "maindomain_changed": "Le domaine principal a été modifié", "monitor_disabled": "La supervision du serveur a été désactivé", @@ -155,7 +155,7 @@ "path_removal_failed": "Impossible de supprimer le chemin {:s}", "pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement", "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)", - "pattern_email": "Doit être une adresse courriel valide (ex. : someone@domain.org)", + "pattern_email": "Doit être une adresse courriel valide (ex. : pseudo@domain.org)", "pattern_firstname": "Doit être un prénom valide", "pattern_lastname": "Doit être un nom valide", "pattern_listname": "Doit être composé uniquement de caractères alphanumériques et de tirets bas", @@ -273,5 +273,5 @@ "domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable.", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", - "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la boîte mail." + "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie" } From cde530735e942fef067c1fb4aa30d1022590b7f8 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 13 Dec 2016 15:09:25 +0100 Subject: [PATCH 157/208] [enh] add empty file for breton translation --- locales/br.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/br.json diff --git a/locales/br.json b/locales/br.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/br.json @@ -0,0 +1 @@ +{} From 12c66d153096b13abe99e8978eb35cd6c2edc2f6 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 3 Dec 2016 00:36:22 +0100 Subject: [PATCH 158/208] [enh] remove timeout from cli interface --- bin/yunohost | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/yunohost b/bin/yunohost index b2a0e4b1b..0bf2c004c 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -66,6 +66,10 @@ def _parse_cli_args(): action='store_true', default=False, help="Don't produce any output", ) + parser.add_argument('--timeout', + type=int, default=None, + help="Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock", + ) parser.add_argument('--admin-password', default=None, dest='password', metavar='PASSWORD', help="The admin password to use to authenticate", @@ -209,6 +213,7 @@ if __name__ == '__main__': ret = moulinette.cli( _retrieve_namespaces(), args, use_cache=opts.use_cache, output_as=opts.output_as, - password=opts.password, parser_kwargs={'top_parser': parser} + password=opts.password, parser_kwargs={'top_parser': parser}, + timeout=opts.timeout, ) sys.exit(ret) From eb1f6a77a9c1800ffd520316dcae097bb155d869 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 15 Dec 2016 16:33:46 +0100 Subject: [PATCH 159/208] Revert "Revert "[fix] Can't restore app on a root domain"" This reverts commit 7717c58de4c8cda615165d84a12a30dd73eea58a. --- src/yunohost/app.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index eb22cb955..85693a0dd 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -917,10 +917,6 @@ def app_checkurl(auth, url, app=None): raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) if domain in apps_map: - # Domain already has apps on sub path - if path == '/': - raise MoulinetteError(errno.EPERM, - m18n.n('app_location_install_failed')) # Loop through apps for p, a in apps_map[domain].items(): # Skip requested app checking @@ -930,7 +926,7 @@ def app_checkurl(auth, url, app=None): if path == p: raise MoulinetteError(errno.EINVAL, m18n.n('app_location_already_used')) - elif path.startswith(p): + elif path.startswith(p) or p.startswith(path): raise MoulinetteError(errno.EPERM, m18n.n('app_location_install_failed')) From 9be254ecfcc4790405d4769e65872376df3faa71 Mon Sep 17 00:00:00 2001 From: opi Date: Thu, 15 Dec 2016 21:24:40 +0100 Subject: [PATCH 160/208] [enh][love] Add CONTRIBUTORS.md --- CONTRIBUTORS.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 000000000..375c1cec6 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,88 @@ +YunoHost core contributors +========================== + +YunoHost is built and maintained by the YunoHost project community. +Everyone is encouraged to submit issues and changes, and to contribute in other ways -- see https://yunohost.org/contribute to find out how. + +-- + +Initial YunoHost core was built by Kload & beudbeud, for YunoHost v2. + +Most of code was written by Kload and jerome, with help of numerous contributors. + +Translation is made by a bunch of lovely people all over the world. + +We would like to thank anyone who ever helped the YunoHost project <3 + + +YunoHost core Contributors +-------------------------- + +- Jérôme Lebleu +- Kload +- Laurent 'Bram' Peuch +- Julien 'ju' Malik +- opi +- Aleks +- Adrien 'beudbeud' Beudin +- M5oul +- Valentin 'zamentur' / 'ljf' Grimaud +- Jocelyn Delalande +- infertux +- Taziden +- ZeHiro +- Josue-T +- nahoj +- a1ex +- JimboJoe +- vetetix +- jellium +- Sebastien 'sebian' Badia +- lmangani +- Julien Vaubourg + + +YunoHost core Translators +------------------------- + +If you want to help translation, please visit https://translate.yunohost.org/projects/yunohost/yunohost/ + + +### Dutch + +- DUBWiSE +- marut + +### English + +- Bugsbane + +### French + +- aoz roon +- Genma +- Jean-Baptiste Holcroft +- Jérôme Lebleu + +### German + +- david.bartke +- Felix Bartels +- Philip Gatzka + +### Hindi + +- Anmol + +### Italian + +- Thomas Bille + +### Portuguese + +- Deleted User + +### Spanish + +- Juanu + From 71df3a6c00ae13ee613a33c1bdbc7809596361bd Mon Sep 17 00:00:00 2001 From: Juanu Date: Tue, 13 Dec 2016 15:01:21 +0100 Subject: [PATCH 161/208] [i18n] Translated using Weblate (Spanish) Currently translated at 100.0% (264 of 264 strings) --- locales/es.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/locales/es.json b/locales/es.json index 093fb17fa..9a7e4314e 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,12 +1,12 @@ { - "action_invalid": "Acción no válida '{action:s}'", + "action_invalid": "Acción no válida '{action:s} 1'", "admin_password": "Contraseña administrativa", - "admin_password_change_failed": "No se pudo cambiar la contraseña", + "admin_password_change_failed": "No se puede cambiar la contraseña", "admin_password_changed": "La contraseña administrativa ha sido cambiada", - "app_already_installed": "{app:s} ya está instalada", - "app_argument_choice_invalid": "Opción no válida para el argumento '{name:s}', deber una de {choices:s}", - "app_argument_invalid": "Valor no válido para el argumento '{name:s}': {error:s}", - "app_argument_required": "Se requiere el argumento '{name:s}'", + "app_already_installed": "{app:s} 2 ya está instalada", + "app_argument_choice_invalid": "Opción no válida para el argumento '{name:s} 3', deber una de {choices:s} 4", + "app_argument_invalid": "Valor no válido para el argumento '{name:s} 5': {error:s} 6", + "app_argument_required": "Se requiere el argumento '{name:s} 7'", "app_extraction_failed": "No se pudieron extraer los archivos de instalación", "app_id_invalid": "Id de la aplicación no válida", "app_incompatible": "La aplicación no es compatible con su versión de YunoHost", @@ -15,9 +15,9 @@ "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_no_upgrade": "No hay aplicaciones para actualizar", - "app_not_correctly_installed": "La aplicación {app:s} parece estar incorrectamente instalada", - "app_not_installed": "{app:s} no está instalada", - "app_not_properly_removed": "La {app:s} no ha sido desinstalada correctamente", + "app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada", + "app_not_installed": "{app:s} 9 no está instalada", + "app_not_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente", "app_package_need_update": "Es necesario actualizar el paquete de la aplicación debido a los cambios en YunoHost", "app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ", "app_removed": "{app:s} ha sido eliminada", @@ -71,7 +71,7 @@ "diagnostic_monitor_network_error": "No se puede monitorizar la red: {error}", "diagnostic_monitor_system_error": "No se puede monitorizar el sistema: {error}", "diagnostic_no_apps": "Aplicación no instalada", - "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecuta 'apt-get remove bind9 && apt-get install dnsmasq'", + "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cert_gen_failed": "No se pudo crear el certificado", "domain_created": "El dominio ha sido creado", "domain_creation_failed": "No se pudo crear el dominio", @@ -84,11 +84,11 @@ "domain_uninstall_app_first": "Una o más aplicaciones están instaladas en este dominio. Debe desinstalarlas antes de eliminarlo.", "domain_unknown": "Dominio desconocido", "domain_zone_exists": "El archivo de zona del DNS ya existe", - "domain_zone_not_found": "No se ha encontrado el archivo de zona DNS para el dominio [:s]", + "domain_zone_not_found": "No se ha encontrado el archivo de zona del DNS para el dominio [:s]", "done": "Hecho.", "downloading": "Descargando...", "dyndns_cron_installed": "La tarea cron para DynDNS ha sido instalada", - "dyndns_cron_remove_failed": "No se pudo eliminar la tarea del cron DynDNS", + "dyndns_cron_remove_failed": "No se pudo eliminar la tarea cron DynDNS", "dyndns_cron_removed": "La tarea cron DynDNS ha sido eliminada", "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en el DynDNS", "dyndns_ip_updated": "Su dirección IP ha sido actualizada en el DynDNS", @@ -193,13 +193,13 @@ "service_conf_file_updated": "El archivo de configuración '{conf}' ha sido actualizado", "service_conf_up_to_date": "La configuración del servicio '{service}' ya está actualizada", "service_conf_updated": "La configuración ha sido actualizada para el servicio '{service}'", - "service_conf_would_be_updated": "La configuración podría haber sido actualizada para el servicio '{service}'", + "service_conf_would_be_updated": "La configuración podría haber sido actualizada para el servicio '{service} 1'", "service_disable_failed": "No se pudo deshabilitar el servicio '{service:s}'", "service_disabled": "El servicio '{service:s}' ha sido deshabilitado", "service_enable_failed": "No se pudo habilitar el servicio '{service:s}'", "service_enabled": "El servicio '{service:s}' ha sido habilitado", "service_no_log": "No hay ningún registro para el servicio '{service:s}'", - "service_regenconf_dry_pending_applying": "Comprobando configuración que podría haber sido aplicada al servicio '{service}'...", + "service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service} 1'...", "service_regenconf_failed": "No se puede regenerar la configuración para el servicio(s): {services}", "service_regenconf_pending_applying": "Aplicando la configuración para el servicio '{service}'...", "service_remove_failed": "No se pudo desinstalar el servicio '{service:s}'", @@ -242,13 +242,13 @@ "yunohost_installing": "Instalando YunoHost...", "yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'", "ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador", - "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón", + "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón de correo", "ssowat_persistent_conf_read_error": "Error al leer la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", "ssowat_persistent_conf_write_error": "Error al guardar la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s} 1! (Use --force para omitir este mensaje)", "certmanager_domain_unknown": "Dominio desconocido {domain:s} 1", - "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} 1 no es autofirmado. ¿Estás seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", - "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para {domain:s} 1 falla de alguna manera...", + "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} 1 no es autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", + "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para el dominio {domain:s} 1 ha fallado de alguna manera...", "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} 1 no está emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", "certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} 1 no está a punto de expirar! Utilice --force para omitir este mensaje", "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} 1 a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", @@ -258,7 +258,7 @@ "certmanager_cert_install_success_selfsigned": "¡Se ha instalado correctamente un certificado autofirmado para el dominio {domain:s} 1!", "certmanager_cert_install_success": "Se ha instalado correctamente un certificado Let's Encrypt para el dominio {domain:s} 1!", "certmanager_cert_renew_success": "¡Se ha renovado correctamente el certificado Let's Encrypt para el dominio {domain:s} 1!", - "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada y esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar certificados para todos los dominios con un certificado Let's Encrypt o un certificado autofirmado", + "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada y esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar los certificados para todos los dominios con un certificado Let's Encrypt o con un certificado autofirmado", "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para el conjunto de dominios {domain:s} 1. Por favor, inténtelo de nuevo más tarde. Consulte https://letsencrypt.org/docs/rate-limits/ para obtener más detalles", "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", "certmanager_no_cert_file": "No se puede leer el certificado para el dominio {domain:s} 1 (archivo: {file:s} 2)", From 025f7e932e02278402225c967e4795de12efa2a7 Mon Sep 17 00:00:00 2001 From: Juanu Date: Tue, 13 Dec 2016 15:53:25 +0100 Subject: [PATCH 162/208] [i18n] Translated using Weblate (Spanish) Currently translated at 100.0% (264 of 264 strings) --- locales/es.json | 68 ++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/locales/es.json b/locales/es.json index 9a7e4314e..b3dd4f14e 100644 --- a/locales/es.json +++ b/locales/es.json @@ -113,16 +113,16 @@ "hook_list_by_invalid": "Propiedad no válida para listar por hook", "hook_name_unknown": "Nombre de hook desconocido '{name:s}'", "installation_complete": "Instalación finalizada", - "installation_failed": "No pudo realizar la instalación", + "installation_failed": "No se pudo realizar la instalación", "ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción", "iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción", - "ldap_initialized": "LDAP iniciado", + "ldap_initialized": "Se ha inicializado LDAP", "license_undefined": "indefinido", "mail_alias_remove_failed": "No se pudo eliminar el alias de correo '{mail:s}'", "mail_domain_unknown": "El dominio de correo '{domain:s}' es desconocido", "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo '{mail:s}'", "maindomain_change_failed": "No se pudo cambiar el dominio principal", - "maindomain_changed": "El dominio principal ha sido cambiado", + "maindomain_changed": "Se ha cambiado el dominio principal", "monitor_disabled": "La monitorización del sistema ha sido deshabilitada", "monitor_enabled": "La monitorización del sistema ha sido habilitada", "monitor_glances_con_failed": "No se pudo conectar al servidor Glances", @@ -130,13 +130,13 @@ "monitor_period_invalid": "Período de tiempo no válido", "monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticas", "monitor_stats_no_update": "No hay estadísticas de monitorización para actualizar", - "monitor_stats_period_unavailable": "No hay estadísticas para ese período", + "monitor_stats_period_unavailable": "No hay estadísticas para el período", "mountpoint_unknown": "Punto de montaje desconocido", "mysql_db_creation_failed": "No se pudo crear la base de datos MySQL", "mysql_db_init_failed": "No se pudo iniciar la base de datos MySQL", - "mysql_db_initialized": "La base de datos MySQL ha sido iniciada", + "mysql_db_initialized": "La base de datos MySQL ha sido inicializada", "network_check_mx_ko": "El registro DNS MX no está configurado", - "network_check_smtp_ko": "El puerto 25 (SMTP) para el correo saliente parece estar bloqueado en su red", + "network_check_smtp_ko": "El puerto 25 (SMTP) para el correo saliente parece estar bloqueado por su red", "network_check_smtp_ok": "El puerto de salida del correo electrónico (25, SMTP) no está bloqueado", "new_domain_required": "Debe proporcionar el nuevo dominio principal", "no_appslist_found": "No se ha encontrado ninguna lista de aplicaciones", @@ -145,13 +145,13 @@ "no_restore_script": "No se ha encontrado un script de restauración para la aplicación '{app:s}'", "not_enough_disk_space": "No hay suficiente espacio en '{path:s}'", "package_not_installed": "El paquete '{pkgname}' no está instalado", - "package_unexpected_error": "Un error inesperado procesando el paquete '{pkgname}'", + "package_unexpected_error": "Ha ocurrido un error inesperado procesando el paquete '{pkgname}'", "package_unknown": "Paquete desconocido '{pkgname}'", - "packages_no_upgrade": "No hay paquetes que actualizar", + "packages_no_upgrade": "No hay paquetes para actualizar", "packages_upgrade_critical_later": "Los paquetes críticos ({packages:s}) serán actualizados más tarde", "packages_upgrade_failed": "No se pudieron actualizar todos los paquetes", - "path_removal_failed": "No se pudo borrar la ruta {:s}", - "pattern_backup_archive_name": "Debe que ser un nombre de archivo válido, solo se admiten caracteres alfanuméricos y los guiones -_", + "path_removal_failed": "No se pudo eliminar la ruta {:s}", + "pattern_backup_archive_name": "Debe ser un nombre de archivo válido, solo se admiten caracteres alfanuméricos y los guiones -_", "pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)", "pattern_email": "Debe ser una dirección de correo electrónico válida (por ejemplo, alguien@dominio.org)", "pattern_firstname": "Debe ser un nombre válido", @@ -180,7 +180,7 @@ "restore_running_hooks": "Ejecutando los hooks de restauración...", "service_add_failed": "No se pudo añadir el servicio '{service:s}'", "service_added": "Servicio '{service:s}' ha sido añadido", - "service_already_started": "El servicio '{service:s}' ya ha sido iniciado", + "service_already_started": "El servicio '{service:s}' ya ha sido inicializado", "service_already_stopped": "El servicio '{service:s}' ya ha sido detenido", "service_cmd_exec_failed": "No se pudo ejecutar el comando '{command:s}'", "service_conf_file_backed_up": "Se ha realizado una copia de seguridad del archivo de configuración '{conf}' en '{backup}'", @@ -199,9 +199,9 @@ "service_enable_failed": "No se pudo habilitar el servicio '{service:s}'", "service_enabled": "El servicio '{service:s}' ha sido habilitado", "service_no_log": "No hay ningún registro para el servicio '{service:s}'", - "service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service} 1'...", + "service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service}'...", "service_regenconf_failed": "No se puede regenerar la configuración para el servicio(s): {services}", - "service_regenconf_pending_applying": "Aplicando la configuración para el servicio '{service}'...", + "service_regenconf_pending_applying": "Aplicando la configuración pendiente para el servicio '{service}'...", "service_remove_failed": "No se pudo desinstalar el servicio '{service:s}'", "service_removed": "El servicio '{service:s}' ha sido desinstalado", "service_start_failed": "No se pudo iniciar el servicio '{service:s}'", @@ -219,7 +219,7 @@ "unit_unknown": "Unidad desconocida '{unit:s}'", "unlimit": "Sin cuota", "unrestore_app": "La aplicación '{app:s}' no será restaurada", - "update_cache_failed": "No se pudo actualizar la caché APT", + "update_cache_failed": "No se pudo actualizar la caché de APT", "updating_apt_cache": "Actualizando lista de paquetes disponibles...", "upgrade_complete": "Actualización finalizada", "upgrading_packages": "Actualizando paquetes...", @@ -243,27 +243,27 @@ "yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'", "ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador", "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón de correo", - "ssowat_persistent_conf_read_error": "Error al leer la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", - "ssowat_persistent_conf_write_error": "Error al guardar la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", - "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s} 1! (Use --force para omitir este mensaje)", - "certmanager_domain_unknown": "Dominio desconocido {domain:s} 1", - "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} 1 no es autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", - "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para el dominio {domain:s} 1 ha fallado de alguna manera...", - "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} 1 no está emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", - "certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} 1 no está a punto de expirar! Utilice --force para omitir este mensaje", - "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} 1 a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", - "certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s} 1. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} 1 es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", - "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} 1 (archivo: {file:s} 2), razón: {reason:s} 3", - "certmanager_cert_install_success_selfsigned": "¡Se ha instalado correctamente un certificado autofirmado para el dominio {domain:s} 1!", - "certmanager_cert_install_success": "Se ha instalado correctamente un certificado Let's Encrypt para el dominio {domain:s} 1!", - "certmanager_cert_renew_success": "¡Se ha renovado correctamente el certificado Let's Encrypt para el dominio {domain:s} 1!", - "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada y esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar los certificados para todos los dominios con un certificado Let's Encrypt o con un certificado autofirmado", - "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para el conjunto de dominios {domain:s} 1. Por favor, inténtelo de nuevo más tarde. Consulte https://letsencrypt.org/docs/rate-limits/ para obtener más detalles", + "ssowat_persistent_conf_read_error": "Error al leer la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", + "ssowat_persistent_conf_write_error": "Error al guardar la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", + "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", + "certmanager_domain_unknown": "Dominio desconocido {domain:s}", + "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", + "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para el dominio {domain:s} ha fallado de alguna manera...", + "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", + "certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} no está a punto de expirar! Utilice --force para omitir este mensaje", + "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", + "certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", + "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} (archivo: {file:s}), razón: {reason:s}", + "certmanager_cert_install_success_selfsigned": "¡Se ha instalado correctamente un certificado autofirmado para el dominio {domain:s}!", + "certmanager_cert_install_success": "¡Se ha instalado correctamente un certificado Let's Encrypt para el dominio {domain:s}!", + "certmanager_cert_renew_success": "¡Se ha renovado correctamente el certificado Let's Encrypt para el dominio {domain:s}!", + "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada, esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar los certificados para todos los dominios con un certificado Let's Encrypt o con un certificado autofirmado", + "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para el conjunto de dominios {domain:s}. Por favor, inténtelo de nuevo más tarde. Consulte https://letsencrypt.org/docs/rate-limits/ para obtener más detalles", "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", - "certmanager_no_cert_file": "No se puede leer el certificado para el dominio {domain:s} 1 (archivo: {file:s} 2)", - "certmanager_conflicting_nginx_file": "No se puede preparar el dominio para el desafío ACME: el archivo de configuración nginx {filepath:s} 1 está en conflicto y debe ser eliminado primero", + "certmanager_no_cert_file": "No se puede leer el certificado para el dominio {domain:s} (archivo: {file:s})", + "certmanager_conflicting_nginx_file": "No se puede preparar el dominio para el desafío ACME: el archivo de configuración nginx {filepath:s} está en conflicto y debe ser eliminado primero", "domain_cannot_remove_main": "No se puede eliminar el dominio principal. Primero debe establecer un nuevo dominio principal", - "certmanager_self_ca_conf_file_not_found": "No se ha encontrado el archivo de configuración para la autoridad de autofirma (file: {file:s} 1)", + "certmanager_self_ca_conf_file_not_found": "No se ha encontrado el archivo de configuración para la autoridad de autofirma (file: {file:s})", "certmanager_unable_to_parse_self_CA_name": "No se puede procesar el nombre de la autoridad de autofirma (file: {file:s} 1)" } From bd258958fa50eca24ccab5fd5905c59bfc2b2a05 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Tue, 13 Dec 2016 07:20:51 +0100 Subject: [PATCH 163/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (265 of 265 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 35749030c..cbd619c38 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -273,5 +273,6 @@ "domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable.", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", - "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie" + "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie", + "domains_available": "Domaines disponibles :" } From 0a7479cc37a8c11d2e646145f7eb7bff1696ec14 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 16 Dec 2016 00:59:10 +0100 Subject: [PATCH 164/208] Update changelog for 2.5.2 release --- debian/changelog | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/debian/changelog b/debian/changelog index 34d883ca1..248c934fd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,42 @@ +yunohost (2.5.2) testing; urgency=low + + LDAP admin user: + * [fix] wait for admin user to be available after a slapd regen-conf, this fix install on slow hardware/vps + + Dovecot/emails: + * [enh] reorder dovecot main configuration so that it is easier to read and extend + * [enh] Allow for dovecot configuration extensions + * [fix] Can't get mailbos used space if dovecot is down + + Backup: + * [fix] Need to create archives_path even for custom output directory + * Keep track of backups with custom directory using symlinks + + Security: + * [fix] Improve dnssec key generation on low entropy devices + * [enh] Add haveged as dependency + + Random broken app installed on slow hardware: + * [enh] List available domains when installing an app by CLI. + + Translation: + * French by Jibec and Genma + * German by Philip Gatzka + * Hindi by Anmol + * Spanish by Juanu + + Other fixes and improvements: + * [enh] remove timeout from cli interface + * [fix] [#662](https://dev.yunohost.org/issues/662): missing 'python-openssl' dependency for Let's Encrypt integration. + * [fix] --no-remove-on-failure for app install should behave as a flag. + * [fix] don't remove trailing char if it's not a slash + + Thanks to all contributors: Aleks, alex, Anmol, Bram, Genma, jibec, ju, + Juanu, ljf, Moul, opi, Philip Gatzka and to the people who are participating + to the beta and giving us feedback <3 + + -- Laurent Peuch Fri, 16 Dec 2016 00:49:08 +0100 + yunohost (2.5.1) testing; urgency=low * [fix] Raise error on malformed SSOwat persistent conf. From 40cb95f33af7f209e839b06df2591c645e132511 Mon Sep 17 00:00:00 2001 From: opi Date: Sat, 17 Dec 2016 15:26:50 +0100 Subject: [PATCH 165/208] [enh] Increase fail2ban maxretry on user login, narrow nginx log files We're intended to a general public, after all ;) --- data/templates/fail2ban/jail.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/templates/fail2ban/jail.conf b/data/templates/fail2ban/jail.conf index 59dcf51df..d34763e48 100644 --- a/data/templates/fail2ban/jail.conf +++ b/data/templates/fail2ban/jail.conf @@ -581,4 +581,5 @@ enabled = true port = http,https protocol = tcp filter = yunohost -logpath = /var/log/nginx/*.log +logpath = /var/log/nginx*/*error.log +maxretry = 6 From a94733c1f9d9dd8e11b484bafc576800029d28be Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 18 Dec 2016 01:05:03 +0100 Subject: [PATCH 166/208] [enh] add yaml syntax check in travis.yml --- .travis.yml | 5 +++++ tests/test_actionmap.py | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 .travis.yml create mode 100644 tests/test_actionmap.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..25fe0e5fc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: python +install: "pip install pytest pyyaml" +python: + - "2.7" +script: "py.test tests" diff --git a/tests/test_actionmap.py b/tests/test_actionmap.py new file mode 100644 index 000000000..08b868839 --- /dev/null +++ b/tests/test_actionmap.py @@ -0,0 +1,4 @@ +import yaml + +def test_yaml_syntax(): + yaml.load(open("data/actionsmap/yunohost.yml")) From aad37409333895051e505c944b2ec05790d2fb46 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 18 Dec 2016 02:01:17 +0100 Subject: [PATCH 167/208] [mod] autopep8 on all files that aren't concerned by a PR --- src/yunohost/backup.py | 12 +- src/yunohost/certificate.py | 6 +- src/yunohost/domain.py | 28 ++--- src/yunohost/dyndns.py | 36 +++--- src/yunohost/firewall.py | 39 ++++--- src/yunohost/monitor.py | 30 ++--- src/yunohost/service.py | 31 ++--- src/yunohost/user.py | 126 +++++++++++---------- src/yunohost/utils/packages.py | 1 + src/yunohost/vendor/acme_tiny/acme_tiny.py | 26 ++++- 10 files changed, 181 insertions(+), 154 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index da5115c28..587da31c7 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -47,7 +47,7 @@ from yunohost.hook import ( from yunohost.monitor import binary_to_human from yunohost.tools import tools_postinstall -backup_path = '/home/yunohost.backup' +backup_path = '/home/yunohost.backup' archives_path = '%s/archives' % backup_path logger = getActionLogger('yunohost.backup') @@ -313,8 +313,6 @@ def backup_create(name=None, description=None, output_directory=None, link = "%s/%s.tar.gz" % (archives_path, name) os.symlink(archive_file, link) - - # Clean temporary directory if tmp_dir != output_directory: _clean_tmp_dir() @@ -323,7 +321,7 @@ def backup_create(name=None, description=None, output_directory=None, # Return backup info info['name'] = name - return { 'archive': info } + return {'archive': info} def backup_restore(auth, name, hooks=[], ignore_hooks=False, @@ -601,7 +599,7 @@ def backup_list(with_info=False, human_readable=False): d[a] = backup_info(a, human_readable=human_readable) result = d - return { 'archives': result } + return {'archives': result} def backup_info(name, with_details=False, human_readable=False): @@ -645,7 +643,7 @@ def backup_info(name, with_details=False, human_readable=False): size = info.get('size', 0) if not size: tar = tarfile.open(archive_file, "r:gz") - size = reduce(lambda x,y: getattr(x, 'size', x)+getattr(y, 'size', y), + size = reduce(lambda x, y: getattr(x, 'size', x) + getattr(y, 'size', y), tar.getmembers()) tar.close() if human_readable: @@ -678,7 +676,7 @@ def backup_delete(name): archive_file = '%s/%s.tar.gz' % (archives_path, name) info_file = "%s/%s.info.json" % (archives_path, name) - for backup_file in [archive_file,info_file]: + for backup_file in [archive_file, info_file]: if not os.path.isfile(backup_file): raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown', name=backup_file)) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 04d755692..01852d2ec 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -193,9 +193,9 @@ def _certificate_install_selfsigned(domain_list, force=False): # and self-sign the cert commands = [ "openssl req -new -config %s -days 3650 -out %s -keyout %s -nodes -batch" - % (conf_file, csr_file, key_file), + % (conf_file, csr_file, key_file), "openssl ca -config %s -days 3650 -in %s -out %s -batch" - % (conf_file, csr_file, crt_file), + % (conf_file, csr_file, crt_file), ] for command in commands: @@ -528,7 +528,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False): CA=certification_authority) except ValueError as e: if "urn:acme:error:rateLimited" in str(e): - raise MoulinetteError(errno.EINVAL, m18n.n( + raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_hit_rate_limit', domain=domain)) else: logger.error(str(e)) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 1aeab33ef..ded8bb27a 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -66,10 +66,10 @@ def domain_list(auth, filter=None, limit=None, offset=None): result = auth.search('ou=domains,dc=yunohost,dc=org', filter, ['virtualdomain']) if len(result) > offset and limit > 0: - for domain in result[offset:offset+limit]: + for domain in result[offset:offset + limit]: result_list.append(domain['virtualdomain'][0]) - return { 'domains': result_list } + return {'domains': result_list} def domain_add(auth, domain, dyndns=False): @@ -83,7 +83,7 @@ def domain_add(auth, domain, dyndns=False): """ from yunohost.hook import hook_callback - attr_dict = { 'objectClass' : ['mailDomain', 'top'] } + attr_dict = {'objectClass': ['mailDomain', 'top']} now = datetime.datetime.now() timestamp = str(now.year) + str(now.month) + str(now.day) @@ -103,7 +103,7 @@ def domain_add(auth, domain, dyndns=False): pass else: dyndomains = json.loads(r.text) - dyndomain = '.'.join(domain.split('.')[1:]) + dyndomain = '.'.join(domain.split('.')[1:]) if dyndomain in dyndomains: if os.path.exists('/etc/cron.d/yunohost-dyndns'): raise MoulinetteError(errno.EPERM, @@ -113,7 +113,6 @@ def domain_add(auth, domain, dyndns=False): raise MoulinetteError(errno.EINVAL, m18n.n('domain_dyndns_root_unknown')) - try: yunohost.certificate._certificate_install_selfsigned([domain], False) @@ -122,7 +121,6 @@ def domain_add(auth, domain, dyndns=False): except MoulinetteError: raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists')) - attr_dict['virtualdomain'] = domain if not auth.add('virtualdomain=%s,ou=domains' % domain, attr_dict): @@ -133,11 +131,14 @@ def domain_add(auth, domain, dyndns=False): service_regen_conf(names=[ 'nginx', 'metronome', 'dnsmasq', 'rmilter']) os.system('yunohost app ssowatconf > /dev/null 2>&1') - except IOError: pass + except IOError: + pass except: # Force domain removal silently - try: domain_remove(auth, domain, True) - except: pass + try: + domain_remove(auth, domain, True) + except: + pass raise hook_callback('post_domain_add', args=[domain]) @@ -165,7 +166,7 @@ def domain_remove(auth, domain, force=False): # Check if apps are installed on the domain for app in os.listdir('/etc/yunohost/apps/'): - with open('/etc/yunohost/apps/' + app +'/settings.yml') as f: + with open('/etc/yunohost/apps/' + app + '/settings.yml') as f: try: app_domain = yaml.load(f)['domain'] except: @@ -224,13 +225,13 @@ def domain_dns_conf(domain, ttl=None): "muc {ttl} IN CNAME @\n" "pubsub {ttl} IN CNAME @\n" "vjud {ttl} IN CNAME @\n" - ).format(ttl=ttl, domain=domain) + ).format(ttl=ttl, domain=domain) # Email result += ('\n' '@ {ttl} IN MX 10 {domain}.\n' '@ {ttl} IN TXT "v=spf1 a mx ip4:{ip4}' - ).format(ttl=ttl, domain=domain, ip4=ip4) + ).format(ttl=ttl, domain=domain, ip4=ip4) if ip6 is not None: result += ' ip6:{ip6}'.format(ip6=ip6) result += ' -all"' @@ -246,7 +247,7 @@ def domain_dns_conf(domain, ttl=None): r'^(?P[a-z_\-\.]+)[\s]+([0-9]+[\s]+)?IN[\s]+TXT[\s]+[^"]*' '(?=.*(;[\s]*|")v=(?P[^";]+))' '(?=.*(;[\s]*|")k=(?P[^";]+))' - '(?=.*(;[\s]*|")p=(?P

[^";]+))'), dkim_content, re.M|re.S + '(?=.*(;[\s]*|")p=(?P

[^";]+))'), dkim_content, re.M | re.S ) if dkim: result += '\n{host}. {ttl} IN TXT "v={v}; k={k}; p={p}"'.format( @@ -296,6 +297,7 @@ def _get_maindomain(): maindomain = f.readline().rstrip() return maindomain + def _set_maindomain(domain): with open('/etc/yunohost/current_host', 'w') as f: f.write(domain) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index b32cde0da..7553e417c 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -94,7 +94,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 && ' \ + os.system('cd /etc/yunohost/dyndns && ' 'dnssec-keygen -a hmac-md5 -b 128 -r /dev/urandom -n USER %s' % domain) os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private') @@ -104,12 +104,14 @@ 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' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}) except requests.ConnectionError: raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) if r.status_code != 201: - try: error = json.loads(r.text)['error'] - except: error = "Server error" + try: + error = json.loads(r.text)['error'] + except: + error = "Server error" raise MoulinetteError(errno.EPERM, m18n.n('dyndns_registration_failed', error=error)) @@ -204,33 +206,33 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, lines = [ 'server %s' % dyn_host, 'zone %s' % host, - 'update delete %s. A' % domain, - 'update delete %s. AAAA' % domain, - 'update delete %s. MX' % domain, - 'update delete %s. TXT' % domain, + 'update delete %s. A' % domain, + 'update delete %s. AAAA' % domain, + 'update delete %s. MX' % domain, + 'update delete %s. TXT' % domain, 'update delete pubsub.%s. A' % domain, 'update delete pubsub.%s. AAAA' % domain, - 'update delete muc.%s. A' % domain, + 'update delete muc.%s. A' % domain, 'update delete muc.%s. AAAA' % domain, - 'update delete vjud.%s. A' % domain, + 'update delete vjud.%s. A' % domain, 'update delete vjud.%s. AAAA' % domain, 'update delete _xmpp-client._tcp.%s. SRV' % domain, 'update delete _xmpp-server._tcp.%s. SRV' % domain, - 'update add %s. 1800 A %s' % (domain, ipv4), + 'update add %s. 1800 A %s' % (domain, ipv4), 'update add %s. 14400 MX 5 %s.' % (domain, domain), 'update add %s. 14400 TXT "v=spf1 a mx -all"' % domain, - 'update add pubsub.%s. 1800 A %s' % (domain, ipv4), - 'update add muc.%s. 1800 A %s' % (domain, ipv4), - 'update add vjud.%s. 1800 A %s' % (domain, ipv4), + 'update add pubsub.%s. 1800 A %s' % (domain, ipv4), + 'update add muc.%s. 1800 A %s' % (domain, ipv4), + 'update add vjud.%s. 1800 A %s' % (domain, ipv4), 'update add _xmpp-client._tcp.%s. 14400 SRV 0 5 5222 %s.' % (domain, domain), 'update add _xmpp-server._tcp.%s. 14400 SRV 0 5 5269 %s.' % (domain, domain) ] if ipv6 is not None: lines += [ - 'update add %s. 1800 AAAA %s' % (domain, ipv6), + 'update add %s. 1800 AAAA %s' % (domain, ipv6), 'update add pubsub.%s. 1800 AAAA %s' % (domain, ipv6), - 'update add muc.%s. 1800 AAAA %s' % (domain, ipv6), - 'update add vjud.%s. 1800 AAAA %s' % (domain, ipv6), + 'update add muc.%s. 1800 AAAA %s' % (domain, ipv6), + 'update add vjud.%s. 1800 AAAA %s' % (domain, ipv6), ] lines += [ 'show', diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 1291cf86a..91f484f48 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -67,14 +67,14 @@ def firewall_allow(protocol, port, ipv4_only=False, ipv6_only=False, # Validate protocols protocols = ['TCP', 'UDP'] if protocol != 'Both' and protocol in protocols: - protocols = [protocol,] + protocols = [protocol, ] # Validate IP versions ipvs = ['ipv4', 'ipv6'] if ipv4_only and not ipv6_only: - ipvs = ['ipv4',] + ipvs = ['ipv4', ] elif ipv6_only and not ipv4_only: - ipvs = ['ipv6',] + ipvs = ['ipv6', ] for p in protocols: # Iterate over IP versions to add port @@ -117,18 +117,18 @@ def firewall_disallow(protocol, port, ipv4_only=False, ipv6_only=False, # Validate protocols protocols = ['TCP', 'UDP'] if protocol != 'Both' and protocol in protocols: - protocols = [protocol,] + protocols = [protocol, ] # Validate IP versions and UPnP ipvs = ['ipv4', 'ipv6'] upnp = True if ipv4_only and ipv6_only: - upnp = True # automatically disallow UPnP + upnp = True # automatically disallow UPnP elif ipv4_only: - ipvs = ['ipv4',] + ipvs = ['ipv4', ] upnp = upnp_only elif ipv6_only: - ipvs = ['ipv6',] + ipvs = ['ipv6', ] upnp = upnp_only elif upnp_only: ipvs = [] @@ -178,7 +178,7 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False): ports = sorted(set(ports['ipv4']) | set(ports['ipv6'])) # Format returned dict - ret = { "opened_ports": ports } + ret = {"opened_ports": ports} if list_forwarded: # Combine TCP and UDP forwarded ports ret['forwarded_ports'] = sorted( @@ -224,8 +224,8 @@ def firewall_reload(skip_upnp=False): # Iterate over ports and add rule for protocol in ['TCP', 'UDP']: for port in firewall['ipv4'][protocol]: - rules.append("iptables -w -A INPUT -p %s --dport %s -j ACCEPT" \ - % (protocol, process.quote(str(port)))) + rules.append("iptables -w -A INPUT -p %s --dport %s -j ACCEPT" + % (protocol, process.quote(str(port)))) rules += [ "iptables -w -A INPUT -i lo -j ACCEPT", "iptables -w -A INPUT -p icmp -j ACCEPT", @@ -253,8 +253,8 @@ def firewall_reload(skip_upnp=False): # Iterate over ports and add rule for protocol in ['TCP', 'UDP']: for port in firewall['ipv6'][protocol]: - rules.append("ip6tables -w -A INPUT -p %s --dport %s -j ACCEPT" \ - % (protocol, process.quote(str(port)))) + rules.append("ip6tables -w -A INPUT -p %s --dport %s -j ACCEPT" + % (protocol, process.quote(str(port)))) rules += [ "ip6tables -w -A INPUT -i lo -j ACCEPT", "ip6tables -w -A INPUT -p icmpv6 -j ACCEPT", @@ -308,13 +308,14 @@ def firewall_upnp(action='status', no_refresh=False): try: # Remove old cron job os.remove('/etc/cron.d/yunohost-firewall') - except: pass + except: + pass action = 'status' no_refresh = False if action == 'status' and no_refresh: # Only return current state - return { 'enabled': enabled } + return {'enabled': enabled} elif action == 'enable' or (enabled and action == 'status'): # Add cron job with open(upnp_cron_job, 'w+') as f: @@ -330,7 +331,8 @@ def firewall_upnp(action='status', no_refresh=False): try: # Remove cron job os.remove(upnp_cron_job) - except: pass + except: + pass enabled = False if action == 'status': no_refresh = True @@ -364,7 +366,8 @@ def firewall_upnp(action='status', no_refresh=False): if upnpc.getspecificportmapping(port, protocol): try: upnpc.deleteportmapping(port, protocol) - except: pass + except: + pass if not enabled: continue try: @@ -403,7 +406,7 @@ def firewall_upnp(action='status', no_refresh=False): if action == 'enable' and not enabled: raise MoulinetteError(errno.ENXIO, m18n.n('upnp_port_open_failed')) - return { 'enabled': enabled } + return {'enabled': enabled} def firewall_stop(): @@ -444,12 +447,14 @@ def _get_ssh_port(default=22): pass return default + def _update_firewall_file(rules): """Make a backup and write new rules to firewall file""" os.system("cp {0} {0}.old".format(firewall_file)) with open(firewall_file, 'w') as f: yaml.safe_dump(rules, f, default_flow_style=False) + def _on_rule_command_error(returncode, cmd, output): """Callback for rules commands error""" # Log error and continue commands execution diff --git a/src/yunohost/monitor.py b/src/yunohost/monitor.py index 315fb3a08..18089e328 100644 --- a/src/yunohost/monitor.py +++ b/src/yunohost/monitor.py @@ -44,8 +44,8 @@ from yunohost.domain import get_public_ip logger = getActionLogger('yunohost.monitor') -glances_uri = 'http://127.0.0.1:61209' -stats_path = '/var/lib/yunohost/stats' +glances_uri = 'http://127.0.0.1:61209' +stats_path = '/var/lib/yunohost/stats' crontab_path = '/etc/cron.d/yunohost-monitor' @@ -87,13 +87,13 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False): # Retrieve monitoring for unit(s) for u in units: if u == 'io': - ## Define setter + # Define setter if len(units) > 1: def _set(dn, dvalue): try: result[dn][u] = dvalue except KeyError: - result[dn] = { u: dvalue } + result[dn] = {u: dvalue} else: def _set(dn, dvalue): result[dn] = dvalue @@ -111,13 +111,13 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False): for dname in devices_names: _set(dname, 'not-available') elif u == 'filesystem': - ## Define setter + # Define setter if len(units) > 1: def _set(dn, dvalue): try: result[dn][u] = dvalue except KeyError: - result[dn] = { u: dvalue } + result[dn] = {u: dvalue} else: def _set(dn, dvalue): result[dn] = dvalue @@ -183,11 +183,11 @@ def monitor_network(units=None, human_readable=False): smtp_check = m18n.n('network_check_smtp_ko') try: - answers = dns.resolver.query(domain,'MX') + answers = dns.resolver.query(domain, 'MX') mx_check = {} i = 0 for server in answers: - mx_id = 'mx%s' %i + mx_id = 'mx%s' % i mx_check[mx_id] = server i = i + 1 except: @@ -307,7 +307,7 @@ def monitor_update_stats(period): stats = _retrieve_stats(period) if not stats: - stats = { 'disk': {}, 'network': {}, 'system': {}, 'timestamp': [] } + stats = {'disk': {}, 'network': {}, 'system': {}, 'timestamp': []} monitor = None # Get monitoring stats @@ -357,7 +357,7 @@ def monitor_update_stats(period): if 'usage' in stats['network'] and iname in stats['network']['usage']: curr = stats['network']['usage'][iname] net_usage[iname] = _append_to_stats(curr, values, 'time_since_update') - stats['network'] = { 'usage': net_usage, 'infos': monitor['network']['infos'] } + stats['network'] = {'usage': net_usage, 'infos': monitor['network']['infos']} # Append system stats for unit, values in monitor['system'].items(): @@ -421,7 +421,7 @@ def monitor_enable(with_stats=False): rules = ('*/5 * * * * root {cmd} day >> /dev/null\n' '3 * * * * root {cmd} week >> /dev/null\n' '6 */4 * * * root {cmd} month >> /dev/null').format( - cmd='/usr/bin/yunohost --quiet monitor update-stats') + cmd='/usr/bin/yunohost --quiet monitor update-stats') with open(crontab_path, 'w') as f: f.write(rules) @@ -530,7 +530,7 @@ def binary_to_human(n, customary=False): symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') prefix = {} for i, s in enumerate(symbols): - prefix[s] = 1 << (i+1)*10 + prefix[s] = 1 << (i + 1) * 10 for s in reversed(symbols): if n >= prefix[s]: value = float(n) / prefix[s] @@ -590,7 +590,7 @@ def _save_stats(stats, period, date=None): # Limit stats if date is None: t = stats['timestamp'] - limit = { 'day': 86400, 'week': 604800, 'month': 2419200 } + limit = {'day': 86400, 'week': 604800, 'month': 2419200} if (t[len(t) - 1] - t[0]) > limit[period]: begin = t[len(t) - 1] - limit[period] stats = _filter_stats(stats, begin) @@ -612,7 +612,7 @@ def _monitor_all(period=None, since=None): since -- Timestamp of the stats beginning """ - result = { 'disk': {}, 'network': {}, 'system': {} } + result = {'disk': {}, 'network': {}, 'system': {}} # Real-time stats if period == 'day' and since is None: @@ -697,7 +697,7 @@ def _calculate_stats_mean(stats): s[k] = _mean(v, t, ts) elif isinstance(v, list): try: - nums = [ float(x * t[i]) for i, x in enumerate(v) ] + nums = [float(x * t[i]) for i, x in enumerate(v)] except: pass else: diff --git a/src/yunohost/service.py b/src/yunohost/service.py index b1b776326..dcd3dee83 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -60,9 +60,9 @@ def service_add(name, status=None, log=None, runlevel=None): services = _get_services() if not status: - services[name] = { 'status': 'service' } + services[name] = {'status': 'service'} else: - services[name] = { 'status': status } + services[name] = {'status': status} if log is not None: services[name]['log'] = log @@ -202,7 +202,7 @@ def service_status(names=[]): status = None if 'status' not in services[name] or \ - services[name]['status'] == 'service': + services[name]['status'] == 'service': status = 'service %s status' % name else: status = str(services[name]['status']) @@ -211,7 +211,7 @@ def service_status(names=[]): if 'runlevel' in services[name].keys(): runlevel = int(services[name]['runlevel']) - result[name] = { 'status': 'unknown', 'loaded': 'unknown' } + result[name] = {'status': 'unknown', 'loaded': 'unknown'} # Retrieve service status try: @@ -261,7 +261,7 @@ def service_log(name, number=50): for log_path in log_list: if os.path.isdir(log_path): - for log in [ f for f in os.listdir(log_path) if os.path.isfile(os.path.join(log_path, f)) and f[-4:] == '.log' ]: + for log in [f for f in os.listdir(log_path) if os.path.isfile(os.path.join(log_path, f)) and f[-4:] == '.log']: result[os.path.join(log_path, log)] = _tail(os.path.join(log_path, log), int(number)) else: result[log_path] = _tail(log_path, int(number)) @@ -314,13 +314,14 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False, common_args = [1 if force else 0, 1 if dry_run else 0] # Execute hooks for pre-regen - pre_args = ['pre',] + common_args + pre_args = ['pre', ] + common_args + def _pre_call(name, priority, path, args): # create the pending conf directory for the service service_pending_path = os.path.join(pending_conf_dir, name) filesystem.mkdir(service_pending_path, 0755, True, uid='admin') # return the arguments to pass to the script - return pre_args + [service_pending_path,] + return pre_args + [service_pending_path, ] pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) # Update the services name @@ -336,8 +337,8 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False, # Iterate over services and process pending conf for service, conf_files in _get_pending_conf(names).items(): logger.info(m18n.n( - 'service_regenconf_pending_applying' if not dry_run else \ - 'service_regenconf_dry_pending_applying', + 'service_regenconf_pending_applying' if not dry_run else + 'service_regenconf_dry_pending_applying', service=service)) conf_hashes = _get_conf_hashes(service) @@ -444,8 +445,8 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False, continue elif not failed_regen: logger.success(m18n.n( - 'service_conf_updated' if not dry_run else \ - 'service_conf_would_be_updated', + 'service_conf_updated' if not dry_run else + 'service_conf_would_be_updated', service=service)) if succeed_regen and not dry_run: _update_conf_hashes(service, conf_hashes) @@ -461,14 +462,15 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False, return result # Execute hooks for post-regen - post_args = ['post',] + common_args + post_args = ['post', ] + common_args + def _pre_call(name, priority, path, args): # append coma-separated applied changes for the service if name in result and result[name]['applied']: regen_conf_files = ','.join(result[name]['applied'].keys()) else: regen_conf_files = '' - return post_args + [regen_conf_files,] + return post_args + [regen_conf_files, ] hook_callback('conf_regen', names, pre_callback=_pre_call) return result @@ -556,7 +558,8 @@ def _tail(file, n, offset=None): return lines[-to_read:offset and -offset or None] avg_line_length *= 1.3 - except IOError: return [] + except IOError: + return [] def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True): diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 7ea9852b7..9de9595f4 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -50,12 +50,12 @@ def user_list(auth, fields=None, filter=None, limit=None, offset=None): fields -- fields to fetch """ - user_attrs = { 'uid': 'username', - 'cn': 'fullname', - 'mail': 'mail', - 'maildrop': 'mail-forward', - 'mailuserquota': 'mailbox-quota' } - attrs = [ 'uid' ] + user_attrs = {'uid': 'username', + 'cn': 'fullname', + 'mail': 'mail', + 'maildrop': 'mail-forward', + 'mailuserquota': 'mailbox-quota'} + attrs = ['uid'] users = {} # Set default arguments values @@ -74,12 +74,12 @@ def user_list(auth, fields=None, filter=None, limit=None, offset=None): raise MoulinetteError(errno.EINVAL, m18n.n('field_invalid', attr)) else: - attrs = [ 'uid', 'cn', 'mail', 'mailuserquota' ] + attrs = ['uid', 'cn', 'mail', 'mailuserquota'] result = auth.search('ou=users,dc=yunohost,dc=org', filter, attrs) if len(result) > offset and limit > 0: - for user in result[offset:offset+limit]: + for user in result[offset:offset + limit]: entry = {} for attr, values in user.items(): try: @@ -88,7 +88,7 @@ def user_list(auth, fields=None, filter=None, limit=None, offset=None): pass uid = entry[user_attrs['uid']] users[uid] = entry - return { 'users' : users } + return {'users': users} def user_create(auth, username, firstname, lastname, mail, password, @@ -112,8 +112,8 @@ def user_create(auth, username, firstname, lastname, mail, password, # Validate uniqueness of username and mail in LDAP auth.validate_uniqueness({ - 'uid' : username, - 'mail' : mail + 'uid': username, + 'mail': mail }) # Validate uniqueness of username in system users @@ -125,10 +125,10 @@ def user_create(auth, username, firstname, lastname, mail, password, raise MoulinetteError(errno.EEXIST, m18n.n('system_username_exists')) # Check that the mail domain exists - if mail[mail.find('@')+1:] not in domain_list(auth)['domains']: + if mail[mail.find('@') + 1:] not in domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('mail_domain_unknown', - domain=mail[mail.find('@')+1:])) + domain=mail[mail.find('@') + 1:])) # Get random UID/GID uid_check = gid_check = 0 @@ -141,24 +141,24 @@ def user_create(auth, username, firstname, lastname, mail, password, fullname = '%s %s' % (firstname, lastname) rdn = 'uid=%s,ou=users' % username char_set = string.ascii_uppercase + string.digits - salt = ''.join(random.sample(char_set,8)) + salt = ''.join(random.sample(char_set, 8)) salt = '$1$' + salt + '$' user_pwd = '{CRYPT}' + crypt.crypt(str(password), salt) attr_dict = { - 'objectClass' : ['mailAccount', 'inetOrgPerson', 'posixAccount'], - 'givenName' : firstname, - 'sn' : lastname, - 'displayName' : fullname, - 'cn' : fullname, - 'uid' : username, - 'mail' : mail, - 'maildrop' : username, - 'mailuserquota' : mailbox_quota, - 'userPassword' : user_pwd, - 'gidNumber' : uid, - 'uidNumber' : uid, - 'homeDirectory' : '/home/' + username, - 'loginShell' : '/bin/false' + 'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount'], + 'givenName': firstname, + 'sn': lastname, + 'displayName': fullname, + 'cn': fullname, + 'uid': username, + 'mail': mail, + 'maildrop': username, + 'mailuserquota': mailbox_quota, + 'userPassword': user_pwd, + 'gidNumber': uid, + 'uidNumber': uid, + 'homeDirectory': '/home/' + username, + 'loginShell': '/bin/false' } # If it is the first user, add some aliases @@ -166,12 +166,12 @@ def user_create(auth, username, firstname, lastname, mail, password, with open('/etc/yunohost/current_host') as f: main_domain = f.readline().rstrip() aliases = [ - 'root@'+ main_domain, - 'admin@'+ main_domain, - 'webmaster@'+ main_domain, - 'postmaster@'+ main_domain, + 'root@' + main_domain, + 'admin@' + main_domain, + 'webmaster@' + main_domain, + 'postmaster@' + main_domain, ] - attr_dict['mail'] = [ attr_dict['mail'] ] + aliases + attr_dict['mail'] = [attr_dict['mail']] + aliases # If exists, remove the redirection from the SSO try: @@ -192,7 +192,6 @@ def user_create(auth, username, firstname, lastname, mail, password, raise MoulinetteError(errno.EPERM, m18n.n('ssowat_persistent_conf_write_error', error=e.strerror)) - if auth.add(rdn, attr_dict): # Invalidate passwd to take user creation into account subprocess.call(['nscd', '-i', 'passwd']) @@ -200,7 +199,7 @@ def user_create(auth, username, firstname, lastname, mail, password, # Update SFTP user group memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid'] memberlist.append(username) - if auth.update('cn=sftpusers,ou=groups', { 'memberUid': memberlist }): + if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}): try: # Attempt to create user home folder subprocess.check_call( @@ -210,12 +209,12 @@ def user_create(auth, username, firstname, lastname, mail, password, logger.warning(m18n.n('user_home_creation_failed'), exc_info=1) app_ssowatconf(auth) - #TODO: Send a welcome mail to user + # TODO: Send a welcome mail to user logger.success(m18n.n('user_created')) hook_callback('post_user_create', args=[username, mail, password, firstname, lastname]) - return { 'fullname' : fullname, 'username' : username, 'mail' : mail } + return {'fullname': fullname, 'username': username, 'mail': mail} raise MoulinetteError(169, m18n.n('user_creation_failed')) @@ -238,9 +237,11 @@ def user_delete(auth, username, purge=False): # Update SFTP user group memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid'] - try: memberlist.remove(username) - except: pass - if auth.update('cn=sftpusers,ou=groups', { 'memberUid': memberlist }): + try: + memberlist.remove(username) + except: + pass + if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}): if purge: subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) else: @@ -286,11 +287,11 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, # Get modifications from arguments if firstname: - new_attr_dict['givenName'] = firstname # TODO: Validate + new_attr_dict['givenName'] = firstname # TODO: Validate new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + user['sn'][0] if lastname: - new_attr_dict['sn'] = lastname # TODO: Validate + new_attr_dict['sn'] = lastname # TODO: Validate new_attr_dict['cn'] = new_attr_dict['displayName'] = user['givenName'][0] + ' ' + lastname if lastname and firstname: @@ -298,34 +299,34 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, if change_password: char_set = string.ascii_uppercase + string.digits - salt = ''.join(random.sample(char_set,8)) + salt = ''.join(random.sample(char_set, 8)) salt = '$1$' + salt + '$' new_attr_dict['userPassword'] = '{CRYPT}' + crypt.crypt(str(change_password), salt) if mail: - auth.validate_uniqueness({ 'mail': mail }) - if mail[mail.find('@')+1:] not in domains: + auth.validate_uniqueness({'mail': mail}) + if mail[mail.find('@') + 1:] not in domains: raise MoulinetteError(errno.EINVAL, m18n.n('mail_domain_unknown', - domain=mail[mail.find('@')+1:])) + domain=mail[mail.find('@') + 1:])) del user['mail'][0] new_attr_dict['mail'] = [mail] + user['mail'] if add_mailalias: if not isinstance(add_mailalias, list): - add_mailalias = [ add_mailalias ] + add_mailalias = [add_mailalias] for mail in add_mailalias: - auth.validate_uniqueness({ 'mail': mail }) - if mail[mail.find('@')+1:] not in domains: + auth.validate_uniqueness({'mail': mail}) + if mail[mail.find('@') + 1:] not in domains: raise MoulinetteError(errno.EINVAL, m18n.n('mail_domain_unknown', - domain=mail[mail.find('@')+1:])) + domain=mail[mail.find('@') + 1:])) user['mail'].append(mail) new_attr_dict['mail'] = user['mail'] if remove_mailalias: if not isinstance(remove_mailalias, list): - remove_mailalias = [ remove_mailalias ] + remove_mailalias = [remove_mailalias] for mail in remove_mailalias: if len(user['mail']) > 1 and mail in user['mail'][1:]: user['mail'].remove(mail) @@ -336,7 +337,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, if add_mailforward: if not isinstance(add_mailforward, list): - add_mailforward = [ add_mailforward ] + add_mailforward = [add_mailforward] for mail in add_mailforward: if mail in user['maildrop'][1:]: continue @@ -345,7 +346,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, if remove_mailforward: if not isinstance(remove_mailforward, list): - remove_mailforward = [ remove_mailforward ] + remove_mailforward = [remove_mailforward] for mail in remove_mailforward: if len(user['maildrop']) > 1 and mail in user['maildrop'][1:]: user['maildrop'].remove(mail) @@ -358,11 +359,11 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, new_attr_dict['mailuserquota'] = mailbox_quota if auth.update('uid=%s,ou=users' % username, new_attr_dict): - logger.success(m18n.n('user_updated')) - app_ssowatconf(auth) - return user_info(auth, username) + logger.success(m18n.n('user_updated')) + app_ssowatconf(auth) + return user_info(auth, username) else: - raise MoulinetteError(169, m18n.n('user_update_failed')) + raise MoulinetteError(169, m18n.n('user_update_failed')) def user_info(auth, username): @@ -378,9 +379,9 @@ def user_info(auth, username): ] if len(username.split('@')) is 2: - filter = 'mail='+ username + filter = 'mail=' + username else: - filter = 'uid='+ username + filter = 'uid=' + username result = auth.search('ou=users,dc=yunohost,dc=org', filter, user_attrs) @@ -436,8 +437,8 @@ def user_info(auth, username): storage_use += ' (%s%%)' % percentage result_dict['mailbox-quota'] = { - 'limit' : userquota if is_limited else m18n.n('unlimit'), - 'use' : storage_use + 'limit': userquota if is_limited else m18n.n('unlimit'), + 'use': storage_use } if result: @@ -445,8 +446,9 @@ def user_info(auth, username): else: raise MoulinetteError(167, m18n.n('user_info_failed')) + def _convertSize(num, suffix=''): - for unit in ['K','M','G','T','P','E','Z']: + for unit in ['K', 'M', 'G', 'T', 'P', 'E', 'Z']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 5be2103e5..2372e7442 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -424,6 +424,7 @@ def get_installed_version(*pkgnames, **kwargs): return versions[pkgnames[0]] return versions + def meets_version_specifier(pkgname, specifier): """Check if a package installed version meets specifier""" spec = SpecifierSet(specifier) diff --git a/src/yunohost/vendor/acme_tiny/acme_tiny.py b/src/yunohost/vendor/acme_tiny/acme_tiny.py index 0f4cb431f..d0ba33d1e 100644 --- a/src/yunohost/vendor/acme_tiny/acme_tiny.py +++ b/src/yunohost/vendor/acme_tiny/acme_tiny.py @@ -1,9 +1,21 @@ #!/usr/bin/env python -import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging +import argparse +import subprocess +import json +import os +import sys +import base64 +import binascii +import time +import hashlib +import re +import copy +import textwrap +import logging try: - from urllib.request import urlopen # Python 3 + from urllib.request import urlopen # Python 3 except ImportError: - from urllib2 import urlopen # Python 2 + from urllib2 import urlopen # Python 2 #DEFAULT_CA = "https://acme-staging.api.letsencrypt.org" DEFAULT_CA = "https://acme-v01.api.letsencrypt.org" @@ -12,6 +24,7 @@ LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.StreamHandler()) LOGGER.setLevel(logging.INFO) + def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): # helper function base64 encode for jose spec def _b64(b): @@ -26,7 +39,7 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): raise IOError("OpenSSL Error: {0}".format(err)) pub_hex, pub_exp = re.search( r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)", - out.decode('utf8'), re.MULTILINE|re.DOTALL).groups() + out.decode('utf8'), re.MULTILINE | re.DOTALL).groups() pub_exp = "{0:x}".format(int(pub_exp)) pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp header = { @@ -72,7 +85,7 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): common_name = re.search(r"Subject:.*? CN=([^\s,;/]+)", out.decode('utf8')) if common_name is not None: domains.add(common_name.group(1)) - subject_alt_names = re.search(r"X509v3 Subject Alternative Name: \n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE|re.DOTALL) + subject_alt_names = re.search(r"X509v3 Subject Alternative Name: \n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE | re.DOTALL) if subject_alt_names is not None: for san in subject_alt_names.group(1).split(", "): if san.startswith("DNS:"): @@ -165,6 +178,7 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): return """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format( "\n".join(textwrap.wrap(base64.b64encode(result).decode('utf8'), 64))) + def main(argv): parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, @@ -194,5 +208,5 @@ def main(argv): signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, log=LOGGER, CA=args.ca) sys.stdout.write(signed_crt) -if __name__ == "__main__": # pragma: no cover +if __name__ == "__main__": # pragma: no cover main(sys.argv[1:]) From 3f22f15dd392a8c612383fa060defe16a295ed34 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 18 Dec 2016 23:57:56 +0100 Subject: [PATCH 168/208] [fix] add timeout to fetchlist's wget --- 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 85693a0dd..a35ccb7e6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -99,7 +99,7 @@ def app_fetchlist(url=None, name=None): m18n.n('custom_appslist_name_required')) list_file = '%s/%s.json' % (repo_path, name) - if os.system('wget "%s" -O "%s.tmp"' % (url, list_file)) != 0: + if os.system('wget --timeout=30 "%s" -O "%s.tmp"' % (url, list_file)) != 0: os.remove('%s.tmp' % list_file) raise MoulinetteError(errno.EBADR, m18n.n('appslist_retrieve_error')) From 418c9b273614a7000b028a720df954416295525b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 21 Dec 2016 21:20:23 -0500 Subject: [PATCH 169/208] Check acme challenge conf exists in nginx when renewing cert --- locales/en.json | 1 + src/yunohost/certificate.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 468be0c1c..dc020645d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -264,5 +264,6 @@ "certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge: the nginx configuration file {filepath:s} is conflicting and should be removed first", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})", + "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_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})" } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 01852d2ec..f1d663eba 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -323,8 +323,16 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal continue # Does it expire soon? - if force or status["validity"] <= VALIDITY_LIMIT: - domain_list.append(domain) + if status["validity"] > VALIDITY_LIMIT and not force: + continue + + # Check ACME challenge configured for given domain + if not _check_acme_challenge_configuration(domain): + logger.warning(m18n.n( + 'certmanager_acme_not_configured_for_domain', domain=domain)) + continue + + domain_list.append(domain) if len(domain_list) == 0: logger.info("No certificate needs to be renewed.") @@ -350,6 +358,11 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_attempt_to_renew_nonLE_cert', domain=domain)) + # Check ACME challenge configured for given domain + if not _check_acme_challenge_configuration(domain): + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_acme_not_configured_for_domain', domain=domain)) + if staging: logger.warning( "Please note that you used the --staging option, and that no new certificate will actually be enabled !") @@ -362,6 +375,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal try: if not no_checks: _check_domain_is_ready_for_ACME(domain) + _fetch_and_enable_new_certificate(domain, staging) logger.success( @@ -487,6 +501,17 @@ location '/.well-known/acme-challenge' app_ssowatconf(auth) +def _check_acme_challenge_configuration(domain): + # Check nginx conf file exists + nginx_conf_folder = "/etc/nginx/conf.d/%s.d" % domain + nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder + + if not os.path.exists(nginx_conf_file): + return False + else: + return True + + def _fetch_and_enable_new_certificate(domain, staging=False): # Make sure tmp folder exists logger.debug("Making sure tmp folders exists...") From d6d7abd959d9555bbf494418b273a79b8fafd245 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 21 Dec 2016 21:38:49 -0500 Subject: [PATCH 170/208] Fix bad validity check.. --- 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 f1d663eba..74aa3c396 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -349,7 +349,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal status = _get_status(domain) # Does it expire soon? - if not force or status["validity"] <= VALIDITY_LIMIT: + if status["validity"] > VALIDITY_LIMIT and not force: raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_attempt_to_renew_valid_cert', domain=domain)) From ab279d9c6b717ec5317ed3854b43e4ef5e8e2143 Mon Sep 17 00:00:00 2001 From: zimo2001 Date: Sat, 17 Dec 2016 00:19:53 +0200 Subject: [PATCH 171/208] Update yunohost.conf In my nginx_error.log, the authentication errors are "helpers.lua", not "access.lua". --- data/templates/fail2ban/yunohost.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/fail2ban/yunohost.conf b/data/templates/fail2ban/yunohost.conf index 54d4a779f..3ca8f1c8f 100644 --- a/data/templates/fail2ban/yunohost.conf +++ b/data/templates/fail2ban/yunohost.conf @@ -14,7 +14,7 @@ # (?:::f{4,6}:)?(?P[\w\-.^_]+) # Values: TEXT # -failregex = access.lua:[1-9]+: authenticate\(\): Connection failed for: .*, client: +failregex = helpers.lua:[1-9]+: authenticate\(\): Connection failed for: .*, client: ^ -.*\"POST /yunohost/api/login HTTP/1.1\" 401 22 # Option: ignoreregex From 6cf57e3bb06759b1854c0bd845228f4ac1ad4c36 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 2 Jan 2017 15:12:22 +0100 Subject: [PATCH 172/208] [fix] Ignore dyndns option is not needed with small domain --- src/yunohost/tools.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 7cc221683..ac3d2c490 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -178,7 +178,8 @@ def tools_postinstall(domain, password, ignore_dyndns=False): Keyword argument: domain -- YunoHost main domain - ignore_dyndns -- Do not subscribe domain to a DynDNS service + ignore_dyndns -- Do not subscribe domain to a DynDNS service (only + needed for nohost.me, noho.st domains) password -- YunoHost admin password """ @@ -203,6 +204,10 @@ def tools_postinstall(domain, password, ignore_dyndns=False): else: raise MoulinetteError(errno.EEXIST, m18n.n('dyndns_unavailable')) + else: + dyndns = False + else: + dyndns = False logger.info(m18n.n('yunohost_installing')) @@ -296,7 +301,6 @@ def tools_postinstall(domain, password, ignore_dyndns=False): os.system('service yunohost-firewall start') service_regen_conf(force=True) - logger.success(m18n.n('yunohost_configured')) From 6277e6d7f62906a8f5d6258e880fccc88c415b9b Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 2 Jan 2017 22:03:55 +0100 Subject: [PATCH 173/208] [mod] logger was used instead of loggin #microdecision --- 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 7cc221683..ce9445daf 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -416,7 +416,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False): apt.progress.base.InstallProgress()) except Exception as e: failure = True - logging.warning('unable to upgrade packages: %s' % str(e)) + logger.warning('unable to upgrade packages: %s' % str(e)) logger.error(m18n.n('packages_upgrade_failed')) else: logger.info(m18n.n('done')) @@ -428,7 +428,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False): app_upgrade(auth) except Exception as e: failure = True - logging.warning('unable to upgrade apps: %s' % str(e)) + logger.warning('unable to upgrade apps: %s' % str(e)) logger.error(m18n.n('app_upgrade_failed')) if not failure: From b829de72180288707aa16e8435ecd39f1e9f9ac3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 3 Jan 2017 09:36:33 -0500 Subject: [PATCH 174/208] Adding check that domain is resolved locally for cert management --- locales/en.json | 1 + src/yunohost/certificate.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/locales/en.json b/locales/en.json index 468be0c1c..25d0f6542 100644 --- a/locales/en.json +++ b/locales/en.json @@ -253,6 +253,7 @@ "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_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_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_not_resolved_locally": "The domain {domain:s} cannot be resolved locally. 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_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_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", "certmanager_cert_install_success": "Successfully installed Let's Encrypt certificate for domain {domain:s}!", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 01852d2ec..652d6b653 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -785,6 +785,13 @@ def _check_domain_is_ready_for_ACME(domain): raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_domain_http_not_working', domain=domain)) + # Check if domain is resolved locally (Might happen despite the previous + # checks because of dns propagation ?... Acme-tiny won't work in that case, + # because it explicitly requests() the domain.) + if not _domain_is_resolved_locally(domain): + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_domain_not_resolved_locally', domain=domain)) + def _dns_ip_match_public_ip(public_ip, domain): try: @@ -809,6 +816,15 @@ def _domain_is_accessible_through_HTTP(ip, domain): return True +def _domain_is_resolved_locally(domain): + try: + requests.head("http://%s/" % domain) + except Exception: + return False + + return True + + def _name_self_CA(): ca_conf = os.path.join(SSL_DIR, "openssl.ca.cnf") From b419c8c5641f29284befc29b059ee37db0b74a59 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 3 Jan 2017 23:18:01 +0100 Subject: [PATCH 175/208] [fix] new rspamd version replace rspamd.socket with rspamd.service --- data/hooks/conf_regen/28-rmilter | 13 ++++--------- data/hooks/conf_regen/31-rspamd | 7 +++---- data/templates/postfix/main.cf | 2 +- data/templates/rmilter/rmilter.conf | 3 +-- data/templates/rmilter/rmilter.socket | 5 ----- 5 files changed, 9 insertions(+), 21 deletions(-) delete mode 100644 data/templates/rmilter/rmilter.socket diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter index 05f921e09..b783a5188 100755 --- a/data/hooks/conf_regen/28-rmilter +++ b/data/hooks/conf_regen/28-rmilter @@ -9,8 +9,6 @@ do_pre_regen() { install -D -m 644 rmilter.conf \ "${pending_dir}/etc/rmilter.conf" - install -D -m 644 rmilter.socket \ - "${pending_dir}/etc/systemd/system/rmilter.socket" } do_post_regen() { @@ -40,14 +38,11 @@ do_post_regen() { [ -z "$regen_conf_files" ] && exit 0 # reload systemd daemon - [[ "$regen_conf_files" =~ rmilter\.socket ]] && { - sudo systemctl -q daemon-reload - } + sudo systemctl -q daemon-reload - # ensure that the socket is listening and stop the service - it will be - # started again by the socket as needed - sudo systemctl -q start rmilter.socket - sudo systemctl -q stop rmilter.service 2>&1 || true + # Restart rmilter due to the rspamd update + # https://rspamd.com/announce/2016/08/01/rspamd-1.3.1.html + sudo systemctl -q restart rmilter.service } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index 327bedef1..afdfc1bf1 100755 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -25,10 +25,9 @@ do_post_regen() { sudo systemctl restart dovecot } - # ensure that the socket is listening and stop the service - it will be - # started again by the socket as needed - sudo systemctl -q start rspamd.socket - sudo systemctl -q stop rspamd.service 2>&1 || true + # Restart rspamd due to the upgrade + # https://rspamd.com/announce/2016/08/01/rspamd-1.3.1.html + sudo systemctl -q restart rspamd.service } FORCE=${2:-0} diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index f3597e136..ffea0aee3 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -141,7 +141,7 @@ smtp_reply_filter = pcre:/etc/postfix/smtp_reply_filter # Rmilter milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} milter_protocol = 6 -smtpd_milters = inet:localhost:11000 +smtpd_milters = unix:/var/tmp/rmilter.sock # Skip email without checking if milter has died milter_default_action = accept diff --git a/data/templates/rmilter/rmilter.conf b/data/templates/rmilter/rmilter.conf index d585b9217..c09a604b8 100644 --- a/data/templates/rmilter/rmilter.conf +++ b/data/templates/rmilter/rmilter.conf @@ -5,8 +5,7 @@ # pidfile - path to pid file pidfile = /run/rmilter/rmilter.pid; -# rmilter is socket-activated under systemd -bind_socket = fd:3; +bind_socket = unix:/var/tmp/rmilter.sock; # DKIM signing dkim { diff --git a/data/templates/rmilter/rmilter.socket b/data/templates/rmilter/rmilter.socket deleted file mode 100644 index dc3ae7a2a..000000000 --- a/data/templates/rmilter/rmilter.socket +++ /dev/null @@ -1,5 +0,0 @@ -.include /lib/systemd/system/rmilter.socket - -[Socket] -ListenStream= -ListenStream=127.0.0.1:11000 From 86f97d6f071602d5ca9402331fa9607a8de558b6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 4 Jan 2017 13:05:57 -0500 Subject: [PATCH 176/208] Changing the way to check domain is locally resolved --- locales/en.json | 2 +- src/yunohost/certificate.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 25d0f6542..387b8c2e8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -253,7 +253,7 @@ "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_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_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_not_resolved_locally": "The domain {domain:s} cannot be resolved locally. 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_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_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_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", "certmanager_cert_install_success": "Successfully installed Let's Encrypt certificate for domain {domain:s}!", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 652d6b653..eb1e44732 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -31,6 +31,7 @@ import grp import smtplib import requests import subprocess +import socket import dns.resolver import glob @@ -788,7 +789,7 @@ def _check_domain_is_ready_for_ACME(domain): # Check if domain is resolved locally (Might happen despite the previous # checks because of dns propagation ?... Acme-tiny won't work in that case, # because it explicitly requests() the domain.) - if not _domain_is_resolved_locally(domain): + if not _domain_is_resolved_locally(public_ip, domain): raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_domain_not_resolved_locally', domain=domain)) @@ -816,13 +817,13 @@ def _domain_is_accessible_through_HTTP(ip, domain): return True -def _domain_is_resolved_locally(domain): +def _domain_is_resolved_locally(public_ip, domain): try: - requests.head("http://%s/" % domain) - except Exception: + ip = socket.gethostbyname(domain) + except socket.error: return False - return True + return (ip in ["127.0.0.1", public_ip]) def _name_self_CA(): From 8737e69359745642ae12c341409db6483792af9e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 4 Jan 2017 13:41:00 -0500 Subject: [PATCH 177/208] Attempt to fix missing perm for metronome in weird cases --- src/yunohost/certificate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 01852d2ec..7ffd468b6 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -562,7 +562,9 @@ def _fetch_and_enable_new_certificate(domain, staging=False): _set_permissions(new_cert_folder, "root", "root", 0655) # Move the private key - shutil.move(domain_key_file, os.path.join(new_cert_folder, "key.pem")) + domain_key_file_finaldest = os.path.join(new_cert_folder, "key.pem") + shutil.move(domain_key_file, domain_key_file_finaldest) + _set_permissions(domain_key_file_finaldest, "root", "metronome", 0640) # Write the cert domain_cert_file = os.path.join(new_cert_folder, "crt.pem") From 047026df6caa9fad6b5e4c45216a9a57a65968f6 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 8 Jan 2017 05:28:13 +0100 Subject: [PATCH 178/208] [mod] remove () --- 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 eb1e44732..b91bc74b6 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -823,7 +823,7 @@ def _domain_is_resolved_locally(public_ip, domain): except socket.error: return False - return (ip in ["127.0.0.1", public_ip]) + return ip in ["127.0.0.1", public_ip] def _name_self_CA(): From d665984789058c99ca046f707f9ecf7d20b537cd Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 8 Jan 2017 05:31:11 +0100 Subject: [PATCH 179/208] [mod] add debugging code to avoid becoming mad when things goes wrong --- src/yunohost/certificate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index b91bc74b6..a53f5f669 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -820,9 +820,11 @@ def _domain_is_accessible_through_HTTP(ip, domain): def _domain_is_resolved_locally(public_ip, domain): try: ip = socket.gethostbyname(domain) - except socket.error: + except socket.error as e: + logger.debug("Couldn't get domain '%s' ip because: %s" % (domain, e)) return False + logger.debug("Domain '%s' ip is %s, except it to be 127.0.0.1 or %s" % (domain, public_ip)) return ip in ["127.0.0.1", public_ip] From 4b9d2526db388baf9e4710806c358b155297db6c Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 8 Jan 2017 05:36:39 +0100 Subject: [PATCH 180/208] [mod] more debug logging --- src/yunohost/certificate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 9d52f3a5d..5f851471c 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -838,7 +838,8 @@ def _dns_ip_match_public_ip(public_ip, domain): def _domain_is_accessible_through_HTTP(ip, domain): try: requests.head("http://" + ip, headers={"Host": domain}) - except Exception: + except Exception as e: + logger.debug("Couldn't reach domain '%s' by requesting this ip '%s' because: %s" % (domain, ip, e)) return False return True From d4e4d01b4f1641e6bf9bea8388fd91565955a2f3 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 9 Jan 2017 13:37:34 +0100 Subject: [PATCH 181/208] [fix] Remove residual rmilter socket file --- data/hooks/conf_regen/28-rmilter | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter index b783a5188..db42371e8 100755 --- a/data/hooks/conf_regen/28-rmilter +++ b/data/hooks/conf_regen/28-rmilter @@ -9,6 +9,10 @@ do_pre_regen() { install -D -m 644 rmilter.conf \ "${pending_dir}/etc/rmilter.conf" + # Remove old socket file (we stopped using it, since rspamd 1.3.1) + # Regen-conf system need an empty file to delete it + install -D -m 644 /dev/null \ + "${pending_dir}/etc/systemd/system/rmilter.socket" } do_post_regen() { From 2cf4768aa17fca02f1d0e15f6f980c4a279bc931 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 9 Jan 2017 15:34:52 +0100 Subject: [PATCH 182/208] [fix] Postfix can't access rmilter socket due to chroot --- data/hooks/conf_regen/28-rmilter | 5 +++++ data/templates/postfix/main.cf | 2 +- data/templates/rmilter/rmilter.conf | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter index db42371e8..011856cd6 100755 --- a/data/hooks/conf_regen/28-rmilter +++ b/data/hooks/conf_regen/28-rmilter @@ -39,6 +39,11 @@ do_post_regen() { sudo chown _rmilter /etc/dkim/*.mail.key sudo chmod 400 /etc/dkim/*.mail.key + # fix rmilter socket permission (postfix is chrooted in /var/spool/postfix ) + sudo mkdir -p /var/spool/postfix/run/rmilter + sudo chown -R postfix:_rmilter /var/spool/postfix/run/rmilter + sudo chmod g+w /var/spool/postfix/run/rmilter + [ -z "$regen_conf_files" ] && exit 0 # reload systemd daemon diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index ffea0aee3..b0b2688d9 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -141,7 +141,7 @@ smtp_reply_filter = pcre:/etc/postfix/smtp_reply_filter # Rmilter milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} milter_protocol = 6 -smtpd_milters = unix:/var/tmp/rmilter.sock +smtpd_milters = unix:/run/rmilter/rmilter.sock # Skip email without checking if milter has died milter_default_action = accept diff --git a/data/templates/rmilter/rmilter.conf b/data/templates/rmilter/rmilter.conf index c09a604b8..829d76418 100644 --- a/data/templates/rmilter/rmilter.conf +++ b/data/templates/rmilter/rmilter.conf @@ -5,7 +5,7 @@ # pidfile - path to pid file pidfile = /run/rmilter/rmilter.pid; -bind_socket = unix:/var/tmp/rmilter.sock; +bind_socket = unix:/var/spool/postfix/run/rmilter/rmilter.sock; # DKIM signing dkim { From 7f5604653b1499df861825abe9bfbb5b9ce06c3a Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 9 Jan 2017 18:43:40 +0100 Subject: [PATCH 183/208] Update changelog for 2.5.3 release --- debian/changelog | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/debian/changelog b/debian/changelog index 248c934fd..9004d14bd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,37 @@ +yunohost (2.5.3) testing; urgency=low + + Love: + * [enh][love] Add CONTRIBUTORS.md + + LE: + * Check acme challenge conf exists in nginx when renewing cert + * Fix bad validity check.. + + Fix a situation where to domain for the LE cert can't be locally resolved: + * Adding check that domain is resolved locally for cert management + * Changing the way to check domain is locally resolved + + Fix a situation where a cert could end up with bad perms for metronome: + * Attempt to fix missing perm for metronome in weird cases + + Rspamd cannot be activate on socket anymore: + * [fix] new rspamd version replace rspamd.socket with rspamd.service + * [fix] Remove residual rmilter socket file + * [fix] Postfix can't access rmilter socket due to chroot + + Various: + * fix fail2ban rules to take into account failed loggin on ssowat + * [fix] Ignore dyndns option is not needed with small domain + * [enh] add yaml syntax check in travis.yml + * [mod] autopep8 on all files that aren't concerned by a PR + * [fix] add timeout to fetchlist's wget + + Thanks to all contributors: Aleks, Bram, ju, ljf, opi, zimo2001 and to the + people who are participating to the beta and giving us feedback <3 + + + -- Laurent Peuch Mon, 09 Jan 2017 18:38:30 +0100 + yunohost (2.5.2) testing; urgency=low LDAP admin user: From 8a70c33c40ce0d2add85cf5051ed8b730d8aa3e4 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 10 Jan 2017 02:56:35 +0100 Subject: [PATCH 184/208] [fix] I'm a fucking idiot --- 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 5f851471c..49de94996 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -852,7 +852,7 @@ def _domain_is_resolved_locally(public_ip, domain): logger.debug("Couldn't get domain '%s' ip because: %s" % (domain, e)) return False - logger.debug("Domain '%s' ip is %s, except it to be 127.0.0.1 or %s" % (domain, public_ip)) + logger.debug("Domain '%s' ip is %s, except it to be 127.0.0.1 or %s" % (domain, ip, public_ip)) return ip in ["127.0.0.1", public_ip] From b1885ddf64c7ae7687f101916b08a8c83db35607 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 10 Jan 2017 02:56:35 +0100 Subject: [PATCH 185/208] [fix] I'm a fucking idiot --- 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 5f851471c..49de94996 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -852,7 +852,7 @@ def _domain_is_resolved_locally(public_ip, domain): logger.debug("Couldn't get domain '%s' ip because: %s" % (domain, e)) return False - logger.debug("Domain '%s' ip is %s, except it to be 127.0.0.1 or %s" % (domain, public_ip)) + logger.debug("Domain '%s' ip is %s, except it to be 127.0.0.1 or %s" % (domain, ip, public_ip)) return ip in ["127.0.0.1", public_ip] From c1b5fbab61923ab08d54dfba4e5ad1a5751fc462 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 10 Jan 2017 02:59:24 +0100 Subject: [PATCH 186/208] Update changelog for 2.5.3.1 release --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9004d14bd..2a719fc83 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (2.5.3.1) testing; urgency=low + + * super quickfix release for a typo that break LE certificates + + -- Laurent Peuch Tue, 10 Jan 2017 02:58:56 +0100 + yunohost (2.5.3) testing; urgency=low Love: From 73d1630459b3c0256007cf441df6046abe6889ee Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 13 Jan 2017 02:09:14 +0100 Subject: [PATCH 187/208] [enh] Compatibility with debian stretch --- debian/control | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian/control b/debian/control index 47f0e61b2..357b2c766 100644 --- a/debian/control +++ b/debian/control @@ -16,12 +16,12 @@ Depends: ${python:Depends}, ${misc:Depends} , glances , dnsutils, bind9utils, unzip, git, curl, cron , ca-certificates, netcat-openbsd, iproute - , mariadb-server | mysql-server, php5-mysql | php5-mysqlnd + , mariadb-server | mysql-server, php5-mysql | php5-mysqlnd | php-mysql | php-mysqlnd , slapd, ldap-utils, sudo-ldap, libnss-ldapd, nscd , postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail , dovecot-ldap, dovecot-lmtpd, dovecot-managesieved , dovecot-antispam, fail2ban - , nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl + , nginx-extras (>=1.6.2), php5-fpm | php-fpm, php5-ldap | php-ldap, php5-intl | php-intl , dnsmasq, openssl, avahi-daemon , ssowat, metronome , rspamd (>= 1.2.0), rmilter (>=1.7.0), redis-server, opendkim-tools @@ -29,7 +29,7 @@ Depends: ${python:Depends}, ${misc:Depends} Recommends: yunohost-admin , openssh-server, ntp, inetutils-ping | iputils-ping , bash-completion, rsyslog, etckeeper - , php5-gd, php5-curl, php-gettext, php5-mcrypt + , php5-gd | php-gd, php5-curl | php-curl, php5-gettext | php-gettext, php5-mcrypt | php-mcrypt , python-pip , unattended-upgrades , libdbd-ldap-perl, libnet-dns-perl From 905a04146bb8b958b3858920581a5e0046579ad4 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 16 Jan 2017 09:24:41 +0100 Subject: [PATCH 188/208] [mod] start putting timeout in certificate code --- src/yunohost/certificate.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 49de94996..ccd1a810f 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -37,6 +37,8 @@ import glob from OpenSSL import crypto from datetime import datetime +from requests.exceptions import Timeout + from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate from moulinette.core import MoulinetteError @@ -567,7 +569,11 @@ def _fetch_and_enable_new_certificate(domain, staging=False): raise MoulinetteError(errno.EINVAL, m18n.n( 'certmanager_cert_signing_failed')) - intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL).text + try: + intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL, timeout=30).text + except Timeout: + # XXX what should we do here? retry? + pass # Now save the key and signed certificate logger.info("Saving the key and signed certificate...") @@ -837,7 +843,10 @@ def _dns_ip_match_public_ip(public_ip, domain): def _domain_is_accessible_through_HTTP(ip, domain): try: - requests.head("http://" + ip, headers={"Host": domain}) + requests.head("http://" + ip, headers={"Host": domain}, timeout=30) + except Timeout as e: + # XXX what should we do here? retry? + pass except Exception as e: logger.debug("Couldn't reach domain '%s' by requesting this ip '%s' because: %s" % (domain, ip, e)) return False From 3be7aff0655d38d4ceba2f9aa54666c1071769a4 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 23 Jan 2017 11:46:21 +0100 Subject: [PATCH 189/208] Revert "[enh] Compatibility with debian stretch" This reverts commit 73d1630459b3c0256007cf441df6046abe6889ee. --- debian/control | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian/control b/debian/control index 357b2c766..47f0e61b2 100644 --- a/debian/control +++ b/debian/control @@ -16,12 +16,12 @@ Depends: ${python:Depends}, ${misc:Depends} , glances , dnsutils, bind9utils, unzip, git, curl, cron , ca-certificates, netcat-openbsd, iproute - , mariadb-server | mysql-server, php5-mysql | php5-mysqlnd | php-mysql | php-mysqlnd + , 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 , dovecot-ldap, dovecot-lmtpd, dovecot-managesieved , dovecot-antispam, fail2ban - , nginx-extras (>=1.6.2), php5-fpm | php-fpm, php5-ldap | php-ldap, php5-intl | php-intl + , nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl , dnsmasq, openssl, avahi-daemon , ssowat, metronome , rspamd (>= 1.2.0), rmilter (>=1.7.0), redis-server, opendkim-tools @@ -29,7 +29,7 @@ Depends: ${python:Depends}, ${misc:Depends} Recommends: yunohost-admin , openssh-server, ntp, inetutils-ping | iputils-ping , bash-completion, rsyslog, etckeeper - , php5-gd | php-gd, php5-curl | php-curl, php5-gettext | php-gettext, php5-mcrypt | php-mcrypt + , php5-gd, php5-curl, php-gettext, php5-mcrypt , python-pip , unattended-upgrades , libdbd-ldap-perl, libnet-dns-perl From 656b2e61ba3ef1056bdb4154f86076188037b8bd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 25 Jan 2017 12:03:30 -0500 Subject: [PATCH 190/208] Implement timeout exceptions --- locales/en.json | 2 ++ src/yunohost/certificate.py | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index e145a1d38..cacac5d18 100644 --- a/locales/en.json +++ b/locales/en.json @@ -266,5 +266,7 @@ "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})", "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_http_check_timeout" : "Timed out when server tried to contact itself through HTTP using public IP address. You may be experiencing hairpinning or the firewall/router ahead of your server is misconfigured.", + "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_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})" } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index ccd1a810f..386894f65 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -572,8 +572,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False): try: intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL, timeout=30).text except Timeout: - # XXX what should we do here? retry? - pass + raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_couldnt_fetch_intermediate_cert')) # Now save the key and signed certificate logger.info("Saving the key and signed certificate...") @@ -843,10 +842,10 @@ def _dns_ip_match_public_ip(public_ip, domain): def _domain_is_accessible_through_HTTP(ip, domain): try: - requests.head("http://" + ip, headers={"Host": domain}, timeout=30) + requests.head("http://" + ip, headers={"Host": domain}, timeout=10) except Timeout as e: - # XXX what should we do here? retry? - pass + logger.warning(m18n.n('certmanager_http_check_timeout')) + return False except Exception as e: logger.debug("Couldn't reach domain '%s' by requesting this ip '%s' because: %s" % (domain, ip, e)) return False From cd88428248494fac1c22db4d53887fb5b25cc575 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 25 Jan 2017 18:38:30 -0500 Subject: [PATCH 191/208] Implementing opi's comments --- locales/en.json | 2 +- src/yunohost/certificate.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index cacac5d18..5e9d02e06 100644 --- a/locales/en.json +++ b/locales/en.json @@ -266,7 +266,7 @@ "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})", "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_http_check_timeout" : "Timed out when server tried to contact itself through HTTP using public IP address. You may be experiencing hairpinning or the firewall/router ahead of your server is misconfigured.", + "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 or the firewall/router ahead of your server is misconfigured.", "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_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})" } diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 386894f65..bd7d02962 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -571,7 +571,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False): try: intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL, timeout=30).text - except Timeout: + except Timeout as e: raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_couldnt_fetch_intermediate_cert')) # Now save the key and signed certificate @@ -844,7 +844,7 @@ def _domain_is_accessible_through_HTTP(ip, domain): try: requests.head("http://" + ip, headers={"Host": domain}, timeout=10) except Timeout as e: - logger.warning(m18n.n('certmanager_http_check_timeout')) + logger.warning(m18n.n('certmanager_http_check_timeout', domain=domain, ip=ip)) return False except Exception as e: logger.debug("Couldn't reach domain '%s' by requesting this ip '%s' because: %s" % (domain, ip, e)) From 4486b24388020e566b79688e0c0cce60edab3edf Mon Sep 17 00:00:00 2001 From: JimboJoe Date: Tue, 31 Jan 2017 15:28:04 +0100 Subject: [PATCH 192/208] ynh_backup: Fix error message when source path doesn't exist --- data/helpers.d/filesystem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 8a2bd9aff..856b7cb86 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -17,7 +17,7 @@ ynh_backup() { # validate arguments [[ -e "${SRCPATH}" ]] || { - echo "Source path '${DESTPATH}' does not exist" >&2 + echo "Source path '${SRCPATH}' does not exist" >&2 return 1 } From e8c916197903646821479131cce0898f6fea518a Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 29 Nov 2016 14:00:15 +0100 Subject: [PATCH 193/208] Remove helper ynh_mkdir_tmp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le helper `ynh_mkdir_tmp` est inutile. Il existe la commande `mktemp -d` pour créer des dossiers temporaires. --- data/helpers.d/filesystem | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 856b7cb86..3517f5e44 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -62,15 +62,3 @@ ynh_bind_or_cp() { echo "This helper is deprecated, you should use ynh_backup instead" >&2 ynh_backup "$1" "$2" 1 "$NO_ROOT" } - -# Create a directory under /tmp -# -# usage: ynh_mkdir_tmp -# | ret: the created directory path -ynh_mkdir_tmp() { - TMPDIR="/tmp/$(ynh_string_random 6)" - while [ -d $TMPDIR ]; do - TMPDIR="/tmp/$(ynh_string_random 6)" - done - mkdir -p "$TMPDIR" && echo "$TMPDIR" -} From cfef34a39f05ff38a00b7d8c7a9f1a445c6e531b Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 29 Nov 2016 14:19:19 +0100 Subject: [PATCH 194/208] Update filesystem --- data/helpers.d/filesystem | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 3517f5e44..9933563cc 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -62,3 +62,13 @@ ynh_bind_or_cp() { echo "This helper is deprecated, you should use ynh_backup instead" >&2 ynh_backup "$1" "$2" 1 "$NO_ROOT" } + +# Create a directory under /tmp +# +# Deprecated helper +# +# usage: ynh_mkdir_tmp +# | ret: the created directory path +ynh_mkdir_tmp() { + mktemp -d +} From 671fd047fb4317b01723d57a3fa673d9916cac64 Mon Sep 17 00:00:00 2001 From: opi Date: Tue, 29 Nov 2016 15:27:51 +0100 Subject: [PATCH 195/208] [enh] Add warning about deprecated ynh_mkdir_tmp helper --- data/helpers.d/filesystem | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 9933563cc..27a016e63 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -70,5 +70,6 @@ ynh_bind_or_cp() { # usage: ynh_mkdir_tmp # | ret: the created directory path ynh_mkdir_tmp() { + echo "This helper is deprecated, you should use 'mktemp -d' instead." >&2 mktemp -d } From d1199b62ab11174f94f50d87c620356d36a17262 Mon Sep 17 00:00:00 2001 From: Juanu Date: Tue, 13 Dec 2016 15:01:21 +0100 Subject: [PATCH 196/208] [i18n] Translated using Weblate (Spanish) Currently translated at 100.0% (264 of 264 strings) --- locales/es.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/locales/es.json b/locales/es.json index 093fb17fa..9a7e4314e 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,12 +1,12 @@ { - "action_invalid": "Acción no válida '{action:s}'", + "action_invalid": "Acción no válida '{action:s} 1'", "admin_password": "Contraseña administrativa", - "admin_password_change_failed": "No se pudo cambiar la contraseña", + "admin_password_change_failed": "No se puede cambiar la contraseña", "admin_password_changed": "La contraseña administrativa ha sido cambiada", - "app_already_installed": "{app:s} ya está instalada", - "app_argument_choice_invalid": "Opción no válida para el argumento '{name:s}', deber una de {choices:s}", - "app_argument_invalid": "Valor no válido para el argumento '{name:s}': {error:s}", - "app_argument_required": "Se requiere el argumento '{name:s}'", + "app_already_installed": "{app:s} 2 ya está instalada", + "app_argument_choice_invalid": "Opción no válida para el argumento '{name:s} 3', deber una de {choices:s} 4", + "app_argument_invalid": "Valor no válido para el argumento '{name:s} 5': {error:s} 6", + "app_argument_required": "Se requiere el argumento '{name:s} 7'", "app_extraction_failed": "No se pudieron extraer los archivos de instalación", "app_id_invalid": "Id de la aplicación no válida", "app_incompatible": "La aplicación no es compatible con su versión de YunoHost", @@ -15,9 +15,9 @@ "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_no_upgrade": "No hay aplicaciones para actualizar", - "app_not_correctly_installed": "La aplicación {app:s} parece estar incorrectamente instalada", - "app_not_installed": "{app:s} no está instalada", - "app_not_properly_removed": "La {app:s} no ha sido desinstalada correctamente", + "app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada", + "app_not_installed": "{app:s} 9 no está instalada", + "app_not_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente", "app_package_need_update": "Es necesario actualizar el paquete de la aplicación debido a los cambios en YunoHost", "app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ", "app_removed": "{app:s} ha sido eliminada", @@ -71,7 +71,7 @@ "diagnostic_monitor_network_error": "No se puede monitorizar la red: {error}", "diagnostic_monitor_system_error": "No se puede monitorizar el sistema: {error}", "diagnostic_no_apps": "Aplicación no instalada", - "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecuta 'apt-get remove bind9 && apt-get install dnsmasq'", + "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cert_gen_failed": "No se pudo crear el certificado", "domain_created": "El dominio ha sido creado", "domain_creation_failed": "No se pudo crear el dominio", @@ -84,11 +84,11 @@ "domain_uninstall_app_first": "Una o más aplicaciones están instaladas en este dominio. Debe desinstalarlas antes de eliminarlo.", "domain_unknown": "Dominio desconocido", "domain_zone_exists": "El archivo de zona del DNS ya existe", - "domain_zone_not_found": "No se ha encontrado el archivo de zona DNS para el dominio [:s]", + "domain_zone_not_found": "No se ha encontrado el archivo de zona del DNS para el dominio [:s]", "done": "Hecho.", "downloading": "Descargando...", "dyndns_cron_installed": "La tarea cron para DynDNS ha sido instalada", - "dyndns_cron_remove_failed": "No se pudo eliminar la tarea del cron DynDNS", + "dyndns_cron_remove_failed": "No se pudo eliminar la tarea cron DynDNS", "dyndns_cron_removed": "La tarea cron DynDNS ha sido eliminada", "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en el DynDNS", "dyndns_ip_updated": "Su dirección IP ha sido actualizada en el DynDNS", @@ -193,13 +193,13 @@ "service_conf_file_updated": "El archivo de configuración '{conf}' ha sido actualizado", "service_conf_up_to_date": "La configuración del servicio '{service}' ya está actualizada", "service_conf_updated": "La configuración ha sido actualizada para el servicio '{service}'", - "service_conf_would_be_updated": "La configuración podría haber sido actualizada para el servicio '{service}'", + "service_conf_would_be_updated": "La configuración podría haber sido actualizada para el servicio '{service} 1'", "service_disable_failed": "No se pudo deshabilitar el servicio '{service:s}'", "service_disabled": "El servicio '{service:s}' ha sido deshabilitado", "service_enable_failed": "No se pudo habilitar el servicio '{service:s}'", "service_enabled": "El servicio '{service:s}' ha sido habilitado", "service_no_log": "No hay ningún registro para el servicio '{service:s}'", - "service_regenconf_dry_pending_applying": "Comprobando configuración que podría haber sido aplicada al servicio '{service}'...", + "service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service} 1'...", "service_regenconf_failed": "No se puede regenerar la configuración para el servicio(s): {services}", "service_regenconf_pending_applying": "Aplicando la configuración para el servicio '{service}'...", "service_remove_failed": "No se pudo desinstalar el servicio '{service:s}'", @@ -242,13 +242,13 @@ "yunohost_installing": "Instalando YunoHost...", "yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'", "ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador", - "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón", + "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón de correo", "ssowat_persistent_conf_read_error": "Error al leer la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", "ssowat_persistent_conf_write_error": "Error al guardar la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s} 1! (Use --force para omitir este mensaje)", "certmanager_domain_unknown": "Dominio desconocido {domain:s} 1", - "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} 1 no es autofirmado. ¿Estás seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", - "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para {domain:s} 1 falla de alguna manera...", + "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} 1 no es autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", + "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para el dominio {domain:s} 1 ha fallado de alguna manera...", "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} 1 no está emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", "certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} 1 no está a punto de expirar! Utilice --force para omitir este mensaje", "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} 1 a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", @@ -258,7 +258,7 @@ "certmanager_cert_install_success_selfsigned": "¡Se ha instalado correctamente un certificado autofirmado para el dominio {domain:s} 1!", "certmanager_cert_install_success": "Se ha instalado correctamente un certificado Let's Encrypt para el dominio {domain:s} 1!", "certmanager_cert_renew_success": "¡Se ha renovado correctamente el certificado Let's Encrypt para el dominio {domain:s} 1!", - "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada y esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar certificados para todos los dominios con un certificado Let's Encrypt o un certificado autofirmado", + "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada y esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar los certificados para todos los dominios con un certificado Let's Encrypt o con un certificado autofirmado", "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para el conjunto de dominios {domain:s} 1. Por favor, inténtelo de nuevo más tarde. Consulte https://letsencrypt.org/docs/rate-limits/ para obtener más detalles", "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", "certmanager_no_cert_file": "No se puede leer el certificado para el dominio {domain:s} 1 (archivo: {file:s} 2)", From e6b10f2af1c17a02f240b214b50b25e86c19ba58 Mon Sep 17 00:00:00 2001 From: Juanu Date: Tue, 13 Dec 2016 15:53:25 +0100 Subject: [PATCH 197/208] [i18n] Translated using Weblate (Spanish) Currently translated at 100.0% (264 of 264 strings) --- locales/es.json | 68 ++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/locales/es.json b/locales/es.json index 9a7e4314e..b3dd4f14e 100644 --- a/locales/es.json +++ b/locales/es.json @@ -113,16 +113,16 @@ "hook_list_by_invalid": "Propiedad no válida para listar por hook", "hook_name_unknown": "Nombre de hook desconocido '{name:s}'", "installation_complete": "Instalación finalizada", - "installation_failed": "No pudo realizar la instalación", + "installation_failed": "No se pudo realizar la instalación", "ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción", "iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción", - "ldap_initialized": "LDAP iniciado", + "ldap_initialized": "Se ha inicializado LDAP", "license_undefined": "indefinido", "mail_alias_remove_failed": "No se pudo eliminar el alias de correo '{mail:s}'", "mail_domain_unknown": "El dominio de correo '{domain:s}' es desconocido", "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo '{mail:s}'", "maindomain_change_failed": "No se pudo cambiar el dominio principal", - "maindomain_changed": "El dominio principal ha sido cambiado", + "maindomain_changed": "Se ha cambiado el dominio principal", "monitor_disabled": "La monitorización del sistema ha sido deshabilitada", "monitor_enabled": "La monitorización del sistema ha sido habilitada", "monitor_glances_con_failed": "No se pudo conectar al servidor Glances", @@ -130,13 +130,13 @@ "monitor_period_invalid": "Período de tiempo no válido", "monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticas", "monitor_stats_no_update": "No hay estadísticas de monitorización para actualizar", - "monitor_stats_period_unavailable": "No hay estadísticas para ese período", + "monitor_stats_period_unavailable": "No hay estadísticas para el período", "mountpoint_unknown": "Punto de montaje desconocido", "mysql_db_creation_failed": "No se pudo crear la base de datos MySQL", "mysql_db_init_failed": "No se pudo iniciar la base de datos MySQL", - "mysql_db_initialized": "La base de datos MySQL ha sido iniciada", + "mysql_db_initialized": "La base de datos MySQL ha sido inicializada", "network_check_mx_ko": "El registro DNS MX no está configurado", - "network_check_smtp_ko": "El puerto 25 (SMTP) para el correo saliente parece estar bloqueado en su red", + "network_check_smtp_ko": "El puerto 25 (SMTP) para el correo saliente parece estar bloqueado por su red", "network_check_smtp_ok": "El puerto de salida del correo electrónico (25, SMTP) no está bloqueado", "new_domain_required": "Debe proporcionar el nuevo dominio principal", "no_appslist_found": "No se ha encontrado ninguna lista de aplicaciones", @@ -145,13 +145,13 @@ "no_restore_script": "No se ha encontrado un script de restauración para la aplicación '{app:s}'", "not_enough_disk_space": "No hay suficiente espacio en '{path:s}'", "package_not_installed": "El paquete '{pkgname}' no está instalado", - "package_unexpected_error": "Un error inesperado procesando el paquete '{pkgname}'", + "package_unexpected_error": "Ha ocurrido un error inesperado procesando el paquete '{pkgname}'", "package_unknown": "Paquete desconocido '{pkgname}'", - "packages_no_upgrade": "No hay paquetes que actualizar", + "packages_no_upgrade": "No hay paquetes para actualizar", "packages_upgrade_critical_later": "Los paquetes críticos ({packages:s}) serán actualizados más tarde", "packages_upgrade_failed": "No se pudieron actualizar todos los paquetes", - "path_removal_failed": "No se pudo borrar la ruta {:s}", - "pattern_backup_archive_name": "Debe que ser un nombre de archivo válido, solo se admiten caracteres alfanuméricos y los guiones -_", + "path_removal_failed": "No se pudo eliminar la ruta {:s}", + "pattern_backup_archive_name": "Debe ser un nombre de archivo válido, solo se admiten caracteres alfanuméricos y los guiones -_", "pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)", "pattern_email": "Debe ser una dirección de correo electrónico válida (por ejemplo, alguien@dominio.org)", "pattern_firstname": "Debe ser un nombre válido", @@ -180,7 +180,7 @@ "restore_running_hooks": "Ejecutando los hooks de restauración...", "service_add_failed": "No se pudo añadir el servicio '{service:s}'", "service_added": "Servicio '{service:s}' ha sido añadido", - "service_already_started": "El servicio '{service:s}' ya ha sido iniciado", + "service_already_started": "El servicio '{service:s}' ya ha sido inicializado", "service_already_stopped": "El servicio '{service:s}' ya ha sido detenido", "service_cmd_exec_failed": "No se pudo ejecutar el comando '{command:s}'", "service_conf_file_backed_up": "Se ha realizado una copia de seguridad del archivo de configuración '{conf}' en '{backup}'", @@ -199,9 +199,9 @@ "service_enable_failed": "No se pudo habilitar el servicio '{service:s}'", "service_enabled": "El servicio '{service:s}' ha sido habilitado", "service_no_log": "No hay ningún registro para el servicio '{service:s}'", - "service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service} 1'...", + "service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service}'...", "service_regenconf_failed": "No se puede regenerar la configuración para el servicio(s): {services}", - "service_regenconf_pending_applying": "Aplicando la configuración para el servicio '{service}'...", + "service_regenconf_pending_applying": "Aplicando la configuración pendiente para el servicio '{service}'...", "service_remove_failed": "No se pudo desinstalar el servicio '{service:s}'", "service_removed": "El servicio '{service:s}' ha sido desinstalado", "service_start_failed": "No se pudo iniciar el servicio '{service:s}'", @@ -219,7 +219,7 @@ "unit_unknown": "Unidad desconocida '{unit:s}'", "unlimit": "Sin cuota", "unrestore_app": "La aplicación '{app:s}' no será restaurada", - "update_cache_failed": "No se pudo actualizar la caché APT", + "update_cache_failed": "No se pudo actualizar la caché de APT", "updating_apt_cache": "Actualizando lista de paquetes disponibles...", "upgrade_complete": "Actualización finalizada", "upgrading_packages": "Actualizando paquetes...", @@ -243,27 +243,27 @@ "yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'", "ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador", "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón de correo", - "ssowat_persistent_conf_read_error": "Error al leer la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", - "ssowat_persistent_conf_write_error": "Error al guardar la configuración persistente de SSOwat: {error:s} 1. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", - "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s} 1! (Use --force para omitir este mensaje)", - "certmanager_domain_unknown": "Dominio desconocido {domain:s} 1", - "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} 1 no es autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", - "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para el dominio {domain:s} 1 ha fallado de alguna manera...", - "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} 1 no está emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", - "certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} 1 no está a punto de expirar! Utilice --force para omitir este mensaje", - "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} 1 a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", - "certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s} 1. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} 1 es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", - "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} 1 (archivo: {file:s} 2), razón: {reason:s} 3", - "certmanager_cert_install_success_selfsigned": "¡Se ha instalado correctamente un certificado autofirmado para el dominio {domain:s} 1!", - "certmanager_cert_install_success": "Se ha instalado correctamente un certificado Let's Encrypt para el dominio {domain:s} 1!", - "certmanager_cert_renew_success": "¡Se ha renovado correctamente el certificado Let's Encrypt para el dominio {domain:s} 1!", - "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada y esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar los certificados para todos los dominios con un certificado Let's Encrypt o con un certificado autofirmado", - "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para el conjunto de dominios {domain:s} 1. Por favor, inténtelo de nuevo más tarde. Consulte https://letsencrypt.org/docs/rate-limits/ para obtener más detalles", + "ssowat_persistent_conf_read_error": "Error al leer la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", + "ssowat_persistent_conf_write_error": "Error al guardar la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", + "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", + "certmanager_domain_unknown": "Dominio desconocido {domain:s}", + "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", + "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para el dominio {domain:s} ha fallado de alguna manera...", + "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", + "certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} no está a punto de expirar! Utilice --force para omitir este mensaje", + "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", + "certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", + "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} (archivo: {file:s}), razón: {reason:s}", + "certmanager_cert_install_success_selfsigned": "¡Se ha instalado correctamente un certificado autofirmado para el dominio {domain:s}!", + "certmanager_cert_install_success": "¡Se ha instalado correctamente un certificado Let's Encrypt para el dominio {domain:s}!", + "certmanager_cert_renew_success": "¡Se ha renovado correctamente el certificado Let's Encrypt para el dominio {domain:s}!", + "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada, esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar los certificados para todos los dominios con un certificado Let's Encrypt o con un certificado autofirmado", + "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para el conjunto de dominios {domain:s}. Por favor, inténtelo de nuevo más tarde. Consulte https://letsencrypt.org/docs/rate-limits/ para obtener más detalles", "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", - "certmanager_no_cert_file": "No se puede leer el certificado para el dominio {domain:s} 1 (archivo: {file:s} 2)", - "certmanager_conflicting_nginx_file": "No se puede preparar el dominio para el desafío ACME: el archivo de configuración nginx {filepath:s} 1 está en conflicto y debe ser eliminado primero", + "certmanager_no_cert_file": "No se puede leer el certificado para el dominio {domain:s} (archivo: {file:s})", + "certmanager_conflicting_nginx_file": "No se puede preparar el dominio para el desafío ACME: el archivo de configuración nginx {filepath:s} está en conflicto y debe ser eliminado primero", "domain_cannot_remove_main": "No se puede eliminar el dominio principal. Primero debe establecer un nuevo dominio principal", - "certmanager_self_ca_conf_file_not_found": "No se ha encontrado el archivo de configuración para la autoridad de autofirma (file: {file:s} 1)", + "certmanager_self_ca_conf_file_not_found": "No se ha encontrado el archivo de configuración para la autoridad de autofirma (file: {file:s})", "certmanager_unable_to_parse_self_CA_name": "No se puede procesar el nombre de la autoridad de autofirma (file: {file:s} 1)" } From 4f6a11d31d6cc4c2b3a0f758f1d1ede576111ae2 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Tue, 13 Dec 2016 07:20:51 +0100 Subject: [PATCH 198/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (265 of 265 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 35749030c..cbd619c38 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -273,5 +273,6 @@ "domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable.", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", - "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie" + "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie", + "domains_available": "Domaines disponibles :" } From 60d8ecd761b252b19744bc9c51f8d6d977036bb4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Thu, 15 Dec 2016 22:10:52 +0100 Subject: [PATCH 199/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (266 of 266 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index cbd619c38..af935f1ea 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -274,5 +274,6 @@ "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie", - "domains_available": "Domaines disponibles :" + "domains_available": "Domaines disponibles :", + "backup_archive_broken_link": "Impossible d'accéder à l'archive de la sauvegarde (lien cassé à {path:s})" } From 18bdcf2c5f47cca5bbe3d2f56cdb338bc2c3b049 Mon Sep 17 00:00:00 2001 From: paddy Date: Sat, 24 Dec 2016 13:47:32 +0100 Subject: [PATCH 200/208] [i18n] Translated using Weblate (Spanish) Currently translated at 99.6% (265 of 266 strings) --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index b3dd4f14e..c5ead2908 100644 --- a/locales/es.json +++ b/locales/es.json @@ -265,5 +265,6 @@ "certmanager_conflicting_nginx_file": "No se puede preparar el dominio para el desafío ACME: el archivo de configuración nginx {filepath:s} está en conflicto y debe ser eliminado primero", "domain_cannot_remove_main": "No se puede eliminar el dominio principal. Primero debe establecer un nuevo dominio principal", "certmanager_self_ca_conf_file_not_found": "No se ha encontrado el archivo de configuración para la autoridad de autofirma (file: {file:s})", - "certmanager_unable_to_parse_self_CA_name": "No se puede procesar el nombre de la autoridad de autofirma (file: {file:s} 1)" + "certmanager_unable_to_parse_self_CA_name": "No se puede procesar el nombre de la autoridad de autofirma (file: {file:s} 1)", + "domains_available": "Dominios instalados:" } From 0633025224a9bc59f4f3f32bbaa489ecded5a776 Mon Sep 17 00:00:00 2001 From: paddy Date: Sat, 24 Dec 2016 13:39:26 +0100 Subject: [PATCH 201/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (267 of 267 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index af935f1ea..ee9b17d40 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -275,5 +275,6 @@ "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie", "domains_available": "Domaines disponibles :", - "backup_archive_broken_link": "Impossible d'accéder à l'archive de la sauvegarde (lien cassé à {path:s})" + "backup_archive_broken_link": "Impossible d'accéder à l'archive de la sauvegarde (lien cassé à {path:s})", + "certmanager_acme_not_configured_for_domain": "266 (EN vers FR) :\nLe certificat pour le domaine {domain:s} ne semble pas être correctement installé. Veuillez préalablement exécuter cert-install pour ce domaine." } From 3b0da3651873b78a2ae421864d6f364f46531393 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Tue, 3 Jan 2017 19:29:48 +0100 Subject: [PATCH 202/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (267 of 267 strings) --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index ee9b17d40..c32ce2f2f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -276,5 +276,5 @@ "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie", "domains_available": "Domaines disponibles :", "backup_archive_broken_link": "Impossible d'accéder à l'archive de la sauvegarde (lien cassé à {path:s})", - "certmanager_acme_not_configured_for_domain": "266 (EN vers FR) :\nLe certificat pour le domaine {domain:s} ne semble pas être correctement installé. Veuillez préalablement exécuter cert-install pour ce domaine." + "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez préalablement exécuter cert-install pour ce domaine." } From 776562a6ef318ac022bf19a3ff6a5b818bc686b5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Tue, 3 Jan 2017 19:29:48 +0100 Subject: [PATCH 203/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (268 of 268 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index c32ce2f2f..6f0908838 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -276,5 +276,6 @@ "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie", "domains_available": "Domaines disponibles :", "backup_archive_broken_link": "Impossible d'accéder à l'archive de la sauvegarde (lien cassé à {path:s})", - "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez préalablement exécuter cert-install pour ce domaine." + "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez préalablement exécuter cert-install pour ce domaine.", + "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur Yunohost. Cela peut arriver si vous avez récemment modifié votre enregistrement DNS. Auquel cas, merci d'attendre quelques heures qu'il se propage. Si le problème persiste, envisager d'ajouter {domain:s} au fichier /etc/hosts (Si vous savez ce que vou faites, utilisez --no-checks pour désactiver ces vérifications.)" } From 18364aee7423a0867837af64940166ea5d1137c5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Mon, 9 Jan 2017 23:18:22 +0100 Subject: [PATCH 204/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (268 of 268 strings) --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 6f0908838..f89ac7482 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -277,5 +277,5 @@ "domains_available": "Domaines disponibles :", "backup_archive_broken_link": "Impossible d'accéder à l'archive de la sauvegarde (lien cassé à {path:s})", "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez préalablement exécuter cert-install pour ce domaine.", - "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur Yunohost. Cela peut arriver si vous avez récemment modifié votre enregistrement DNS. Auquel cas, merci d'attendre quelques heures qu'il se propage. Si le problème persiste, envisager d'ajouter {domain:s} au fichier /etc/hosts (Si vous savez ce que vou faites, utilisez --no-checks pour désactiver ces vérifications.)" + "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être déterminé depuis votre serveur YunoHost. Cela peut arriver si vous avez récemment modifié votre enregistrement DNS. Auquel cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)" } From 28ec1dec8fcb46c9f79586b985ec60d1c3b58b1e Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 29 Nov 2016 14:00:15 +0100 Subject: [PATCH 205/208] Remove helper ynh_mkdir_tmp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le helper `ynh_mkdir_tmp` est inutile. Il existe la commande `mktemp -d` pour créer des dossiers temporaires. --- data/helpers.d/filesystem | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 856b7cb86..3517f5e44 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -62,15 +62,3 @@ ynh_bind_or_cp() { echo "This helper is deprecated, you should use ynh_backup instead" >&2 ynh_backup "$1" "$2" 1 "$NO_ROOT" } - -# Create a directory under /tmp -# -# usage: ynh_mkdir_tmp -# | ret: the created directory path -ynh_mkdir_tmp() { - TMPDIR="/tmp/$(ynh_string_random 6)" - while [ -d $TMPDIR ]; do - TMPDIR="/tmp/$(ynh_string_random 6)" - done - mkdir -p "$TMPDIR" && echo "$TMPDIR" -} From 65d412055d7e9848c9083e06a762eb8ecd021143 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 29 Nov 2016 14:19:19 +0100 Subject: [PATCH 206/208] Update filesystem --- data/helpers.d/filesystem | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 3517f5e44..9933563cc 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -62,3 +62,13 @@ ynh_bind_or_cp() { echo "This helper is deprecated, you should use ynh_backup instead" >&2 ynh_backup "$1" "$2" 1 "$NO_ROOT" } + +# Create a directory under /tmp +# +# Deprecated helper +# +# usage: ynh_mkdir_tmp +# | ret: the created directory path +ynh_mkdir_tmp() { + mktemp -d +} From 8e811fd0ac1e79d6d09d14eb576826d655d3099a Mon Sep 17 00:00:00 2001 From: opi Date: Tue, 29 Nov 2016 15:27:51 +0100 Subject: [PATCH 207/208] [enh] Add warning about deprecated ynh_mkdir_tmp helper --- data/helpers.d/filesystem | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 9933563cc..27a016e63 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -70,5 +70,6 @@ ynh_bind_or_cp() { # usage: ynh_mkdir_tmp # | ret: the created directory path ynh_mkdir_tmp() { + echo "This helper is deprecated, you should use 'mktemp -d' instead." >&2 mktemp -d } From c89d1e25d2bf1e989f3c77d2986724092c4aff11 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Wed, 1 Feb 2017 22:16:53 +0100 Subject: [PATCH 208/208] [i18n] Translated using Weblate (French) Currently translated at 100.0% (270 of 270 strings) --- locales/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index f89ac7482..6f1369b7a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -277,5 +277,7 @@ "domains_available": "Domaines disponibles :", "backup_archive_broken_link": "Impossible d'accéder à l'archive de la sauvegarde (lien cassé à {path:s})", "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez préalablement exécuter cert-install pour ce domaine.", - "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être déterminé depuis votre serveur YunoHost. Cela peut arriver si vous avez récemment modifié votre enregistrement DNS. Auquel cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)" + "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être déterminé depuis votre serveur YunoHost. Cela peut arriver si vous avez récemment modifié votre enregistrement DNS. Auquel cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)", + "certmanager_http_check_timeout": "Expiration du délai lors de la tentative du serveur de se contacter via HTTP en utilisant son adresse IP publique (domaine {domain:s} avec l’IP {ip:s}). Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", + "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation/le renouvellement du certificat a été interrompu - veuillez réessayer prochainement." }