diff --git a/bin/yunohost b/bin/yunohost index 3f947364b..3e6f031b5 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -1,8 +1,13 @@ -#!/usr/bin/env python +#! /usr/bin/python # -*- coding: utf-8 -*- -import sys import os +import sys +import argparse + +import moulinette +from moulinette.actionsmap import ActionsMap +from moulinette.interfaces.cli import colorize, get_locale # Either we are in a development environment or not IN_DEVEL = False @@ -34,17 +39,11 @@ if IN_DEVEL: def _die(message, title='Error:'): """Print error message and exit""" - try: - from moulinette.interfaces.cli import colorize - except ImportError: - colorize = lambda msg, c: msg print('%s %s' % (colorize(title, 'red'), message)) sys.exit(1) def _parse_cli_args(): """Parse additional arguments for the cli""" - import argparse - parser = argparse.ArgumentParser(add_help=False) parser.add_argument('--no-cache', action='store_false', default=True, dest='use_cache', @@ -90,8 +89,6 @@ def _parse_cli_args(): def _init_moulinette(debug=False, verbose=False, quiet=False): """Configure logging and initialize the moulinette""" - from moulinette import init - # Define loggers handlers handlers = set(LOGGERS_HANDLERS) if quiet and 'tty' in handlers: @@ -100,7 +97,7 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): handlers.append('tty') root_handlers = set(handlers) - if not debug: + if not debug and 'tty' in root_handlers: root_handlers.remove('tty') # Define loggers level @@ -167,11 +164,10 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): _die(str(e)) # Initialize moulinette - init(logging_config=logging, _from_source=IN_DEVEL) + moulinette.init(logging_config=logging, _from_source=IN_DEVEL) def _retrieve_namespaces(): """Return the list of namespaces to load""" - from moulinette.actionsmap import ActionsMap ret = ['yunohost'] for n in ActionsMap.get_namespaces(): # Append YunoHost modules @@ -190,8 +186,6 @@ if __name__ == '__main__': if not os.path.isfile('/etc/yunohost/installed') and \ (len(args) < 2 or (args[0] +' '+ args[1] != 'tools postinstall' and \ args[0] +' '+ args[1] != 'backup restore')): - from moulinette.interfaces.cli import get_locale - # Init i18n m18n.load_namespace('yunohost') m18n.set_locale(get_locale()) @@ -200,9 +194,9 @@ if __name__ == '__main__': _die(m18n.n('yunohost_not_installed'), m18n.g('error')) # Execute the action - from moulinette import cli - ret = cli(_retrieve_namespaces(), args, - use_cache=opts.use_cache, output_as=opts.output_as, - password=opts.password, parser_kwargs={'top_parser': parser} + ret = moulinette.cli( + _retrieve_namespaces(), args, + use_cache=opts.use_cache, output_as=opts.output_as, + password=opts.password, parser_kwargs={'top_parser': parser} ) sys.exit(ret) diff --git a/bin/yunohost-api b/bin/yunohost-api index 3e8e7e31c..f1370205e 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -1,8 +1,13 @@ -#!/usr/bin/env python +#! /usr/bin/python # -*- coding: utf-8 -*- +import os import sys -import os.path +import argparse + +import moulinette +from moulinette.actionsmap import ActionsMap +from moulinette.interfaces.cli import colorize # Either we are in a development environment or not IN_DEVEL = False @@ -38,17 +43,11 @@ if IN_DEVEL: def _die(message, title='Error:'): """Print error message and exit""" - try: - from moulinette.interfaces.cli import colorize - except ImportError: - colorize = lambda msg, c: msg print('%s %s' % (colorize(title, 'red'), message)) sys.exit(1) def _parse_api_args(): """Parse main arguments for the api""" - import argparse - parser = argparse.ArgumentParser(add_help=False, description="Run the YunoHost API to manage your server.", ) @@ -87,8 +86,6 @@ def _parse_api_args(): def _init_moulinette(use_websocket=True, debug=False, verbose=False): """Configure logging and initialize the moulinette""" - from moulinette import init - # Define loggers handlers handlers = set(LOGGERS_HANDLERS) if not use_websocket and 'api' in handlers: @@ -162,11 +159,10 @@ def _init_moulinette(use_websocket=True, debug=False, verbose=False): _die(str(e)) # Initialize moulinette - init(logging_config=logging, _from_source=IN_DEVEL) + moulinette.init(logging_config=logging, _from_source=IN_DEVEL) def _retrieve_namespaces(): """Return the list of namespaces to load""" - from moulinette.actionsmap import ActionsMap ret = ['yunohost'] for n in ActionsMap.get_namespaces(): # Append YunoHost modules @@ -195,14 +191,12 @@ if __name__ == '__main__': _init_moulinette(opts.use_websocket, opts.debug, opts.verbose) # Run the server - from moulinette import api, MoulinetteError from yunohost.utils.packages import ynh_packages_version - ret = api(_retrieve_namespaces(), - host=opts.host, port=opts.port, - routes={ - ('GET', '/installed'): is_installed, - ('GET', '/version'): ynh_packages_version, - }, - use_cache=opts.use_cache, use_websocket=opts.use_websocket + ret = moulinette.api( + _retrieve_namespaces(), + host=opts.host, port=opts.port, routes={ + ('GET', '/installed'): is_installed, + ('GET', '/version'): ynh_packages_version, + }, use_cache=opts.use_cache, use_websocket=opts.use_websocket ) sys.exit(ret) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index cabe2b9d6..e538bfc47 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -334,7 +334,7 @@ app: arguments: -u: full: --url - help: URL of remote JSON list (default https://yunohost.org/official.json) + help: URL of remote JSON list (default https://app.yunohost.org/official.json) -n: full: --name help: Name of the list (default yunohost) @@ -377,6 +377,14 @@ app: full: --raw help: Return the full app_dict action: store_true + -i: + full: --installed + help: Return only installed apps + action: store_true + -b: + full: --with-backup + help: Return only apps with backup feature (force --installed filter) + action: store_true ### app_info() info: @@ -428,7 +436,7 @@ app: help: Custom name for the app -a: full: --args - help: Serialize arguments for app installation + help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path") ### app_remove() TODO: Write help remove: @@ -639,6 +647,8 @@ backup: action_help: Restore from a local backup archive api: POST /backup/restore/ configuration: + authenticate: all + authenticator: ldap-anonymous lock: false arguments: name: @@ -969,7 +979,6 @@ service: action_help: > Check if the specific file has been modified and display differences. Stores the file hash in the services.yml file - api: PUT /services/safecopy arguments: new_conf_file: help: Path to the desired conf file @@ -990,7 +999,6 @@ service: action_help: > Check if the specific file has been modified before removing it. Backup the file in /home/yunohost.backup - api: PUT /services/safecopy arguments: conf_file: help: Path to the targeted conf file @@ -1159,10 +1167,10 @@ dyndns: arguments: --dyn-host: help: Dynette DNS server to inform - default: "dynhost.yunohost.org" + default: "dyndns.yunohost.org" -d: full: --domain - help: Full domain to subscribe with + help: Full domain to update extra: pattern: *pattern_domain -k: @@ -1314,7 +1322,6 @@ hook: ### hook_add() add: action_help: Store hook script to filesystem - api: PUT /hook arguments: app: help: App to link with @@ -1324,7 +1331,6 @@ hook: ### hook_remove() remove: action_help: Remove hook scripts from filesystem - api: DELETE /hook arguments: app: help: Scripts related to app will be removed @@ -1362,7 +1368,7 @@ hook: ### hook_callback() callback: action_help: Execute all scripts binded to an action - api: GET /hooks + api: POST /hooks/ arguments: action: help: Action name @@ -1378,7 +1384,6 @@ hook: ### hook_exec() exec: action_help: Execute hook from a file with arguments - api: GET /hook arguments: path: help: Path of the script to execute @@ -1392,3 +1397,6 @@ hook: full: --no-trace help: Do not print each command that will be executed action: store_true + -d: + full: --chdir + help: The directory from where the script will be executed diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 70a81c84e..89d1f64e5 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -4,7 +4,7 @@ # | arg: app - the application id # | arg: key - the setting to get ynh_app_setting_get() { - sudo yunohost app setting "$1" "$2" --output-as plain + sudo yunohost app setting "$1" "$2" --output-as plain --quiet } # Set an application setting @@ -14,7 +14,7 @@ ynh_app_setting_get() { # | arg: key - the setting name to set # | arg: value - the setting value to set ynh_app_setting_set() { - sudo yunohost app setting "$1" "$2" -v "$3" + sudo yunohost app setting "$1" "$2" -v "$3" --quiet } # Delete an application setting @@ -23,5 +23,5 @@ ynh_app_setting_set() { # | arg: app - the application id # | arg: key - the setting to delete ynh_app_setting_delete() { - sudo yunohost app setting -d "$1" "$2" + sudo yunohost app setting -d "$1" "$2" --quiet } diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index 7b17a8ac1..da62e20ea 100644 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -14,8 +14,11 @@ function safe_copy () { cd /usr/share/yunohost/templates/rspamd -# Copy Rspamd configuration -safe_copy metrics.conf /etc/rspamd/metrics.conf +# Create configuration directories +sudo mkdir -p /etc/rspamd/local.d /etc/rspamd/override.d + +# Copy specific configuration to rewrite the defaults +safe_copy metrics.conf.local /etc/rspamd/local.d/metrics.conf # Install Rspamd sieve script safe_copy rspamd.sieve /etc/dovecot/global_script/rspamd.sieve diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 49c303994..683747adb 100644 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -17,11 +17,11 @@ function safe_copy () { cd /usr/share/yunohost/templates/dnsmasq # Get IPv4 address -ip=$(curl -s -4 https://ip.yunohost.org 2>/dev/null) +ip=$(curl -s -4 https://ip.yunohost.org 2>/dev/null || true) ynh_validate_ip4 $ip || ip='0.0.0.0' # Get IPv6 IP address -ipv6=$(curl -s -6 http://ip6.yunohost.org 2>/dev/null) +ipv6=$(curl -s -6 http://ip6.yunohost.org 2>/dev/null || true) ynh_validate_ip6 $ipv6 || ipv6='' sudo mkdir -p /etc/dnsmasq.d diff --git a/data/templates/nginx/yunohost_admin.conf b/data/templates/nginx/yunohost_admin.conf index 0660eca46..bcd99493b 100644 --- a/data/templates/nginx/yunohost_admin.conf +++ b/data/templates/nginx/yunohost_admin.conf @@ -29,11 +29,14 @@ server { return 302 https://$http_host/yunohost/admin; } - # Block crawlers bot location /yunohost { - if ($http_user_agent ~ (crawl|Googlebot|Slurp|spider|bingbot|tracker|click|parser|spider|facebookexternalhit) ) { - return 403; - } + # Block crawlers bot + if ($http_user_agent ~ (crawl|Googlebot|Slurp|spider|bingbot|tracker|click|parser|spider|facebookexternalhit) ) { + return 403; + } + + # Redirect most of 404 to maindomain.tld/yunohost/sso + access_by_lua_file /usr/share/ssowat/access.lua; } include conf.d/yunohost_admin.conf.inc; diff --git a/data/templates/rspamd/metrics.conf b/data/templates/rspamd/metrics.conf deleted file mode 100644 index 1236b2a3e..000000000 --- a/data/templates/rspamd/metrics.conf +++ /dev/null @@ -1,1163 +0,0 @@ -# Metrics settings - -metric { - name = "default"; - # If this param is set to non-zero - # then a metric would accept all symbols - # unknown_weight = 1.0 - - actions { - reject = 21; - add_header = 8; - greylist = 4; - }; - - group { - name = "header"; - symbol { - weight = 2.0; - description = "Subject is missing inside message"; - name = "MISSING_SUBJECT"; - } - symbol { - weight = 2.100000; - description = "Message pretends to be send from Outlook but has 'strange' tags "; - name = "FORGED_OUTLOOK_TAGS"; - } - symbol { - weight = 0.30; - description = "Sender is forged (different From: header and smtp MAIL FROM: addresses)"; - name = "FORGED_SENDER"; - } - symbol { - weight = 3.500000; - description = "Recipients seems to be autogenerated (works if recipients count is more than 5)"; - name = "SUSPICIOUS_RECIPS"; - } - symbol { - weight = 6.0; - description = "Fake reply (has RE in subject, but has not References header)"; - name = "FAKE_REPLY_C"; - } - symbol { - weight = 1.0; - description = "Messages that have only HTML part"; - name = "MIME_HTML_ONLY"; - } - symbol { - weight = 2.0; - description = "Forged yahoo msgid"; - name = "FORGED_MSGID_YAHOO"; - } - symbol { - weight = 2.0; - description = "Forged The Bat! MUA headers"; - name = "FORGED_MUA_THEBAT_BOUN"; - } - symbol { - weight = 5.0; - description = "Charset is missing in a message"; - name = "R_MISSING_CHARSET"; - } - symbol { - weight = 2.0; - description = "Two received headers with ip addresses"; - name = "RCVD_DOUBLE_IP_SPAM"; - } - symbol { - weight = 5.0; - description = "Forged outlook HTML signature"; - name = "FORGED_OUTLOOK_HTML"; - } - symbol { - weight = 5.0; - description = "Recipients are absent or undisclosed"; - name = "R_UNDISC_RCPT"; - } - symbol { - weight = 2.0; - description = "Fake helo for verizon provider"; - name = "FM_FAKE_HELO_VERIZON"; - } - symbol { - weight = 2.0; - description = "Quoted reply-to from yahoo (seems to be forged)"; - name = "REPTO_QUOTE_YAHOO"; - } - symbol { - weight = 5.0; - description = "Mime-OLE is needed but absent (e.g. fake Outlook or fake Exchange)"; - name = "MISSING_MIMEOLE"; - } - symbol { - weight = 2.0; - description = "To header is missing"; - name = "MISSING_TO"; - } - symbol { - weight = 1.500000; - description = "From that contains encoded characters while base 64 is not needed as all symbols are 7bit"; - name = "FROM_EXCESS_BASE64"; - } - symbol { - weight = 1.200000; - description = "From that contains encoded characters while quoted-printable is not needed as all symbols are 7bit"; - name = "FROM_EXCESS_QP"; - } - symbol { - weight = 1.500000; - description = "To that contains encoded characters while base 64 is not needed as all symbols are 7bit"; - name = "TO_EXCESS_BASE64"; - } - symbol { - weight = 1.200000; - description = "To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit"; - name = "TO_EXCESS_QP"; - } - symbol { - weight = 1.500000; - description = "Reply-To that contains encoded characters while base 64 is not needed as all symbols are 7bit"; - name = "REPLYTO_EXCESS_BASE64"; - } - symbol { - weight = 1.200000; - description = "Reply-To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit"; - name = "REPLYTO_EXCESS_QP"; - } - symbol { - weight = 1.500000; - description = "Cc that contains encoded characters while base 64 is not needed as all symbols are 7bit"; - name = "CC_EXCESS_BASE64"; - } - symbol { - weight = 1.200000; - description = "Cc that contains encoded characters while quoted-printable is not needed as all symbols are 7bit"; - name = "CC_EXCESS_QP"; - } - symbol { - weight = 5.0; - description = "Mixed characters in a message"; - name = "R_MIXED_CHARSET"; - } - symbol { - weight = 3.500000; - description = "Recipients list seems to be sorted"; - name = "SORTED_RECIPS"; - } - symbol { - weight = 3.0; - description = "Spambots signatures in received headers"; - name = "R_RCVD_SPAMBOTS"; - } - symbol { - weight = 2.0; - description = "To header seems to be autogenerated"; - name = "R_TO_SEEMS_AUTO"; - } - symbol { - weight = 1.0; - description = "Subject needs encoding"; - name = "SUBJECT_NEEDS_ENCODING"; - } - symbol { - weight = 3.840000; - description = "Spam string at the end of message to make statistics faults 0"; - name = "TRACKER_ID"; - } - symbol { - weight = 1.0; - description = "No space in from header"; - name = "R_NO_SPACE_IN_FROM"; - } - symbol { - weight = 8.0; - description = "Subject seems to be spam"; - name = "R_SAJDING"; - } - symbol { - weight = 3.0; - description = "Detects bad content-transfer-encoding for text parts"; - name = "R_BAD_CTE_7BIT"; - } - symbol { - weight = 10.0; - description = "Flash redirect on imageshack.us"; - name = "R_FLASH_REDIR_IMGSHACK"; - } - symbol { - weight = 5.0; - description = "Message id is incorrect"; - name = "INVALID_MSGID"; - } - symbol { - weight = 3.0; - description = "Message id is missing "; - name = "MISSING_MID"; - } - symbol { - weight = 1.0; - description = "Recipients are not the same as RCPT TO: mail command"; - name = "FORGED_RECIPIENTS"; - } - symbol { - weight = 0.0; - description = "Recipients are not the same as RCPT TO: mail command, but a message from a maillist"; - name = "FORGED_RECIPIENTS_MAILLIST"; - } - symbol { - weight = 0.0; - description = "Sender is not the same as MAIL FROM: envelope, but a message is from a maillist"; - name = "FORGED_SENDER_MAILLIST"; - } - symbol { - weight = 2.0; - description = "Forged Exchange messages "; - name = "RATWARE_MS_HASH"; - } - symbol { - weight = 1.0; - description = "Reply-type in content-type"; - name = "STOX_REPLY_TYPE"; - } - symbol { - weight = 0.1; - description = "One received header in a message "; - name = "ONCE_RECEIVED"; - } - symbol { - weight = 2.0; - description = "One received header with 'bad' patterns inside"; - name = "ONCE_RECEIVED_STRICT"; - } - symbol { - weight = 2.0; - description = "Only Content-Type header without other MIME headers"; - name = "MIME_HEADER_CTYPE_ONLY"; - } - symbol { - weight = -1.0; - description = "Message seems to be from maillist"; - name = "MAILLIST"; - } - symbol { - weight = 1.0; - description = "Header From begins with tab"; - name = "HEADER_FROM_DELIMITER_TAB"; - } - symbol { - weight = 1.0; - description = "Header To begins with tab"; - name = "HEADER_TO_DELIMITER_TAB"; - } - symbol { - weight = 1.0; - description = "Header Cc begins with tab"; - name = "HEADER_CC_DELIMITER_TAB"; - } - symbol { - weight = 1.0; - description = "Header Reply-To begins with tab"; - name = "HEADER_REPLYTO_DELIMITER_TAB"; - } - symbol { - weight = 1.0; - description = "Header Date begins with tab"; - name = "HEADER_DATE_DELIMITER_TAB"; - } - symbol { - weight = 1.0; - description = "Header From has no delimiter between header name and header value"; - name = "HEADER_FROM_EMPTY_DELIMITER"; - } - symbol { - weight = 1.0; - description = "Header To has no delimiter between header name and header value"; - name = "HEADER_TO_EMPTY_DELIMITER"; - } - symbol { - weight = 1.0; - description = "Header Cc has no delimiter between header name and header value"; - name = "HEADER_CC_EMPTY_DELIMITER"; - } - symbol { - weight = 1.0; - description = "Header Reply-To has no delimiter between header name and header value"; - name = "HEADER_REPLYTO_EMPTY_DELIMITER"; - } - symbol { - weight = 1.0; - description = "Header Date has no delimiter between header name and header value"; - name = "HEADER_DATE_EMPTY_DELIMITER"; - } - symbol { - weight = 4.0; - description = "Header Received has raw illegal character"; - name = "RCVD_ILLEGAL_CHARS"; - } - symbol { - weight = 4.0; - description = "Fake helo mail.ru in header Received from non mail.ru sender address"; - name = "FAKE_RECEIVED_mail_ru"; - } - symbol { - weight = 4.0; - description = "Fake smtp.yandex.ru Received"; - name = "FAKE_RECEIVED_smtp_yandex_ru"; - } - symbol { - weight = 3.600000; - description = "Forged generic Received"; - name = "FORGED_GENERIC_RECEIVED"; - } - symbol { - weight = 3.600000; - description = "Forged generic Received"; - name = "FORGED_GENERIC_RECEIVED2"; - } - symbol { - weight = 3.600000; - description = "Forged generic Received"; - name = "FORGED_GENERIC_RECEIVED3"; - } - symbol { - weight = 3.600000; - description = "Forged generic Received"; - name = "FORGED_GENERIC_RECEIVED4"; - } - symbol { - weight = 4.600000; - description = "Forged generic Received"; - name = "FORGED_GENERIC_RECEIVED5"; - } - symbol { - weight = 3.0; - description = "Invalid Postfix Received"; - name = "INVALID_POSTFIX_RECEIVED"; - } - symbol { - weight = 5.0; - description = "Invalid Exim Received"; - name = "INVALID_EXIM_RECEIVED"; - } - symbol { - weight = 3.0; - description = "Invalid Exim Received"; - name = "INVALID_EXIM_RECEIVED2"; - } - } - - group { - name = "mua"; - symbol { - weight = 4.0; - description = "Message pretends to be send from The Bat! but has forged Message-ID"; - name = "FORGED_MUA_THEBAT_MSGID"; - } - symbol { - weight = 3.0; - description = "Message pretends to be send from The Bat! but has forged Message-ID"; - name = "FORGED_MUA_THEBAT_MSGID_UNKNOWN"; - } - symbol { - weight = 3.0; - description = "Message pretends to be send from KMail but has forged Message-ID"; - name = "FORGED_MUA_KMAIL_MSGID"; - } - symbol { - weight = 2.500000; - description = "Message pretends to be send from KMail but has forged Message-ID"; - name = "FORGED_MUA_KMAIL_MSGID_UNKNOWN"; - } - symbol { - weight = 4.0; - description = "Message pretends to be send from Opera Mail but has forged Message-ID"; - name = "FORGED_MUA_OPERA_MSGID"; - } - symbol { - weight = 4.0; - description = "Message pretends to be send from suspicious Opera Mail/10.x (Windows) but has forged Message-ID, apparently from KMail"; - name = "SUSPICIOUS_OPERA_10W_MSGID"; - } - symbol { - weight = 4.0; - description = "Message pretends to be send from Mozilla Mail but has forged Message-ID"; - name = "FORGED_MUA_MOZILLA_MAIL_MSGID"; - } - symbol { - weight = 2.500000; - description = "Message pretends to be send from Mozilla Mail but has forged Message-ID"; - name = "FORGED_MUA_MOZILLA_MAIL_MSGID_UNKNOWN"; - } - symbol { - weight = 4.0; - description = "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID"; - name = "FORGED_MUA_THUNDERBIRD_MSGID"; - } - symbol { - weight = 2.500000; - description = "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID"; - name = "FORGED_MUA_THUNDERBIRD_MSGID_UNKNOWN"; - } - symbol { - weight = 4.0; - description = "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID"; - name = "FORGED_MUA_SEAMONKEY_MSGID"; - } - symbol { - weight = 2.500000; - description = "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID"; - name = "FORGED_MUA_SEAMONKEY_MSGID_UNKNOWN"; - } - symbol { - weight = 3.0; - description = "Forged outlook MUA"; - name = "FORGED_MUA_OUTLOOK"; - } - } - symbol { - weight = 0.0; - description = "Avoid false positives for FORGED_MUA_* in maillist"; - name = "FORGED_MUA_MAILLIST"; - } - - group { - name = "body"; - symbol { - weight = 9.0; - description = "White color on white background in HTML messages"; - name = "R_WHITE_ON_WHITE"; - } - symbol { - weight = 3.0; - description = "Short html part with a link to an image"; - name = "HTML_SHORT_LINK_IMG_1"; - } - symbol { - weight = 1.0; - description = "Short html part with a link to an image"; - name = "HTML_SHORT_LINK_IMG_2"; - } - symbol { - weight = 0.5; - description = "Short html part with a link to an image"; - name = "HTML_SHORT_LINK_IMG_3"; - } - symbol { - weight = 5.0; - description = "Suspicious boundary in header Content-Type"; - name = "SUSPICIOUS_BOUNDARY"; - } - symbol { - weight = 4.0; - description = "Suspicious boundary in header Content-Type"; - name = "SUSPICIOUS_BOUNDARY2"; - } - symbol { - weight = 3.0; - description = "Suspicious boundary in header Content-Type"; - name = "SUSPICIOUS_BOUNDARY3"; - } - symbol { - weight = 4.0; - description = "Suspicious boundary in header Content-Type"; - name = "SUSPICIOUS_BOUNDARY4"; - } - symbol { - weight = 3.0; - description = "Text and HTML parts differ"; - name = "R_PARTS_DIFFER"; - } - - symbol { - weight = 2.0; - description = "Message contains empty parts and image"; - name = "R_EMPTY_IMAGE"; - } - symbol { - weight = 2.0; - description = "Drugs patterns inside message"; - name = "DRUGS_MANYKINDS"; - } - symbol { - weight = 2.0; - description = ""; - name = "DRUGS_ANXIETY"; - } - symbol { - weight = 2.0; - description = ""; - name = "DRUGS_MUSCLE"; - } - symbol { - weight = 2.0; - description = ""; - name = "DRUGS_ANXIETY_EREC"; - } - symbol { - weight = 2.0; - description = ""; - name = "DRUGS_DIET"; - } - symbol { - weight = 2.0; - description = ""; - name = "DRUGS_ERECTILE"; - } - symbol { - weight = 3.300000; - description = "2 'advance fee' patterns in a message"; - name = "ADVANCE_FEE_2"; - } - symbol { - weight = 2.120000; - description = "3 'advance fee' patterns in a message"; - name = "ADVANCE_FEE_3"; - } - symbol { - weight = 8.0; - description = "Lotto signatures"; - name = "R_LOTTO"; - } - } - - group { - name = "rbl"; - symbol { - name = "DNSWL_BLOCKED"; - weight = 0.0; - description = "Resolver blocked due to excessive queries"; - } - symbol { - name = "RCVD_IN_DNSWL"; - weight = 0.0; - description = "Unrecognised result from dnswl.org"; - } - symbol { - name = "RCVD_IN_DNSWL_NONE"; - weight = 0.0; - description = "Sender listed at http://www.dnswl.org, low none"; - } - symbol { - name = "RCVD_IN_DNSWL_LOW"; - weight = 0.0; - description = "Sender listed at http://www.dnswl.org, low trust"; - } - symbol { - name = "RCVD_IN_DNSWL_MED"; - weight = 0.0; - description = "Sender listed at http://www.dnswl.org, medium trust"; - } - symbol { - name = "RCVD_IN_DNSWL_HI"; - weight = 0.0; - description = "Sender listed at http://www.dnswl.org, high trust"; - } - - symbol { - name = "RBL_SPAMHAUS"; - weight = 0.0; - description = "Unrecognised result from Spamhaus zen"; - } - symbol { - name = "RBL_SPAMHAUS_SBL"; - weight = 2.0; - description = "From address is listed in zen sbl"; - } - symbol { - name = "RBL_SPAMHAUS_CSS"; - weight = 2.0; - description = "From address is listed in zen css"; - } - symbol { - name = "RBL_SPAMHAUS_XBL"; - weight = 4.0; - description = "From address is listed in zen xbl"; - } - symbol { - name = "RBL_SPAMHAUS_PBL"; - weight = 2.0; - description = "From address is listed in zen pbl"; - } - symbol { - name = "RECEIVED_SPAMHAUS_XBL"; - weight = 3.0; - description = "Received address is listed in zen pbl"; - one_shot = true; - } - - symbol { - name = "RWL_SPAMHAUS_WL"; - weight = 0.0; - description = "Unrecognised result from Spamhaus whitelist"; - } - symbol { - name = "RWL_SPAMHAUS_WL_IND"; - weight = 0.0; - description = "Sender listed at Spamhaus whitelist"; - } - symbol { - name = "RWL_SPAMHAUS_WL_TRANS"; - weight = 0.0; - description = "Sender listed at Spamhaus whitelist"; - } - symbol { - name = "RWL_SPAMHAUS_WL_IND_EXP"; - weight = 0.0; - description = "Sender listed at Spamhaus whitelist"; - } - symbol { - name = "RWL_SPAMHAUS_WL_TRANS_EXP"; - weight = 0.0; - description = "Sender listed at Spamhaus whitelist"; - } - - symbol { - weight = 2.0; - description = "From address is listed in senderscore.com BL"; - name = "RBL_SENDERSCORE"; - } - symbol { - weight = 1.0; - description = "From address is listed in ABUSE.CH BL"; - name = "RBL_ABUSECH"; - } - symbol { - weight = 1.0; - description = "From address is listed in UCEPROTECT LEVEL1 BL"; - name = "RBL_UCEPROTECT_LEVEL1"; - } - symbol { - name = "RBL_MAILSPIKE"; - weight = 0.0; - description = "Unrecognised result from Mailspike blacklist"; - } - symbol { - name = "RWL_MAILSPIKE"; - weight = 0.0; - description = "Unrecognised result from Mailspike whitelist"; - } - symbol { - name = "RBL_MAILSPIKE_ZOMBIE"; - weight = 2.0; - description = "From address is listed in RBL"; - } - symbol { - name = "RBL_MAILSPIKE_WORST"; - weight = 2.0; - description = "From address is listed in RBL"; - } - symbol { - name = "RBL_MAILSPIKE_VERYBAD"; - weight = 1.5; - description = "From address is listed in RBL"; - } - symbol { - name = "RBL_MAILSPIKE_BAD"; - weight = 1.0; - description = "From address is listed in RBL"; - } - symbol { - name = "RWL_MAILSPIKE_POSSIBLE"; - weight = 0.0; - description = "From address is listed in RWL"; - } - symbol { - name = "RWL_MAILSPIKE_GOOD"; - weight = 0.0; - description = "From address is listed in RWL"; - } - symbol { - name = "RWL_MAILSPIKE_VERYGOOD"; - weight = 0.0; - description = "From address is listed in RWL"; - } - symbol { - name = "RWL_MAILSPIKE_EXCELLENT"; - weight = 0.0; - description = "From address is listed in RWL"; - } - - symbol { - weight = 0.0; - name = "RBL_SORBS"; - description = "Unrecognised result from SORBS RBL"; - } - symbol { - weight = 2.5; - name = "RBL_SORBS_HTTP"; - description = "List of Open HTTP Proxy Servers."; - } - symbol { - weight = 2.5; - name = "RBL_SORBS_SOCKS"; - description = "List of Open SOCKS Proxy Servers."; - } - symbol { - weight = 1.0; - name = "RBL_SORBS_MISC"; - description = "List of open Proxy Servers not listed in the SOCKS or HTTP lists."; - } - symbol { - weight = 3.0; - name = "RBL_SORBS_SMTP"; - description = "List of Open SMTP relay servers."; - } - symbol { - weight = 1.5; - name = "RBL_SORBS_RECENT"; - description = "List of hosts that have been noted as sending spam/UCE/UBE to the admins of SORBS within the last 28 days (includes new.spam.dnsbl.sorbs.net)."; - } - symbol { - weight = 0.4; - name = "RBL_SORBS_WEB"; - description = "List of web (WWW) servers which have spammer abusable vulnerabilities (e.g. FormMail scripts)"; - } - symbol { - weight = 2.0; - name = "RBL_SORBS_DUL"; - description = "Dynamic IP Address ranges (NOT a Dial Up list!)"; - } - symbol { - weight = 1.0; - name = "RBL_SORBS_BLOCK"; - description = "List of hosts demanding that they never be tested by SORBS."; - } - symbol { - weight = 1.0; - name = "RBL_SORBS_ZOMBIE"; - description = "List of networks hijacked from their original owners, some of which have already used for spamming."; - } - - symbol { - weight = 1.0; - name = "RBL_SEM"; - description = "Address is listed in Spameatingmonkey RBL"; - } - - symbol { - weight = 1.0; - name = "RBL_SEM_IPV6"; - description = "Address is listed in Spameatingmonkey RBL (ipv6)"; - } - } - - group { - name = "bayes"; - - symbol { - weight = 3.0; - description = "Message probably spam, probability: "; - name = "BAYES_SPAM"; - } - symbol { - weight = -3.0; - description = "Message probably ham, probability: "; - name = "BAYES_HAM"; - } - } - - group { - name = "fuzzy"; - symbol { - weight = 5.0; - description = "Generic fuzzy hash match"; - name = "FUZZY_UNKNOWN"; - } - symbol { - weight = 10.0; - description = "Denied fuzzy hash"; - name = "FUZZY_DENIED"; - } - symbol { - weight = 5.0; - description = "Probable fuzzy hash"; - name = "FUZZY_PROB"; - } - symbol { - weight = -2.1; - description = "Whitelisted fuzzy hash"; - name = "FUZZY_WHITE"; - } - } - - group { - name = "spf"; - symbol { - weight = 1.0; - description = "SPF verification failed"; - name = "R_SPF_FAIL"; - } - symbol { - weight = 0.0; - description = "SPF verification soft-failed"; - name = "R_SPF_SOFTFAIL"; - } - symbol { - weight = 0.0; - description = "SPF policy is neutral"; - name = "R_SPF_NEUTRAL"; - } - symbol { - weight = -1.1; - description = "SPF verification alowed"; - name = "R_SPF_ALLOW"; - } - } - - group { - name = "dkim"; - symbol { - weight = 1.0; - description = "DKIM verification failed"; - name = "R_DKIM_REJECT"; - } - symbol { - weight = 0.0; - description = "DKIM verification soft-failed"; - name = "R_DKIM_TEMPFAIL"; - } - symbol { - weight = -1.1; - description = "DKIM verification succeed"; - name = "R_DKIM_ALLOW"; - one_shot = true; - } - } - - group { - name = "surbl"; - symbol { - weight = 5.500000; - description = "SURBL: Phishing sites"; - name = "PH_SURBL_MULTI"; - } - symbol { - weight = 5.500000; - description = "SURBL: Malware sites"; - name = "MW_SURBL_MULTI"; - } - symbol { - weight = 5.500000; - description = "SURBL: AbuseButler web sites"; - name = "AB_SURBL_MULTI"; - } - symbol { - weight = 5.500000; - description = "SURBL: SpamCop web sites"; - name = "SC_SURBL_MULTI"; - } - symbol { - weight = 5.500000; - description = "SURBL: jwSpamSpy + Prolocation sites"; - name = "JP_SURBL_MULTI"; - } - symbol { - weight = 5.500000; - description = "SURBL: sa-blacklist web sites "; - name = "WS_SURBL_MULTI"; - } - symbol { - weight = 4.500000; - description = "rambler.ru uribl"; - name = "RAMBLER_URIBL"; - } - - symbol { - weight = 0.0; - name = "SEM_URIBL_UNKNOWN"; - description = "Spameatingmonkey uribl unknown"; - } - symbol { - weight = 3.5; - name = "SEM_URIBL"; - description = "Spameatingmonkey uribl"; - } - - symbol { - weight = 0.0; - name = "SEM_URIBL_FRESH15_UNKNOWN"; - description = "Spameatingmonkey uribl unknown"; - } - symbol { - weight = 3.0; - name = "SEM_URIBL_FRESH15"; - description = "Spameatingmonkey uribl. Domains registered in the last 15 days (.AERO,.BIZ,.COM,.INFO,.NAME,.NET,.PRO,.SK,.TEL,.US)"; - } - - symbol { - weight = 0.000000; - description = "DBL uribl unknown symbol (error)"; - name = "DBL"; - } - symbol { - weight = 6.500000; - description = "DBL uribl spam"; - name = "DBL_SPAM"; - } - symbol { - weight = 6.500000; - description = "DBL uribl phishing"; - name = "DBL_PHISH"; - } - symbol { - weight = 6.500000; - description = "DBL uribl malware"; - name = "DBL_MALWARE"; - } - symbol { - weight = 5.500000; - description = "DBL uribl botnet C&C domain"; - name = "DBL_BOTNET"; - } - symbol { - weight = 6.500000; - description = "DBL uribl abused legit spam"; - name = "DBL_ABUSE"; - } - symbol { - weight = 1.500000; - description = "DBL uribl abused spammed redirector domain"; - name = "DBL_ABUSE_REDIR"; - } - symbol { - weight = 7.500000; - description = "DBL uribl abused legit phish"; - name = "DBL_ABUSE_PHISH"; - } - symbol { - weight = 7.500000; - description = "DBL uribl abused legit malware"; - name = "DBL_ABUSE_MALWARE"; - } - symbol { - weight = 5.500000; - description = "DBL uribl abused legit botnet C&C"; - name = "DBL_ABUSE_BOTNET"; - } - symbol { - weight = 0.00000; - description = "DBL uribl IP queries prohibited!"; - name = "DBL_PROHIBIT"; - } - symbol { - weight = 7.5; - description = "uribl.com black url"; - name = "URIBL_BLACK"; - } - symbol { - weight = 3.5; - description = "uribl.com red url"; - name = "URIBL_RED"; - } - symbol { - weight = 1.5; - description = "uribl.com grey url"; - name = "URIBL_GREY"; - } - symbol { - weight = 9.500000; - description = "rambler.ru emailbl"; - name = "RAMBLER_EMAILBL"; - } - } - - group { - name = "phishing"; - - symbol { - weight = 5.0; - description = "Phished mail"; - name = "PHISHING"; - } - } - - group { - name = "date"; - - symbol { - weight = 4.0; - description = "Message date is in future"; - name = "DATE_IN_FUTURE"; - } - symbol { - weight = 1.0; - description = "Message date is in past"; - name = "DATE_IN_PAST"; - } - symbol { - weight = 1.0; - description = "Message date is missing"; - name = "MISSING_DATE"; - } - } - - group { - name = "hfilter"; - - symbol { - weight = 3.00; - name = "HFILTER_HELO_BAREIP"; - description = "Helo host is bare ip"; - } - symbol { - weight = 4.50; - name = "HFILTER_HELO_BADIP"; - description = "Helo host is very bad ip"; - } - symbol { - weight = 2.00; - name = "HFILTER_HELO_UNKNOWN"; - description = "Helo host empty or unknown"; - } - symbol { - weight = 0.5; - name = "HFILTER_HELO_1"; - description = "Helo host checks (very low)"; - } - symbol { - weight = 1.00; - name = "HFILTER_HELO_2"; - description = "Helo host checks (low)"; - } - symbol { - weight = 2.00; - name = "HFILTER_HELO_3"; - description = "Helo host checks (medium)"; - } - symbol { - weight = 2.50; - name = "HFILTER_HELO_4"; - description = "Helo host checks (hard)"; - } - symbol { - weight = 3.00; - name = "HFILTER_HELO_5"; - description = "Helo host checks (very hard)"; - } - symbol { - weight = 0.5; - name = "HFILTER_HOSTNAME_1"; - description = "Hostname checks (very low)"; - } - symbol { - weight = 1.00; - name = "HFILTER_HOSTNAME_2"; - description = "Hostname checks (low)"; - } - symbol { - weight = 2.00; - name = "HFILTER_HOSTNAME_3"; - description = "Hostname checks (medium)"; - } - symbol { - weight = 2.50; - name = "HFILTER_HOSTNAME_4"; - description = "Hostname checks (hard)"; - } - symbol { - weight = 3.00; - name = "HFILTER_HOSTNAME_5"; - description = "Hostname checks (very hard)"; - } - symbol { - weight = 0.20; - name = "HFILTER_HELO_NORESOLVE_MX"; - description = "MX found in Helo and no resolve"; - } - symbol { - weight = 0.3; - name = "HFILTER_HELO_NORES_A_OR_MX"; - description = "Helo no resolve to A or MX"; - } - symbol { - weight = 1.00; - name = "HFILTER_HELO_IP_A"; - description = "Helo A IP != hostname IP"; - } - symbol { - weight = 2.00; - name = "HFILTER_HELO_NOT_FQDN"; - description = "Helo not FQDN"; - } - symbol { - weight = 0.5; - name = "HFILTER_FROMHOST_NORESOLVE_MX"; - description = "MX found in FROM host and no resolve"; - } - symbol { - weight = 1.50; - name = "HFILTER_FROMHOST_NORES_A_OR_MX"; - description = "FROM host no resolve to A or MX"; - } - symbol { - weight = 3.00; - name = "HFILTER_FROMHOST_NOT_FQDN"; - description = "FROM host not FQDN"; - } - symbol { - weight = 0.00; - name = "HFILTER_FROM_BOUNCE"; - description = "Bounce message"; - } - symbol { - weight = 0.50; - name = "HFILTER_MID_NORESOLVE_MX"; - description = "MX found in Message-id host and no resolve"; - } - symbol { - weight = 0.50; - name = "HFILTER_MID_NORES_A_OR_MX"; - description = "Message-id host no resolve to A or MX"; - } - symbol { - weight = 0.50; - name = "HFILTER_MID_NOT_FQDN"; - description = "Message-id host not FQDN"; - } - symbol { - weight = 4.00; - name = "HFILTER_HOSTNAME_UNKNOWN"; - description = "Unknown hostname (no PTR or no resolve PTR to hostname)"; - } - symbol { - weight = 1.50; - name = "HFILTER_RCPT_BOUNCEMOREONE"; - description = "Message from bounce and over 1 recepient"; - } - symbol { - weight = 3.50; - name = "HFILTER_URL_ONLY"; - description = "URL only in body"; - } - symbol { - weight = 2.20; - name = "HFILTER_URL_ONELINE"; - description = "One line URL and text in body"; - } - } - - group { - name = "dmarc"; - - symbol { - weight = -1.0; - name = "DMARC_POLICY_ALLOW"; - description = "DMARC permit policy"; - } - symbol { - weight = 2.0; - name = "DMARC_POLICY_REJECT"; - description = "DMARC reject policy"; - } - symbol { - weight = 1.5; - name = "DMARC_POLICY_QUARANTINE"; - description = "DMARC quarantine policy"; - } - symbol { - weight = 0.1; - name = "DMARC_POLICY_SOFTFAIL"; - description = "DMARC failed"; - } - } -} diff --git a/data/templates/rspamd/metrics.conf.local b/data/templates/rspamd/metrics.conf.local new file mode 100644 index 000000000..583280e70 --- /dev/null +++ b/data/templates/rspamd/metrics.conf.local @@ -0,0 +1,8 @@ +# Metrics settings +# This define overridden options. + +actions { + reject = 21; + add_header = 8; + greylist = 4; +} diff --git a/debian/changelog b/debian/changelog index 01eaa7089..81124e12b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,66 @@ +moulinette-yunohost (2.2.4) stable; urgency=low + + [ Jérôme Lebleu ] + * [fix] Update first registered domain with DynDNS instead of current_host + * [fix] Set found private key and don't validate it in dyndns_update + * [fix] Use dyndns.yunohost.org instead of dynhost.yunohost.org + + [ opi ] + * [fix] Catch ConnectionError from requests package + + -- Jérôme Lebleu Sun, 27 Mar 2016 16:30:42 +0200 + +yunohost (2.3.11.2) testing; urgency=low + + * [fix] Don't fail dnsmasq regen if IPv4/6 cannot be retrieved + + -- Jérôme Lebleu Wed, 23 Mar 2016 14:57:22 +0100 + +yunohost (2.3.11.1) testing; urgency=low + + * [deb] Include sysvinit services and files in the package, thanks to + nthykier and pabs from #debian-mentor + + -- Jérôme Lebleu Wed, 23 Mar 2016 12:38:56 +0100 + +yunohost (2.3.11) testing; urgency=low + + [ Laurent Peuch ] + * [mod] Explain how to start yunohost-firewall service + + [ Jérôme Lebleu ] + * [fix] Remove useless API routes for some actions + * [fix] Update API route for hook_callback action + * [deb] Attempt to improve services management in Debian packaging + * [deb] Add missing cron dependency + * [deb] Clean debian/control with cosmetic changes + * [deb] Fix helpers bash script installation + + [ Julien Malik ] + * [enh] Add helper for IP address validation + * [enh] move /usr/share/yunohost/apps/helpers to + /usr/share/yunohost/helpers since it became of more general use + * [enh] Remove unused checkupdate and upgrade scripts + * [fix] Validate IP addresses returned by ipX.yunohost.org + * [fix] fix lintian script-not-executable + * [deb] dh_python2 replaces shebang during build. Using the correct one + in source directly + + [ Moul ] + * [enh] Add '-a' argument's usage example for app_install + + [ opi ] + * [enh] Add diagnosis function. #39 + * [enh] Redirect most of 404 to maindomain.tld/yunohost/sso + * [enh] Add --installed and --with-backup to app_list action (wip #227) + * [enh] More explicit backup forbidden directory error message. + * [enh] Use dedicated app list domain. + * [fix] Use only dyndns.yunohost.org domain. + * [fix] Use plain text 502 error page. + * [fix] Cleaner Nginx redirection rules. Use permanent only when paths match. + + -- Jérôme Lebleu Wed, 23 Mar 2016 10:39:34 +0100 + yunohost (2.3.10.2) testing; urgency=low * [fix] Workaround for the bad people who are not using IPv6 yet @@ -332,12 +395,6 @@ moulinette-yunohost (2.3.0) testing; urgency=low -- Jérôme Lebleu Tue, 08 Sep 2015 14:19:28 +0200 -moulinette-yunohost (2.2.3-1) stable; urgency=low - - * [fix] Catch ConnectionError from requests package - - -- opi Sun, 06 Mar 2016 21:51:06 +0100 - moulinette-yunohost (2.2.3) stable; urgency=low * [fix] Catch proper exception in backup_list (fix #65) diff --git a/debian/control b/debian/control index 692878f1e..2053e9d60 100644 --- a/debian/control +++ b/debian/control @@ -9,50 +9,41 @@ Homepage: https://yunohost.org/ Package: yunohost Architecture: all -Depends: ${python:Depends}, ${misc:Depends}, - moulinette (>= 2.3.4), - python-psutil, - python-requests, - glances, - python-pip, - python-miniupnpc | pyminiupnpc, - dnsutils, - bind9utils, - python-apt, - ca-certificates, - python-dnspython, - netcat-openbsd, - iproute, - unzip, - git-core, - curl, - mariadb-server | mysql-server, php5-mysql | php5-mysqlnd, - slapd, ldap-utils, sudo-ldap, libnss-ldapd, - 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, - dnsmasq, openssl, avahi-daemon, - ssowat, metronome, - rspamd, rmilter (>=1.7.0), redis-server, opendkim-tools -Recommends: yunohost-admin, - bash-completion, rsyslog, ntp, openssh-server, - inetutils-ping | iputils-ping, - php5-gd, php5-curl, php-gettext, php5-mcrypt, - unattended-upgrades, - libdbd-ldap-perl, libnet-dns-perl +Depends: ${python:Depends}, ${misc:Depends} + , moulinette (>= 2.3.4) + , python-psutil, python-requests, python-dnspython + , python-apt, python-miniupnpc + , glances + , 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 + , 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 + , dnsmasq, openssl, avahi-daemon + , ssowat, metronome + , rspamd (>= 1.2.0), rmilter (>=1.7.0), redis-server, opendkim-tools +Recommends: yunohost-admin + , openssh-server, ntp, inetutils-ping | iputils-ping + , bash-completion, rsyslog + , php5-gd, php5-curl, php-gettext, php5-mcrypt + , python-pip + , unattended-upgrades + , libdbd-ldap-perl, libnet-dns-perl Suggests: htop, vim, rsync, acpi-support-base, udisks2 -Conflicts: iptables-persistent, - moulinette-yunohost, yunohost-config, - yunohost-config-others, yunohost-config-postfix, - yunohost-config-dovecot, yunohost-config-slapd, - yunohost-config-nginx, yunohost-config-amavis, - yunohost-config-mysql, yunohost-predepends -Replaces: moulinette-yunohost, yunohost-config, - yunohost-config-others, yunohost-config-postfix, - yunohost-config-dovecot, yunohost-config-slapd, - yunohost-config-nginx, yunohost-config-amavis, - yunohost-config-mysql, yunohost-predepends +Conflicts: iptables-persistent + , moulinette-yunohost, yunohost-config + , yunohost-config-others, yunohost-config-postfix + , yunohost-config-dovecot, yunohost-config-slapd + , yunohost-config-nginx, yunohost-config-amavis + , yunohost-config-mysql, yunohost-predepends +Replaces: moulinette-yunohost, yunohost-config + , yunohost-config-others, yunohost-config-postfix + , yunohost-config-dovecot, yunohost-config-slapd + , yunohost-config-nginx, yunohost-config-amavis + , yunohost-config-mysql, yunohost-predepends Description: manageable and configured self-hosting server YunoHost aims to make self-hosting accessible to everyone. It configures an email, Web and IM server alongside a LDAP base. It also provides diff --git a/debian/rules b/debian/rules index 51d60b695..8d280ff77 100755 --- a/debian/rules +++ b/debian/rules @@ -8,7 +8,8 @@ dh ${@} --with=python2,systemd override_dh_installinit: - dh_installinit --noscripts + dh_installinit -pyunohost --name=yunohost-api --noscripts + dh_installinit -pyunohost --name=yunohost-firewall --noscripts override_dh_systemd_enable: dh_systemd_enable --name=yunohost-api diff --git a/locales/en.json b/locales/en.json index 43de13fee..10c056d9b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -5,7 +5,7 @@ "installation_complete" : "Installation complete", "installation_failed" : "Installation failed", "unexpected_error" : "An unexpected error occured", - "action_invalid" : "Invalid action '{:s}'", + "action_invalid" : "Invalid action '{action:s}'", "not_enough_disk_space" : "Not enough free disk space on '{path:s}'", "license_undefined" : "undefined", @@ -27,6 +27,7 @@ "app_upgrade_failed" : "Unable to upgrade {app:s}", "app_id_invalid" : "Invalid app id", "app_already_installed" : "{app:s} is already installed", + "app_not_properly_removed" : "{app:s} has not been properly removed", "app_removed" : "{app:s} successfully removed", "app_location_already_used" : "An app is already installed on this location", "app_location_install_failed" : "Unable to install the app on this location", @@ -68,8 +69,10 @@ "no_ipv6_connectivity": "IPv6 connectivity is not available", "dyndns_key_generating" : "DNS key is being generated, it may take a while...", + "dyndns_key_not_found" : "DNS key not found for the domain", + "dyndns_no_domain_registered": "No domain has been registered with DynDNS", "dyndns_unavailable" : "Unavailable DynDNS subdomain", - "dyndns_registration_failed" : "Unable to register DynDNS domain: {:s}", + "dyndns_registration_failed" : "Unable to register DynDNS domain: {error:s}", "dyndns_registered" : "DynDNS domain successfully registered", "dyndns_ip_update_failed" : "Unable to update IP address on DynDNS", "dyndns_ip_updated" : "IP address successfully updated on DynDNS", @@ -79,8 +82,8 @@ "port_available" : "Port {port:d} is available", "port_unavailable" : "Port {port:d} is not available", - "port_already_opened" : "Port {} is already opened for {:s} connections", - "port_already_closed" : "Port {} is already closed for {:s} connections", + "port_already_opened" : "Port {port:d} is already opened for {ip_version:s} connections", + "port_already_closed" : "Port {port:d} is already closed for {ip_version:s} connections", "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.", "upnp_dev_not_found" : "No UPnP device found", @@ -92,12 +95,12 @@ "firewall_reloaded" : "Firewall successfully reloaded", "hook_list_by_invalid" : "Invalid property to list hook by", - "hook_name_unknown" : "Unknown hook name '{:s}'", - "hook_exec_failed" : "Script execution failed", - "hook_exec_not_terminated" : "Script execution hasn’t terminated", + "hook_name_unknown" : "Unknown hook name '{name:s}'", + "hook_exec_failed" : "Script execution failed: {path:s}", + "hook_exec_not_terminated" : "Script execution hasn’t terminated: {path:s}", "mountpoint_unknown" : "Unknown mountpoint", - "unit_unknown" : "Unknown unit '{:s}'", + "unit_unknown" : "Unknown unit '{unit:s}'", "monitor_period_invalid" : "Invalid time period", "monitor_stats_no_update" : "No monitoring statistics to update", "monitor_stats_file_not_found" : "Statistics file not found", @@ -107,25 +110,26 @@ "monitor_not_enabled" : "Server monitoring is not enabled", "monitor_glances_con_failed" : "Unable to connect to Glances server", - "service_unknown" : "Unknown service '{:s}'", - "service_add_failed" : "Unable to add service '{:s}'", - "service_added" : "Service successfully added", - "service_remove_failed" : "Unable to remove service '{:s}'", - "service_removed" : "Service successfully removed", - "service_start_failed" : "Unable to start service '{:s}'", - "service_already_started" : "Service '{:s}' is already started", - "service_started" : "Service '{:s}' successfully started", - "service_stop_failed" : "Unable to stop service '{:s}'", - "service_already_stopped" : "Service '{:s}' is already stopped", - "service_stopped" : "Service '{:s}' successfully stopped", - "service_enable_failed" : "Unable to enable service '{:s}'", - "service_enabled" : "Service '{:s}' successfully enabled", - "service_disable_failed" : "Unable to disable service '{:s}'", - "service_disabled" : "Service '{:s}' successfully disabled", - "service_status_failed" : "Unable to determine status of service '{:s}'", - "service_no_log" : "No log to display for service '{:s}'", - "service_cmd_exec_failed" : "Unable to execute command '{:s}'", - "services_configured": "Configuration successfully generated", + "service_unknown" : "Unknown service '{service:s}'", + "service_add_failed" : "Unable to add service '{service:s}'", + "service_added" : "Service successfully added: '{service:s}'", + "service_remove_failed" : "Unable to remove service '{service:s}'", + "service_removed" : "Service successfully removed: '{service:s}'", + "service_start_failed" : "Unable to start service '{service:s}'", + "service_already_started" : "Service '{service:s}' is already started", + "service_started" : "Service '{service:s}' successfully started", + "service_stop_failed" : "Unable to stop service '{service:s}'", + "service_already_stopped" : "Service '{service:s}' is already stopped", + "service_stopped" : "Service '{service:s}' successfully stopped", + "service_enable_failed" : "Unable to enable service '{service:s}'", + "service_enabled" : "Service '{service:s}' successfully enabled", + "service_disable_failed" : "Unable to disable service '{service:s}'", + "service_disabled" : "Service '{service:s}' successfully disabled", + "service_status_failed" : "Unable to determine status of service '{service:s}'", + "service_no_log" : "No log to display for service '{service:s}'", + "service_cmd_exec_failed" : "Unable to execute command '{command:s}'", + "service_configured": "Configuration successfully generated for service '{service:s}'", + "service_configured_all": "Configuration successfully generated for every services", "service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file).", "no_such_conf_file": "Unable to copy the file {file:s}: the file does not exist", "service_add_configuration": "Adding the configuration file {file:s}", @@ -148,14 +152,14 @@ "updating_apt_cache" : "Updating the lists of available packages...", "update_cache_failed" : "Unable to update APT cache", "packages_no_upgrade" : "There is no package to upgrade", - "packages_upgrade_critical_later" : "Critical packages ({:s}) will be upgraded later", + "packages_upgrade_critical_later" : "Critical packages ({packages:s}) will be upgraded later", "upgrading_packages" : "Upgrading packages...", "packages_upgrade_failed" : "Unable to upgrade all packages", "system_upgraded" : "System successfully upgraded", "backup_action_required" : "You must specify something to save", "backup_output_directory_required" : "You must provide an output directory for the backup", - "backup_output_directory_forbidden" : "Forbidden output directory", + "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" : "Output directory is not empty", "backup_hook_unknown" : "Backup hook '{hook:s}' unknown", "backup_running_hooks" : "Running backup hooks...", @@ -190,10 +194,10 @@ "backup_deleted" : "Backup successfully deleted", "field_invalid" : "Invalid field '{:s}'", - "mail_domain_unknown" : "Unknown mail address domain '{:s}'", - "mail_alias_remove_failed" : "Unable to remove mail alias '{:s}'", - "mail_forward_remove_failed" : "Unable to remove mail forward '{:s}'", - "user_unknown" : "Unknown user", + "mail_domain_unknown" : "Unknown mail address domain '{domain:s}'", + "mail_alias_remove_failed" : "Unable to remove mail alias '{mail:s}'", + "mail_forward_remove_failed" : "Unable to remove mail forward '{mail:s}'", + "user_unknown" : "Unknown user: {user:s}", "system_username_exists" : "Username already exists in the system users", "user_creation_failed" : "Unable to create user", "user_created" : "User successfully created", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 716dcf4cd..4b18b5461 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -85,7 +85,7 @@ def app_fetchlist(url=None, name=None): Keyword argument: name -- Name of the list (default yunohost) - url -- URL of remote JSON list (default https://yunohost.org/official.json) + url -- URL of remote JSON list (default https://app.yunohost.org/official.json) """ # Create app path if not exists @@ -93,7 +93,7 @@ def app_fetchlist(url=None, name=None): except OSError: os.makedirs(repo_path) if url is None: - url = 'https://yunohost.org/official.json' + url = 'https://app.yunohost.org/official.json' name = 'yunohost' else: if name is None: @@ -131,7 +131,7 @@ def app_removelist(name): logger.success(m18n.n('appslist_removed')) -def app_list(offset=None, limit=None, filter=None, raw=False): +def app_list(offset=None, limit=None, filter=None, raw=False, installed=False, with_backup=False): """ List apps @@ -140,12 +140,15 @@ def app_list(offset=None, limit=None, filter=None, raw=False): offset -- Starting number for app fetching limit -- Maximum number of app fetched raw -- Return the full app_dict + installed -- Return only installed apps + with_backup -- Return only apps with backup feature (force --installed filter) """ if offset: offset = int(offset) else: offset = 0 if limit: limit = int(limit) else: limit = 1000 + installed = with_backup or installed app_dict = {} if raw: @@ -188,16 +191,27 @@ def app_list(offset=None, limit=None, filter=None, raw=False): for app_id, app_info_dict in sorted_app_dict.items(): if i < limit: if (filter and ((filter in app_id) or (filter in app_info_dict['manifest']['name']))) or not filter: - installed = _is_installed(app_id) + app_installed = _is_installed(app_id) + + # Only installed apps filter + if installed and not app_installed: + continue + + # Filter only apps with backup and restore scripts + if with_backup and ( + not os.path.isfile(apps_setting_path + app_id + '/scripts/backup') or + not os.path.isfile(apps_setting_path + app_id + '/scripts/restore') + ): + continue if raw: - app_info_dict['installed'] = installed - if installed: + app_info_dict['installed'] = app_installed + if app_installed: app_info_dict['status'] = _get_app_status(app_id) list_dict[app_id] = app_info_dict else: label = None - if installed: + if app_installed: app_info_dict_raw = app_info(app=app_id, raw=True) label = app_info_dict_raw['settings']['label'] list_dict.append({ @@ -209,7 +223,7 @@ def app_list(offset=None, limit=None, filter=None, raw=False): # FIXME: Temporarly allow undefined license 'license': app_info_dict['manifest'].get('license', m18n.n('license_undefined')), - 'installed': installed + 'installed': app_installed }) i += 1 else: @@ -515,37 +529,53 @@ def app_install(auth, app, label=None, args=None): # Move scripts and manifest to the right place os.system('cp %s/manifest.json %s' % (app_tmp_folder, app_setting_path)) os.system('cp -R %s/scripts %s' % (app_tmp_folder, app_setting_path)) + + # Execute the app install script + install_retcode = 1 try: - if hook_exec(app_tmp_folder + '/scripts/install', args=args_list, env=env_dict) == 0: - # Store app status - with open(app_setting_path + '/status.json', 'w+') as f: - json.dump(status, f) - - # Clean and set permissions - shutil.rmtree(app_tmp_folder) - os.system('chmod -R 400 %s' % app_setting_path) - os.system('chown -R root: %s' % app_setting_path) - os.system('chown -R admin: %s/scripts' % app_setting_path) - app_ssowatconf(auth) - logger.success(m18n.n('installation_complete')) - else: - raise MoulinetteError(errno.EIO, m18n.n('installation_failed')) + install_retcode = hook_exec( + os.path.join(app_tmp_folder, 'scripts/install'), args_list, env=env_dict) + except (KeyboardInterrupt, EOFError): + install_retcode = -1 except: - # Execute remove script and clean folders - hook_remove(app_instance_name) - shutil.rmtree(app_setting_path) - shutil.rmtree(app_tmp_folder) + 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) - # Reraise proper exception - try: - raise - except MoulinetteError: - raise - except (KeyboardInterrupt, EOFError): - raise MoulinetteError(errno.EINTR, m18n.g('operation_interrupted')) - except Exception as e: - logger.debug('app installation failed', exc_info=1) - raise MoulinetteError(errno.EIO, m18n.n('unexpected_error')) + # Execute remove script + remove_retcode = hook_exec( + os.path.join(app_tmp_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 + hook_remove(app_instance_name) + shutil.rmtree(app_setting_path) + shutil.rmtree(app_tmp_folder) + + if install_retcode == -1: + raise MoulinetteError(errno.EINTR, + m18n.g('operation_interrupted')) + raise MoulinetteError(errno.EIO, m18n.n('installation_failed')) + + # Store app status + with open(app_setting_path + '/status.json', 'w+') as f: + json.dump(status, f) + + # Clean and set permissions + shutil.rmtree(app_tmp_folder) + os.system('chmod -R 400 %s' % app_setting_path) + os.system('chown -R root: %s' % app_setting_path) + os.system('chown -R admin: %s/scripts' % app_setting_path) + + app_ssowatconf(auth) + + logger.success(m18n.n('installation_complete')) def app_remove(auth, app): @@ -624,10 +654,7 @@ def app_addaccess(auth, apps, users=[]): try: user_info(auth, allowed_user) except MoulinetteError: - # FIXME: Add username keyword in user_unknown - logger.warning('{0}{1}'.format( - m18n.g('colon', m18n.n('user_unknown')), - allowed_user)) + logger.warning(m18n.n('user_unknown', user=allowed_user)) continue allowed_users.add(allowed_user) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 76b4d9508..03ba0d61a 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -39,6 +39,13 @@ from moulinette.core import MoulinetteError from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger +from yunohost.app import app_info, app_ssowatconf, _is_installed, _parse_app_instance_name +from yunohost.hook import ( + hook_info, hook_callback, hook_exec, custom_hook_folder +) +from yunohost.monitor import binary_to_human +from yunohost.tools import tools_postinstall + backup_path = '/home/yunohost.backup' archives_path = '%s/archives' % backup_path @@ -63,8 +70,6 @@ def backup_create(name=None, description=None, output_directory=None, """ # TODO: Add a 'clean' argument to clean output directory - from yunohost.hook import hook_info, hook_callback, hook_exec - tmp_dir = None # Validate what to backup @@ -167,9 +172,6 @@ def backup_create(name=None, description=None, output_directory=None, # Backup apps if not ignore_apps: - from yunohost.app import app_info - from yunohost.app import _parse_app_instance_name - # Filter applications to backup apps_list = set(os.listdir('/etc/yunohost/apps')) apps_filtered = set() @@ -215,7 +217,8 @@ def backup_create(name=None, description=None, output_directory=None, env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_instance_name], - raise_on_error=True, env=env_dict) + raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict) + except: logger.exception(m18n.n('backup_app_failed', app=app_instance_name)) # Cleaning app backup directory @@ -288,21 +291,20 @@ def backup_create(name=None, description=None, output_directory=None, return { 'archive': info } -def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=False, force=False): +def backup_restore(auth, name, hooks=[], ignore_hooks=False, + apps=[], ignore_apps=False, force=False): """ Restore from a local backup archive Keyword argument: name -- Name of the local backup archive hooks -- List of restoration hooks names to execute + ignore_hooks -- Do not execute backup hooks apps -- List of application names to restore ignore_apps -- Do not restore apps force -- Force restauration on an already installed system """ - from yunohost.hook import hook_info, hook_callback, hook_exec - from yunohost.hook import custom_hook_folder - # Validate what to restore if ignore_hooks and ignore_apps: raise MoulinetteError(errno.EINVAL, @@ -380,8 +382,6 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals _clean_tmp_dir() raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed')) else: - from yunohost.tools import tools_postinstall - # Retrieve the domain from the backup try: with open("%s/yunohost/current_host" % tmp_dir, 'r') as f: @@ -437,9 +437,6 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals # Add apps restore hook if not ignore_apps: - from yunohost.app import _is_installed - from yunohost.app import _parse_app_instance_name - # Filter applications to restore apps_list = set(info['apps'].keys()) apps_filtered = set() @@ -454,6 +451,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals for app_instance_name in apps_filtered: tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_instance_name) + tmp_app_bkp_dir = tmp_app_dir + '/backup' # Check if the app is not already installed if _is_installed(app_instance_name): @@ -487,7 +485,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) hook_exec(tmp_script, args=[tmp_app_dir + '/backup', app_instance_name], - raise_on_error=True, env=env_dict) + raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict) except: logger.exception(m18n.n('restore_app_failed', app=app_instance_name)) # Cleaning app directory @@ -501,6 +499,8 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals if not result['hooks'] and not result['apps']: _clean_tmp_dir(1) raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done')) + if result['apps']: + app_ssowatconf(auth) _clean_tmp_dir() logger.success(m18n.n('restore_complete')) @@ -553,8 +553,6 @@ def backup_info(name, with_details=False, human_readable=False): human_readable -- Print sizes in human readable format """ - from yunohost.monitor import binary_to_human - archive_file = '%s/%s.tar.gz' % (archives_path, name) if not os.path.isfile(archive_file): raise MoulinetteError(errno.EIO, @@ -602,7 +600,6 @@ def backup_delete(name): name -- Name of the local backup archive """ - from yunohost.hook import hook_callback hook_callback('pre_backup_delete', args=[name]) archive_file = '%s/%s.tar.gz' % (archives_path, name) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 97826062f..878bc577e 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -24,12 +24,12 @@ Subscribe and Update DynDNS Hosts """ import os -import requests import re import json import glob import base64 import errno +import requests import subprocess from moulinette.core import MoulinetteError @@ -62,6 +62,10 @@ class IPRouteLine(object): for k, v in self.m.groupdict().items(): setattr(self, k, v) +re_dyndns_private_key = re.compile( + r'.*/K(?P[^\s\+]+)\.\+157.+\.private$' +) + def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None): """ @@ -107,30 +111,26 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None try: error = json.loads(r.text)['error'] except: error = "Server error" raise MoulinetteError(errno.EPERM, - m18n.n('dyndns_registration_failed', error)) + m18n.n('dyndns_registration_failed', error=error)) logger.success(m18n.n('dyndns_registered')) dyndns_installcron() -def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None, +def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, ipv4=None, ipv6=None): """ Update IP on DynDNS platform Keyword argument: - domain -- Full domain to subscribe with + domain -- Full domain to update dyn_host -- Dynette DNS server to inform key -- Public DNS key ipv4 -- IP address to send ipv6 -- IPv6 address to send """ - if domain is None: - with open('/etc/yunohost/current_host', 'r') as f: - domain = f.readline().rstrip() - # IPv4 if ipv4 is None: ipv4 = get_public_ip() @@ -168,6 +168,37 @@ def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None, old_ipv6 = '0000:0000:0000:0000:0000:0000:0000:0000' if old_ip != ipv4 or old_ipv6 != ipv6: + if domain is None: + # Retrieve the first registered domain + for path in glob.iglob('/etc/yunohost/dyndns/K*.private'): + match = re_dyndns_private_key.match(path) + if not match: + continue + _domain = match.group('domain') + try: + # Check if domain is registered + if requests.get('https://{0}/test/{1}'.format( + dyn_host, _domain)).status_code == 200: + continue + except requests.ConnectionError: + raise MoulinetteError(errno.ENETUNREACH, + m18n.n('no_internet_connection')) + domain = _domain + key = path + break + if not domain: + raise MoulinetteError(errno.EINVAL, + m18n.n('dyndns_no_domain_registered')) + + if key is None: + keys = glob.glob( + '/etc/yunohost/dyndns/K{0}.+*.private'.format(domain)) + if len(keys) > 0: + key = keys[0] + if not key: + raise MoulinetteError(errno.EIO, + m18n.n('dyndns_key_not_found')) + host = domain.split('.')[1:] host = '.'.join(host) lines = [ @@ -209,11 +240,7 @@ def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None, for line in lines: zone.write(line + '\n') - if key is None: - private_key_file = glob.glob('/etc/yunohost/dyndns/*.private')[0] - else: - private_key_file = key - if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' % private_key_file) == 0: + if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' % key) == 0: logger.success(m18n.n('dyndns_ip_updated')) with open('/etc/yunohost/dyndns/old_ip', 'w') as f: f.write(ipv4) diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 678ff7db5..ef6249434 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -83,7 +83,7 @@ def firewall_allow(protocol, port, ipv4_only=False, ipv6_only=False, firewall[i][p].append(port) else: ipv = "IPv%s" % i[3] - logger.warning(m18n.n('port_already_opened', port, ipv)) + logger.warning(m18n.n('port_already_opened', port=port, ip_version=ipv)) # Add port forwarding with UPnP if not no_upnp and port not in firewall['uPnP'][p]: firewall['uPnP'][p].append(port) @@ -140,7 +140,7 @@ def firewall_disallow(protocol, port, ipv4_only=False, ipv6_only=False, firewall[i][p].remove(port) else: ipv = "IPv%s" % i[3] - logger.warning(m18n.n('port_already_closed', port, ipv)) + logger.warning(m18n.n('port_already_closed', port=port, ip_version=ipv)) # Remove port forwarding with UPnP if upnp and port in firewall['uPnP'][p]: firewall['uPnP'][p].remove(port) @@ -335,7 +335,7 @@ def firewall_upnp(action='status', no_refresh=False): if action == 'status': no_refresh = True else: - raise MoulinetteError(errno.EINVAL, m18n.n('action_invalid', action)) + raise MoulinetteError(errno.EINVAL, m18n.n('action_invalid', action=action)) # Refresh port mapping using UPnP if not no_refresh: diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index a61e3eb1c..acf51f4e2 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -110,7 +110,7 @@ def hook_info(action, name): }) if not hooks: - raise MoulinetteError(errno.EINVAL, m18n.n('hook_name_unknown', name)) + raise MoulinetteError(errno.EINVAL, m18n.n('hook_name_unknown', name=name)) return { 'action': action, 'name': name, @@ -275,7 +275,8 @@ def hook_callback(action, hooks=[], args=None): return result -def hook_exec(path, args=None, raise_on_error=False, no_trace=False, env=None): +def hook_exec(path, args=None, raise_on_error=False, no_trace=False, + chdir=None, env=None): """ Execute hook from a file with arguments @@ -284,6 +285,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, env=None): args -- A list of arguments to pass to the script raise_on_error -- Raise if the script returns a non-zero exit code no_trace -- Do not print each command that will be executed + chdir -- The directory from where the script will be executed env -- Dictionnary of environment variables to export """ @@ -297,50 +299,54 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, env=None): raise MoulinetteError(errno.EIO, m18n.g('file_not_exist')) # Construct command variables - cmd_fdir, cmd_fname = os.path.split(path) - cmd_fname = './{0}'.format(cmd_fname) - cmd_args = '' if args and isinstance(args, list): # Concatenate arguments and escape them with double quotes to prevent # bash related issue if an argument is empty and is not the last cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in args)) + if not chdir: + # use the script directory as current one + chdir, cmd_script = os.path.split(path) + cmd_script = './{0}'.format(cmd_script) + else: + cmd_script = path envcli = '' if env is not None: envcli = ' '.join([ '{key}="{val}"'.format(key=key, val=val) for key,val in env.items()]) # Construct command to execute - command = ['sudo', '-u', 'admin', '-H', 'sh', '-c'] + command = ['sudo', '-n', '-u', 'admin', '-H', 'sh', '-c'] if no_trace: - cmd = 'cd "{0:s}" && {1:s} /bin/bash "{2:s}" {3:s}' + cmd = '{envcli} /bin/bash "{script}" {args}' else: # use xtrace on fd 7 which is redirected to stdout - cmd = 'cd "{0:s}" && {1:s} BASH_XTRACEFD=7 /bin/bash -x "{2:s}" {3:s} 7>&1' - command.append(cmd.format(cmd_fdir, envcli, cmd_fname, cmd_args)) + cmd = '{envcli} BASH_XTRACEFD=7 /bin/bash -x "{script}" {args} 7>&1' + command.append(cmd.format(envcli=envcli, script=cmd_script, args=cmd_args)) if logger.isEnabledFor(log.DEBUG): logger.info(m18n.n('executing_command', command=' '.join(command))) else: - logger.info(m18n.n('executing_script', script='{0}/{1}'.format( - cmd_fdir, cmd_fname))) + logger.info(m18n.n('executing_script', script=path)) # Define output callbacks and call command callbacks = ( lambda l: logger.info(l.rstrip()), lambda l: logger.warning(l.rstrip()), ) - returncode = call_async_output(command, callbacks, shell=False) + returncode = call_async_output( + command, callbacks, shell=False, cwd=chdir + ) # Check and return process' return code if returncode is None: if raise_on_error: - raise MoulinetteError(m18n.n('hook_exec_not_terminated')) + raise MoulinetteError(m18n.n('hook_exec_not_terminated', path=path)) else: - logger.error(m18n.n('hook_exec_not_terminated')) + logger.error(m18n.n('hook_exec_not_terminated', path=path)) return 1 elif raise_on_error and returncode != 0: - raise MoulinetteError(m18n.n('hook_exec_failed')) + raise MoulinetteError(m18n.n('hook_exec_failed', path=path)) return returncode diff --git a/src/yunohost/monitor.py b/src/yunohost/monitor.py index 542455dd7..5142c8305 100644 --- a/src/yunohost/monitor.py +++ b/src/yunohost/monitor.py @@ -139,7 +139,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False): for dname in devices_names: _set(dname, 'not-available') else: - raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u)) + raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u)) if result_dname is not None: return result[result_dname] @@ -237,7 +237,7 @@ def monitor_network(units=None, human_readable=False): 'gateway': gateway, } else: - raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u)) + raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u)) if len(units) == 1: return result[units[0]] @@ -287,7 +287,7 @@ def monitor_system(units=None, human_readable=False): elif u == 'infos': result[u] = json.loads(glances.getSystem()) else: - raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u)) + raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u)) if len(units) == 1 and type(result[units[0]]) is not str: return result[units[0]] diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 4cedfb35e..7443ffab2 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -75,9 +75,9 @@ def service_add(name, status=None, log=None, runlevel=None): try: _save_services(services) except: - raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', name)) + raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', service=name)) - logger.success(m18n.n('service_added')) + logger.success(m18n.n('service_added', service=name)) def service_remove(name): @@ -93,14 +93,14 @@ def service_remove(name): try: del services[name] except KeyError: - raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', name)) + raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=name)) try: _save_services(services) except: - raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', name)) + raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', service=name)) - logger.success(m18n.n('service_removed')) + logger.success(m18n.n('service_removed', service=name)) def service_start(names): @@ -115,12 +115,12 @@ def service_start(names): names = [names] for name in names: if _run_service_command('start', name): - logger.success(m18n.n('service_started', name)) + logger.success(m18n.n('service_started', service=name)) else: if service_status(name)['status'] != 'running': raise MoulinetteError(errno.EPERM, - m18n.n('service_start_failed', name)) - logger.info(m18n.n('service_already_started', name)) + m18n.n('service_start_failed', service=name)) + logger.info(m18n.n('service_already_started', service=name)) def service_stop(names): @@ -135,12 +135,12 @@ def service_stop(names): names = [names] for name in names: if _run_service_command('stop', name): - logger.success(m18n.n('service_stopped', name)) + logger.success(m18n.n('service_stopped', service=name)) else: if service_status(name)['status'] != 'inactive': raise MoulinetteError(errno.EPERM, - m18n.n('service_stop_failed', name)) - logger.info(m18n.n('service_already_stopped', name)) + m18n.n('service_stop_failed', service=name)) + logger.info(m18n.n('service_already_stopped', service=name)) def service_enable(names): @@ -155,10 +155,10 @@ def service_enable(names): names = [names] for name in names: if _run_service_command('enable', name): - logger.success(m18n.n('service_enabled', name)) + logger.success(m18n.n('service_enabled', service=name)) else: raise MoulinetteError(errno.EPERM, - m18n.n('service_enable_failed', name)) + m18n.n('service_enable_failed', service=name)) def service_disable(names): @@ -173,10 +173,10 @@ def service_disable(names): names = [names] for name in names: if _run_service_command('disable', name): - logger.success(m18n.n('service_disabled', name)) + logger.success(m18n.n('service_disabled', service=name)) else: raise MoulinetteError(errno.EPERM, - m18n.n('service_disable_failed', name)) + m18n.n('service_disable_failed', service=name)) def service_status(names=[]): @@ -200,7 +200,7 @@ def service_status(names=[]): for name in names: if check_names and name not in services.keys(): raise MoulinetteError(errno.EINVAL, - m18n.n('service_unknown', name)) + m18n.n('service_unknown', service=name)) status = None if 'status' not in services[name] or \ @@ -221,7 +221,7 @@ def service_status(names=[]): shell=True) except subprocess.CalledProcessError as e: if 'usage:' in e.output.lower(): - logger.warning(m18n.n('service_status_failed', name)) + logger.warning(m18n.n('service_status_failed', service=name)) else: result[name]['status'] = 'inactive' else: @@ -253,7 +253,7 @@ def service_log(name, number=50): services = _get_services() if name not in services.keys(): - raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', name)) + raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=name)) if 'log' in services[name]: log_list = services[name]['log'] @@ -268,7 +268,7 @@ def service_log(name, number=50): else: result[log_path] = _tail(log_path, int(number)) else: - raise MoulinetteError(errno.EPERM, m18n.n('service_no_log', name)) + raise MoulinetteError(errno.EPERM, m18n.n('service_no_log', service=name)) return result @@ -289,9 +289,10 @@ def service_regenconf(service=None, force=False): if service is not None: hook_callback('conf_regen', [service], args=[force]) + logger.success(m18n.n('service_configured', service=service)) else: hook_callback('conf_regen', args=[force]) - logger.success(m18n.n('services_configured')) + logger.success(m18n.n('service_configured_all')) def _run_service_command(action, service): @@ -304,8 +305,7 @@ def _run_service_command(action, service): """ if service not in _get_services().keys(): - raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', - service)) + raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service)) cmd = None if action in ['start', 'stop', 'restart', 'reload']: @@ -320,7 +320,7 @@ def _run_service_command(action, service): ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: # TODO: Log output? - logger.warning(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd))) + logger.warning(m18n.n('service_cmd_exec_failed', command=' '.join(e.cmd))) return False return True diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index b9b81723c..a17128ed0 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -380,7 +380,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False): # ... and set a hourly cron up to upgrade critical packages if critical_upgrades: logger.info(m18n.n('packages_upgrade_critical_later', - ', '.join(critical_upgrades))) + packages=', '.join(critical_upgrades))) with open('/etc/cron.d/yunohost-upgrade', 'w+') as f: f.write('00 * * * * root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin apt-get install %s -y && rm -f /etc/cron.d/yunohost-upgrade\n' % ' '.join(critical_upgrades)) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index dc3c3d6fa..ec7dd539c 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -128,7 +128,7 @@ def user_create(auth, username, firstname, lastname, mail, password, if mail[mail.find('@')+1:] not in domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('mail_domain_unknown', - mail[mail.find('@')+1:])) + domain=mail[mail.find('@')+1:])) # Get random UID/GID uid_check = gid_check = 0 @@ -275,7 +275,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, # Populate user informations result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch) if not result: - raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown')) + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username)) user = result[0] # Get modifications from arguments @@ -301,7 +301,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, if mail[mail.find('@')+1:] not in domains: raise MoulinetteError(errno.EINVAL, m18n.n('mail_domain_unknown', - mail[mail.find('@')+1:])) + domain=mail[mail.find('@')+1:])) del user['mail'][0] new_attr_dict['mail'] = [mail] + user['mail'] @@ -313,7 +313,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, if mail[mail.find('@')+1:] not in domains: raise MoulinetteError(errno.EINVAL, m18n.n('mail_domain_unknown', - mail[mail.find('@')+1:])) + domain=mail[mail.find('@')+1:])) user['mail'].append(mail) new_attr_dict['mail'] = user['mail'] @@ -325,7 +325,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, user['mail'].remove(mail) else: raise MoulinetteError(errno.EINVAL, - m18n.n('mail_alias_remove_failed', mail)) + m18n.n('mail_alias_remove_failed', mail=mail)) new_attr_dict['mail'] = user['mail'] if add_mailforward: @@ -345,7 +345,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, user['maildrop'].remove(mail) else: raise MoulinetteError(errno.EINVAL, - m18n.n('mail_forward_remove_failed', mail)) + m18n.n('mail_forward_remove_failed', mail=mail)) new_attr_dict['maildrop'] = user['maildrop'] if mailbox_quota is not None: @@ -381,7 +381,7 @@ def user_info(auth, username): if result: user = result[0] else: - raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown')) + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username)) result_dict = { 'username': user['uid'][0],