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/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 + diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter index 05f921e09..011856cd6 100755 --- a/data/hooks/conf_regen/28-rmilter +++ b/data/hooks/conf_regen/28-rmilter @@ -9,7 +9,9 @@ do_pre_regen() { install -D -m 644 rmilter.conf \ "${pending_dir}/etc/rmilter.conf" - install -D -m 644 rmilter.socket \ + # 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" } @@ -37,17 +39,19 @@ 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 - [[ "$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/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 diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index f3597e136..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 = inet:localhost:11000 +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 d585b9217..829d76418 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/spool/postfix/run/rmilter/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 diff --git a/locales/en.json b/locales/en.json index 468be0c1c..e145a1d38 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 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}!", @@ -264,5 +265,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/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')) 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..5f851471c 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 @@ -193,9 +194,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: @@ -323,8 +324,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.") @@ -341,7 +350,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)) @@ -350,6 +359,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 +376,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 +502,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...") @@ -528,7 +554,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)) @@ -562,7 +588,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") @@ -785,6 +813,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(public_ip, domain): + raise MoulinetteError(errno.EINVAL, m18n.n( + 'certmanager_domain_not_resolved_locally', domain=domain)) + def _dns_ip_match_public_ip(public_ip, domain): try: @@ -803,12 +838,24 @@ 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 +def _domain_is_resolved_locally(public_ip, domain): + try: + ip = socket.gethostbyname(domain) + 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] + + def _name_self_CA(): ca_conf = os.path.join(SSL_DIR, "openssl.ca.cnf") 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/tools.py b/src/yunohost/tools.py index 7cc221683..15c8a98f8 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')) @@ -416,7 +420,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 +432,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: 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:]) 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"))