diff --git a/.editorconfig b/.editorconfig index 85e5fd9c..fbe828c3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,9 +12,6 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.html] -indent_style = tab - [Makefile] indent_style = tab indent_size = 4 diff --git a/.gitignore b/.gitignore index 104111b7..14e6c4a7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ tools/__pycache__/ externals/ .env .vagrant -api/docs/api-docs.html -mailinabox-ca.crt +api/docs/api-docs.html \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a7d548ff..a75a9a43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,35 @@ CHANGELOG ========= +v0.53 (April 12, 2021) +---------------------- + +Software updates: + +* Upgraded Roundcube to version 1.4.11 addressing a security issue, and its desktop notifications plugin. +* Upgraded Z-Push (for Exchange/ActiveSync) to version 2.6.2. + +Control panel: + +* Backblaze B2 is now a supported backup protocol. +* Fixed an issue in the daily mail reports. +* Sort the Custom DNS by zone and qname, and add an option to go back to the old sort order (creation order). + +Mail: + +* Enable sending DMARC failure reports to senders that request them. + +Setup: + +* Fixed error when upgrading from Nextcloud 13. + v0.52 (January 31, 2021) ------------------------ Software updates: * Upgraded Roundcube to version 1.4.10. -* Upgraded zpush to 2.6.1. +* Upgraded Z-Push to 2.6.1. Mail: diff --git a/README.md b/README.md index ec41cc8e..6ab1530c 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,14 @@ -(Power) Mail-in-a-Box -===================== +This is not the original Mail-in-a-Box. See https://github.com/mail-in-a-box/mailinabox for the real deal! I made a number of modifications to to: +- add geoipblocking on the admin web console +- add geoipblocking for ssh access +- make fail2ban a more stricter +- add fail2ban filter for web scanners +- other small stuff -## Installation +Original mailinabox content starts here: -- **PRE-REQUISITES:** Debian 10 (Buster) or Ubuntu 20.04 LTS fresh installation - -Update packages: -```sh -sudo apt update -sudo apt full-upgrade -``` - -Make sure that the `en_US.UTF-8` locale exists and is set as primary (this depends on the image you use) -```sh -sudo apt install locales -sudo dpkg-reconfigure locales -``` - -Install Power-Mail-in-a-Box (short link) -```sh -curl -L https://dvn.pt/powermiab | sudo bash -``` - -If that doesn't work: -```sh -curl https://raw.githubusercontent.com/ddavness/power-mailinabox/master/setup/bootstrap.sh | sudo bash -``` - -## Current Version: v0.52.POWER.0 (Tracking v0.52) - -This is a fork of MiaB (duh), hacked and tuned to my needs: - -✅ - **Done** - -👨‍💻 - **Not there yet, but soon!** - -💤 - **I did not begin this part yet!** - -- ✅ Support for Debian AND Ubuntu 20.04 LTS; - -- ✅ Native support for SMTP relays (For example: SendGrid); - -- ✅ Bumped the bootstrap and jQuery dependencies' versions - and we've got a brand new admin panel now! - -- ✅ Per-domain `nginx` configuration: Custom pages will no longer have their pages defaulting to the MiaB services (`/admin`, `/mail`, etc.); - -- ✅ Updated NextCloud to the latest version available; - -- ✅ Performing backups immediately from the admin panel (independently from the daily schedule); - -- 👨‍💻 Encrypting backups using user-provided PGP keys; - -- 👨‍💻 Integrate a WKD server (Web Key Directory) for PGP keys; - -- 💤 Restricting access to the admin panel to certain IP's? - -- 💤 Customizing MTA names? (because privacy) - -### Ideas section: - -- 💤 Ability to download the backups from the admin panel; - -- 💤 Possibility of making some services optional (if they require more software to be installed) on setup? - -- - For example, one might simply not use NextCloud/Munin at all, and they're there... just wasting resources. - -- 💤 AXFR Transfers (for secondary DNS) using TSIG? - -- 💤 Expand DNS record options? - -- 💤 More complete webmail configuration via the admin panel/plugin management? - -- 💤 Expand the TOTP Two-Factor-Authentication for the webmail? - -- - Maybe U2F one day, too, but I don't have a capable device for this just yet... - -- 💤 Anything else I might need to use; - -All in all, I think I should rename this to something like "Central [Clown Computing](https://www.urbandictionary.com/define.php?term=clown%20computing)", since I'm trying to cram as many services as possible into that poor machine (Spending 5$ is better than spending 10$) - -Original Documentation -====================== +Mail-in-a-Box +============= By [@JoshData](https://github.com/JoshData) and [contributors](https://github.com/mail-in-a-box/mailinabox/graphs/contributors). @@ -96,7 +24,7 @@ Our goals are to: * Promote [decentralization](http://redecentralize.org/), innovation, and privacy on the web. * Have automated, auditable, and [idempotent](https://web.archive.org/web/20190518072631/https://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration. * **Not** make a totally unhackable, NSA-proof server. -* ~~**Not** make something customizable by power users.~~ +* **Not** make something customizable by power users. Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which supersedes the goals above. Please review it when joining our community. @@ -104,7 +32,7 @@ Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which su In The Box ---------- -Mail-in-a-Box turns a fresh ~~Ubuntu 18.04 LTS~~ Debian 10 (Buster) 64-bit machine into a working mail server by installing and configuring various components. +Mail-in-a-Box turns a fresh Ubuntu 20.04 or 18.04 LTS 64-bit machine into a working mail server by installing and configuring various components. It is a one-click email appliance. There are no user-configurable setup options. It "just works." @@ -139,7 +67,7 @@ Clone this repository and checkout the tag corresponding to the most recent rele $ git clone https://github.com/mail-in-a-box/mailinabox $ cd mailinabox - $ git checkout v0.52 + $ git checkout v0.53 Begin the installation. @@ -158,7 +86,7 @@ This is a challenge faced by everyone who runs their own mail server, with or wi Contributing and Development ---------------------------- -Mail-in-a-Box is an open source project. Your contributions and pull requests are welcome. See [CONTRIBUTING](CONTRIBUTING.md) to get started. +Mail-in-a-Box is an open source project. Your contributions and pull requests are welcome. See [CONTRIBUTING](CONTRIBUTING.md) to get started. The Acknowledgements diff --git a/Vagrantfile b/Vagrantfile index 6ed9dd80..467fb95e 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,20 +1,8 @@ - # -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| - config.vm.box = "debian/buster64" - config.vm.provider :virtualbox do |vb| - vb.customize ["modifyvm", :id, "--cpus", 4, "--memory", 4096] - end - config.vm.provider :libvirt do |v| - v.memory = 4096 - v.cpus = 4 - v.nested = true - end - config.vm.provider :kvm do |kvm| - kvm.memory_size = '4096m' - end + config.vm.box = "ubuntu/bionic64" # Network config: Since it's a mail server, the machine must be connected # to the public web. However, we currently don't want to expose SSH since @@ -22,20 +10,19 @@ Vagrant.configure("2") do |config| # machine on a private network. config.vm.hostname = "mailinabox.lan" config.vm.network "private_network", ip: "192.168.50.4" - config.vm.synced_folder ".", "/vagrant", nfs_version: "3" - #, :mount_options => ["ro"] config.vm.provision :shell, :inline => <<-SH # Set environment variables so that the setup script does # not ask any questions during provisioning. We'll let the # machine figure out its own public IP. export NONINTERACTIVE=1 - export PUBLIC_IP=192.168.50.4 + export PUBLIC_IP=auto export PUBLIC_IPV6=auto export PRIMARY_HOSTNAME=auto - export SKIP_NETWORK_CHECKS=1 + #export SKIP_NETWORK_CHECKS=1 + # Start the setup script. cd /vagrant setup/start.sh SH -end \ No newline at end of file +end diff --git a/api/mailinabox.yml b/api/mailinabox.yml index f5453f6f..14cf54de 100644 --- a/api/mailinabox.yml +++ b/api/mailinabox.yml @@ -499,123 +499,6 @@ paths: text/html: schema: type: string - /system/backup/new: - post: - tags: - - System - summary: Perform system backup - description: Performs a system backup. - operationId: performSystemBackup - requestBody: - required: true - content: - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/PerformBackupRequest' - examples: - incremental: - summary: Perform incremental backup. - value: - full: false - full: - summary: Force a full backup. - value: - full: true - x-codeSamples: - - lang: curl - source: | - curl -X POST "https://{host}/admin/system/backup/new" \ - -d "full=" \ - -u ":" - responses: - 200: - description: Successful operation - content: - text/html: - schema: - $ref: '#/components/schemas/PerformBackupResponse' - 403: - description: Forbidden - content: - text/plain: - schema: - type: string - /system/smtp/relay: - get: - tags: - - System - summary: Get SMTP relay configuration - description: Gets basic configuration on how the box should use third-party relay services to deliver mail. - operationId: getRelayConfig - x-codeSamples: - - lang: curl - source: | - curl -X GET "https://{host}/admin/system/smtp/relay" \ - -u ":" - responses: - 200: - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/SmtpRelayConfig' - 403: - description: Forbidden - content: - text/html: - schema: - type: string - post: - tags: - - System - summary: Set SMTP relay configuration - description: Sets the configuration on how the box should use third-party relays to deliver mail. - operationId: setRelayConfig - requestBody: - required: true - content: - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/SetSmtpRelayConfigRequest' - examples: - disable: - summary: Do not use relays. - value: - enabled: false - host: "" - auth_enabled: false - user: "" - key: "" - no_auth: - summary: Use a relay that does not require authentication. - value: - enabled: true - host: smtp.relay.net - auth_enabled: false - user: "" - key: "" - auth: - summary: Use a relay that requires authentication. - value: - enabled: true - host: smtp.relay.net - auth_enabled: true - user: someuser - key: key-or-password-here - responses: - 200: - description: Successful operation - content: - text/plain: - schema: - type: string - 403: - description: Forbidden - content: - text/html: - schema: - type: string - /ssl/status: get: tags: @@ -2576,19 +2459,6 @@ components: minimum: 1 example: 3 description: Backup config update request. - PerformBackupRequest: - type: object - required: - - full - properties: - full: - type: boolean - example: false - description: New backup type. - PerformBackupResponse: - type: string - example: OK - description: Backup creation response. SystemBackupConfigUpdateResponse: type: string example: OK @@ -2791,52 +2661,6 @@ components: type: string example: web updated description: Web update response. - SmtpRelayConfig: - type: object - required: - - enabled - - host - - auth_enabled - - user - properties: - enabled: - type: boolean - example: true - host: - type: string - example: sendgrid.net - auth_enabled: - type: boolean - example: true - user: - type: string - example: someuser - description: SMTP configuration details. - SetSmtpRelayConfigRequest: - type: object - required: - - enabled - - host - - auth_enabled - - user - - key - properties: - enabled: - type: boolean - example: true - host: - type: string - example: sendgrid.net - auth_enabled: - type: boolean - example: true - user: - type: string - example: apikey - key: - type: string - example: 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 - description: SMTP Configuration form MfaStatusResponse: type: object properties: diff --git a/conf/nginx-alldomains.conf b/conf/nginx-alldomains.conf index 95008f19..e49e5af2 100644 --- a/conf/nginx-alldomains.conf +++ b/conf/nginx-alldomains.conf @@ -1,6 +1,6 @@ + # Expose this directory as static files. root $ROOT; - - # ADDITIONAL DIRECTIVES HERE + index index.html index.htm; location = /robots.txt { log_not_found off; @@ -25,24 +25,31 @@ alias /var/lib/mailinabox/mta-sts.txt; } - # Z-Push (Microsoft Exchange ActiveSync) - location /Microsoft-Server-ActiveSync { - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/index.php; - fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc"; - fastcgi_read_timeout 630; + # Roundcube Webmail configuration. + rewrite ^/mail$ /mail/ redirect; + rewrite ^/mail/$ /mail/index.php; + location /mail/ { + index index.php; + alias /usr/local/lib/roundcubemail/; + } + location ~ /mail/config/.* { + # A ~-style location is needed to give this precedence over the next block. + return 403; + } + location ~ /mail/.*\.php { + # note: ~ has precendence over a regular location block + include fastcgi_params; + fastcgi_split_path_info ^/mail(/.*)()$; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name; fastcgi_pass php-fpm; # Outgoing mail also goes through this endpoint, so increase the maximum # file upload limit to match the corresponding Postfix limit. client_max_body_size 128M; } - location ~* ^/autodiscover/autodiscover.xml$ { - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/autodiscover/autodiscover.php; - fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc"; - fastcgi_pass php-fpm; - } + + # ADDITIONAL DIRECTIVES HERE # Disable viewing dotfiles (.htaccess, .svn, .git, etc.) # This block is placed at the end. Nginx's precedence rules means this block diff --git a/conf/nginx-primaryonly.conf b/conf/nginx-primaryonly.conf index aaaf6947..d50e5c3c 100644 --- a/conf/nginx-primaryonly.conf +++ b/conf/nginx-primaryonly.conf @@ -1,5 +1,3 @@ - # ADDITIONAL DIRECTIVES HERE - # Control Panel # Proxy /admin to our Python based control panel daemon. It is # listening on IPv4 only so use an IP address and not 'localhost'. @@ -40,30 +38,6 @@ add_header Content-Security-Policy "frame-ancestors 'none';"; } - # Roundcube Webmail configuration. - rewrite ^/mail$ /mail/ redirect; - rewrite ^/mail/$ /mail/index.php; - location /mail/ { - index index.php; - alias /usr/local/lib/roundcubemail/; - } - location ~ /mail/config/.* { - # A ~-style location is needed to give this precedence over the next block. - return 403; - } - location ~ /mail/.*\.php { - # note: ~ has precendence over a regular location block - include fastcgi_params; - fastcgi_split_path_info ^/mail(/.*)()$; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name; - fastcgi_pass php-fpm; - - # Outgoing mail also goes through this endpoint, so increase the maximum - # file upload limit to match the corresponding Postfix limit. - client_max_body_size 128M; - } - # Nextcloud configuration. rewrite ^/cloud$ /cloud/ redirect; rewrite ^/cloud/$ /cloud/index.php; @@ -122,3 +96,5 @@ rewrite ^/.well-known/host-meta.json /cloud/public.php?service=host-meta-json last; rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect; rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect; + + # ADDITIONAL DIRECTIVES HERE diff --git a/conf/www_default.html b/conf/www_default.html index e78b0bbc..68d0366b 100644 --- a/conf/www_default.html +++ b/conf/www_default.html @@ -1,15 +1,10 @@ - - - Redirecting... - - - - - - - - + + this is a mail-in-a-box + + + +

this is a mail-in-a-box

+

take control of your email at https://mailinabox.email/

+ diff --git a/management/backup.py b/management/backup.py index b23d1532..618a51c5 100755 --- a/management/backup.py +++ b/management/backup.py @@ -10,7 +10,7 @@ import os, os.path, shutil, glob, re, datetime, sys import dateutil.parser, dateutil.relativedelta, dateutil.tz import rtyaml -from exclusiveprocess import Lock, CannotAcquireLock +from exclusiveprocess import Lock from utils import load_environment, shell, wait_for_service, fix_boto, get_php_version @@ -210,22 +210,14 @@ def get_target_type(config): protocol = config["target"].split(":")[0] return protocol -def perform_backup(full_backup, user_initiated=False): +def perform_backup(full_backup): env = load_environment() php_fpm = f"php{get_php_version()}-fpm" # Create an global exclusive lock so that the backup script - # cannot be run more than one. - lock = Lock(name="mailinabox_backup_daemon", die=(not user_initiated)) - if user_initiated: - # God forgive me for what I'm about to do - try: - lock._acquire() - except CannotAcquireLock: - return "Another backup is already being done!" - else: - lock.forever() - + # cannot be run more than once. + Lock(die=True).forever() + config = get_backup_config(env) backup_root = os.path.join(env["STORAGE_ROOT"], 'backup') backup_cache_dir = os.path.join(backup_root, 'cache') @@ -329,14 +321,9 @@ def perform_backup(full_backup, user_initiated=False): # backup. Since it checks that dovecot and postfix are running, block for a # bit (maximum of 10 seconds each) to give each a chance to finish restarting # before the status checks might catch them down. See #381. - if user_initiated: - # God forgive me for what I'm about to do - lock._release() - # We don't need to wait for the services to be up in this case - else: - wait_for_service(25, True, env, 10) - wait_for_service(993, True, env, 10) - + wait_for_service(25, True, env, 10) + wait_for_service(993, True, env, 10) + # Execute a post-backup script that does the copying to a remote server. # Run as the STORAGE_USER user, not as root. Pass our settings in # environment variables so the script has access to STORAGE_ROOT. @@ -346,7 +333,6 @@ def perform_backup(full_backup, user_initiated=False): ['su', env['STORAGE_USER'], '-c', post_script, config["target"]], env=env) - def run_duplicity_verification(): env = load_environment() backup_root = os.path.join(env["STORAGE_ROOT"], 'backup') diff --git a/management/daemon.py b/management/daemon.py index d08dfdb3..f727958b 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -280,17 +280,50 @@ def dns_set_secondary_nameserver(): @app.route('/dns/custom') @authorized_personnel_only def dns_get_records(qname=None, rtype=None): - from dns_update import get_custom_dns_config - return json_response([ - { - "qname": r[0], - "rtype": r[1], - "value": r[2], - } - for r in get_custom_dns_config(env) - if r[0] != "_secondary_nameserver" - and (not qname or r[0] == qname) - and (not rtype or r[1] == rtype) ]) + # Get the current set of custom DNS records. + from dns_update import get_custom_dns_config, get_dns_zones + records = get_custom_dns_config(env, only_real_records=True) + + # Filter per the arguments for the more complex GET routes below. + records = [r for r in records + if (not qname or r[0] == qname) + and (not rtype or r[1] == rtype) ] + + # Make a better data structure. + records = [ + { + "qname": r[0], + "rtype": r[1], + "value": r[2], + "sort-order": { }, + } for r in records ] + + # To help with grouping by zone in qname sorting, label each record with which zone it is in. + # There's an inconsistency in how we handle zones in get_dns_zones and in sort_domains, so + # do this first before sorting the domains within the zones. + zones = utils.sort_domains([z[0] for z in get_dns_zones(env)], env) + for r in records: + for z in zones: + if r["qname"] == z or r["qname"].endswith("." + z): + r["zone"] = z + break + + # Add sorting information. The 'created' order follows the order in the YAML file on disk, + # which tracs the order entries were added in the control panel since we append to the end. + # The 'qname' sort order sorts by our standard domain name sort (by zone then by qname), + # then by rtype, and last by the original order in the YAML file (since sorting by value + # may not make sense, unless we parse IP addresses, for example). + for i, r in enumerate(records): + r["sort-order"]["created"] = i + domain_sort_order = utils.sort_domains([r["qname"] for r in records], env) + for i, r in enumerate(sorted(records, key = lambda r : ( + zones.index(r["zone"]), + domain_sort_order.index(r["qname"]), + r["rtype"]))): + r["sort-order"]["qname"] = i + + # Return. + return json_response(records) @app.route('/dns/custom/', methods=['GET', 'POST', 'PUT', 'DELETE']) @app.route('/dns/custom//', methods=['GET', 'POST', 'PUT', 'DELETE']) @@ -594,19 +627,6 @@ def backup_set_custom(): request.form.get('min_age', '') )) -@app.route('/system/backup/new', methods=["POST"]) -@authorized_personnel_only -def backup_new(): - from backup import perform_backup, get_backup_config - - # If backups are disabled, don't perform the backup - config = get_backup_config(env) - if config["target"] == "off": - return "Backups are disabled in this machine. Nothing was done." - - msg = perform_backup(request.form.get('full', False) == 'true', True) - return "OK" if msg is None else msg - @app.route('/system/privacy', methods=["GET"]) @authorized_personnel_only def privacy_status_get(): @@ -621,49 +641,6 @@ def privacy_status_set(): utils.write_settings(config, env) return "OK" -@app.route('/system/smtp/relay', methods=["GET"]) -@authorized_personnel_only -def smtp_relay_get(): - config = utils.load_settings(env) - return { - "enabled": config.get("SMTP_RELAY_ENABLED", True), - "host": config.get("SMTP_RELAY_HOST", ""), - "auth_enabled": config.get("SMTP_RELAY_AUTH", False), - "user": config.get("SMTP_RELAY_USER", "") - } - -@app.route('/system/smtp/relay', methods=["POST"]) -@authorized_personnel_only -def smtp_relay_set(): - from editconf import edit_conf - config = utils.load_settings(env) - newconf = request.form - try: - # Write on daemon settings - config["SMTP_RELAY_ENABLED"] = (newconf.get("enabled") == "true") - config["SMTP_RELAY_HOST"] = newconf.get("host") - config["SMTP_RELAY_AUTH"] = (newconf.get("auth_enabled") == "true") - config["SMTP_RELAY_USER"] = newconf.get("user") - utils.write_settings(config, env) - # Write on Postfix configs - edit_conf("/etc/postfix/main.cf", [ - "relayhost=" + (f"[{config['SMTP_RELAY_HOST']}]:587" if config["SMTP_RELAY_ENABLED"] else ""), - "smtp_sasl_auth_enable=" + ("yes" if config["SMTP_RELAY_AUTH"] else "no"), - "smtp_sasl_security_options=" + ("noanonymous" if config["SMTP_RELAY_AUTH"] else "anonymous"), - "smtp_sasl_tls_security_options=" + ("noanonymous" if config["SMTP_RELAY_AUTH"] else "anonymous") - ], delimiter_re=r"\s*=\s*", delimiter="=", comment_char="#") - if config["SMTP_RELAY_AUTH"]: - # Edit the sasl password - with open("/etc/postfix/sasl_passwd", "w") as f: - f.write(f"[{config['SMTP_RELAY_HOST']}]:587 {config['SMTP_RELAY_USER']}:{newconf.get('key')}\n") - utils.shell("check_output", ["/usr/bin/chmod", "600", "/etc/postfix/sasl_passwd"], capture_stderr=True) - utils.shell("check_output", ["/usr/sbin/postmap", "/etc/postfix/sasl_passwd"], capture_stderr=True) - # Restart Postfix - return utils.shell("check_output", ["/usr/bin/systemctl", "restart", "postfix"], capture_stderr=True) - except Exception as e: - return (str(e), 500) - - # MUNIN @app.route('/munin/') diff --git a/management/daily_tasks.sh b/management/daily_tasks.sh index dee8b602..db496399 100755 --- a/management/daily_tasks.sh +++ b/management/daily_tasks.sh @@ -12,14 +12,14 @@ export LC_TYPE=en_US.UTF-8 # On Mondays, i.e. once a week, send the administrator a report of total emails # sent and received so the admin might notice server abuse. if [ `date "+%u"` -eq 1 ]; then - management/mail_log.py -t week | management/email_administrator.py "Mail-in-a-Box Usage Report" + management/mail_log.py -t week | management/email_administrator.py "Mail-in-a-Box Usage Report" fi # Take a backup. management/backup.py 2>&1 | management/email_administrator.py "Backup Status" # Provision any new certificates for new domains or domains with expiring certificates. -management/ssl_certificates.py -q 2>&1 | management/email_administrator.py "TLS Certificate Provisioning Result" +management/ssl_certificates.py -q 2>&1 | management/email_administrator.py "TLS Certificate Provisioning Result" # Run status checks and email the administrator if anything changed. -management/status_checks.py --show-changes 2>&1 | management/email_administrator.py "Status Checks Change Notice" +management/status_checks.py --show-changes 2>&1 | management/email_administrator.py "Status Checks Change Notice" diff --git a/management/dns_update.py b/management/dns_update.py index 6280414f..569de9b1 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -753,7 +753,7 @@ def write_opendkim_tables(domains, env): ######################################################################## -def get_custom_dns_config(env): +def get_custom_dns_config(env, only_real_records=False): try: custom_dns = rtyaml.load(open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml'))) if not isinstance(custom_dns, dict): raise ValueError() # caught below @@ -761,6 +761,8 @@ def get_custom_dns_config(env): return [ ] for qname, value in custom_dns.items(): + if qname == "_secondary_nameserver" and only_real_records: continue # skip fake record + # Short form. Mapping a domain name to a string is short-hand # for creating A records. if isinstance(value, str): diff --git a/management/mail_log.py b/management/mail_log.py index 9e08df77..1626f820 100755 --- a/management/mail_log.py +++ b/management/mail_log.py @@ -44,9 +44,8 @@ TIME_DELTAS = OrderedDict([ ('today', datetime.datetime.now() - datetime.datetime.now().replace(hour=0, minute=0, second=0)) ]) -# Start date > end date! -START_DATE = datetime.datetime.now() -END_DATE = None +END_DATE = NOW = datetime.datetime.now() +START_DATE = None VERBOSE = False @@ -121,7 +120,7 @@ def scan_mail_log(env): pass print("Scanning logs from {:%Y-%m-%d %H:%M:%S} to {:%Y-%m-%d %H:%M:%S}".format( - END_DATE, START_DATE) + START_DATE, END_DATE) ) # Scan the lines in the log files until the date goes out of range @@ -253,7 +252,7 @@ def scan_mail_log(env): if collector["postgrey"]: msg = "Greylisted Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}" - print_header(msg.format(END_DATE, START_DATE)) + print_header(msg.format(START_DATE, END_DATE)) print(textwrap.fill( "The following mail was greylisted, meaning the emails were temporarily rejected. " @@ -291,7 +290,7 @@ def scan_mail_log(env): if collector["rejected"]: msg = "Blocked Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}" - print_header(msg.format(END_DATE, START_DATE)) + print_header(msg.format(START_DATE, END_DATE)) data = OrderedDict(sorted(collector["rejected"].items(), key=email_sort)) @@ -344,20 +343,20 @@ def scan_mail_log_line(line, collector): # Replaced the dateutil parser for a less clever way of parser that is roughly 4 times faster. # date = dateutil.parser.parse(date) - - # date = datetime.datetime.strptime(date, '%b %d %H:%M:%S') - # date = date.replace(START_DATE.year) - - # strptime fails on Feb 29 if correct year is not provided. See https://bugs.python.org/issue26460 - date = datetime.datetime.strptime(str(START_DATE.year) + ' ' + date, '%Y %b %d %H:%M:%S') - # print("date:", date) + + # strptime fails on Feb 29 with ValueError: day is out of range for month if correct year is not provided. + # See https://bugs.python.org/issue26460 + date = datetime.datetime.strptime(str(NOW.year) + ' ' + date, '%Y %b %d %H:%M:%S') + # if log date in future, step back a year + if date > NOW: + date = date.replace(year = NOW.year - 1) + #print("date:", date) # Check if the found date is within the time span we are scanning - # END_DATE < START_DATE - if date > START_DATE: + if date > END_DATE: # Don't process, and halt return False - elif date < END_DATE: + elif date < START_DATE: # Don't process, but continue return True @@ -606,7 +605,7 @@ def email_sort(email): def valid_date(string): - """ Validate the given date string fetched from the --startdate argument """ + """ Validate the given date string fetched from the --enddate argument """ try: date = dateutil.parser.parse(string) except ValueError: @@ -820,12 +819,14 @@ if __name__ == "__main__": parser.add_argument("-t", "--timespan", choices=TIME_DELTAS.keys(), default='today', metavar='