diff --git a/Dockerfile b/Dockerfile index 4f241ff..6554a21 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,36 +2,6 @@ ARG ALPINE_VERSION=latest FROM alpine:${ALPINE_VERSION} LABEL maintaner="Bojan Cekrlic - https://github.com/bokysan/docker-postfix/" -# See README.md for details - -# Set the timezone for the container, if needed. -ENV TZ= -# Postfix myhostname -ENV HOSTNAME= -# Host that relays your msgs -ENV RELAYHOST= -# An (optional) username for the relay server -ENV RELAYHOST_USERNAME= -# An (optional) login password for the relay server -ENV RELAYHOST_PASSWORD= -# Define relay host TLS connection level. See http://www.postfix.org/postconf.5.html#smtp_tls_security_level for details. -# By default, the permissive level ("may") is used, if not defined. -ENV RELAYHOST_TLS_LEVEL= -# Allow domains from per Network ( default 127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 ) -ENV MYNETWORKS=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 -# Allow any sender domains -ENV ALLOWED_SENDER_DOMAINS= -# Don't allow blank value for ALLOWED_SENDER_DOMAINS -ENV ALLOW_EMPTY_SENDER_DOMAINS= -# Attachments size. 0 means unlimited. Usually needs to be set if your relay host has an attachment size limit -ENV MESSAGE_SIZE_LIMIT= -# Enable additional debugging for connections to postfix -ENV INBOUND_DEBUGGING= -# DKIM domain selector. If not set, the default (mail) will be used -ENV DKIM_SELECTOR= -# Logformat. Defaults to "plain". Can be either "plain" or "json". -ENV LOG_FORMAT= - # Install supervisor, postfix # Install postfix first to get the first account (101) # Install opendkim second to get the second account (102) @@ -39,7 +9,7 @@ RUN true && \ apk add --no-cache --upgrade cyrus-sasl cyrus-sasl-plain cyrus-sasl-login && \ apk add --no-cache postfix && \ apk add --no-cache opendkim && \ - apk add --no-cache --upgrade ca-certificates tzdata supervisor rsyslog musl musl-utils bash && \ + apk add --no-cache --upgrade ca-certificates tzdata supervisor rsyslog musl musl-utils bash opendkim-utils && \ (rm "/tmp/"* 2>/dev/null || true) && (rm -rf /var/cache/apk/* 2>/dev/null || true) # Set up configuration diff --git a/README.md b/README.md index a7a8ea0..02f4b4e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ All standard caveats of configuring the SMTP server apply: - Hosting centers also tend to block port 25, which can be unblocked per requirst (e.g. for AWS either [fill out a form](https://aws.amazon.com/premiumsupport/knowledge-center/ec2-port-25-throttle/) or forward mail to their [SES](https://aws.amazon.com/ses/) service, which is free for low volumes) - You'll most likely need to at least [set up SPF records](https://en.wikipedia.org/wiki/Sender_Policy_Framework) or [DKIM](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail) - If using DKIM (below), make sure to add DKIM keys to your domain's DNS entries -- You'll most likely need to set up (PTR)[https://en.wikipedia.org/wiki/Reverse_DNS_lookup] records to prevent your mails going to spam +- You'll most likely need to set up [PTR](https://en.wikipedia.org/wiki/Reverse_DNS_lookup) records to prevent your mails going to spam If you don't know what any of the above means, get some help. Google is your friend. It's also worth noting that as a consequence it's pretty difficult to host a SMTP server on a dynamic IP address. @@ -38,26 +38,39 @@ exposed on purpose, as it's regularly blocked by ISP or already occupied by othe ## Configuration options -The following configuration options are available: - -```Dockerfile -ENV vars -$TZ = The timezone for the image -$HOSTNAME = Postfix myhostname -$RELAYHOST = Host that relays your msgs -$RELAYHOST_USERNAME = An (optional) username for the relay server -$RELAYHOST_PASSWORD = An (optional) login password for the relay server -$RELAYHOST_TLS_LEVEL = Relay host TLS connection leve -$MYNETWORKS = allow domains from per Network ( default 127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 ) -$ALLOWED_SENDER_DOMAINS = domains sender domains -$ALLOW_EMPTY_SENDER_DOMAINS = if value is set (i.e: "true"), $ALLOWED_SENDER_DOMAINS can be unset -$MESSAGE_SIZE_LIMIT = The size of the messsage, in bytes -$INBOUND_DEBUGGING = Set to 1 to enable detailed debugging in the logs -$MASQUERADED_DOMAINS = domains where you want to masquerade internal hosts -$DKIM_SELECTOR = override DKIM selector (by default "mail") -``` +The following configuration options are available + +### General options + +- `TZ` = The timezone for the image +- `FORCE_COLOR` = Set to `1` to force color output (otherwise auto-detected) +- `INBOUND_DEBUGGING` = Set to `1` to enable detailed debugging in the logs +- `ALLOWED_SENDER_DOMAINS` = domains which are allowed to send email via this server +- `ALLOW_EMPTY_SENDER_DOMAINS` = if value is set (i.e: `true`), `ALLOWED_SENDER_DOMAINS` can be unset +- `LOG_FORMAT` = Set your log format (JSON or plain) + +### Postfix-specific options -### `HOSTNAME` +- `RELAYHOST` = Host that relays your messages +- `RELAYHOST_USERNAME` = An (optional) username for the relay server +- `RELAYHOST_PASSWORD` = An (optional) login password for the relay server +- `RELAYHOST_TLS_LEVEL` = Relay host TLS connection leve +- `POSTFIX_message_size_limit` = The maximum size of the messsage, in bytes, by default it's unlimited +- `POSTFIX_mynetworks` = Allow sending mails only from specific networks ( default `127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` ) +- `POSTFIX_hostname` = Set tha name of this postfix server +- `MASQUERADED_DOMAINS` = domains where you want to masquerade internal hosts +- `POSTFIX_` = provide any additional postfix setting +- `SMTP_HEADER_CHECKS`= Set to `1` to enable header checks of to a location + of the file for header checks + +### DKIM-specific options + +- `DKIM_SELECTOR` = Override the default DKIM selector (by default "mail"). +- `DKIM_AUTOGENERATE` = Set to non-empty value (e.g. `true` or `1`) to have + the server auto-generate domain keys. +- `OPENDKIM_` = Provide any additonal OpenDKIM setting. + +### `POSTFIX_hostname` You may configure a specific hostname that the SMTP server will use to identify itself. If you don't do it, the default Docker host name will be used. A lot of times, this will be just the container id (e.g. `f73792d540a5`) @@ -104,7 +117,7 @@ Define relay host TLS connection level. See [smtp_tls_security_level](http://www This level defines how the postfix will connect to your upstream server. -### `MESSAGE_SIZE_LIMIT` +### `POSTFIX_message_size_limit` Define the maximum size of the message, in bytes. See more in [Postfix documentation](http://www.postfix.org/postconf.5.html#message_size_limit). @@ -113,7 +126,7 @@ By default, this limit is set to 0 (zero), which means unlimited. Why would you with `RELAYHOST` setting. If your relay host has a message limit (and usually it does), set it also here. This will help you "fail fast" -- your message will be rejected at the time of sending instead having it stuck in the outbound queue indefenetly. -### `MYNETWORKS` +### `POSTFIX_mynetworks` This implementation is meant for private installations -- so that when you configure your services using _docker compose_ you can just plug it in. Precisely because of this reason and the prevent any issues with this postfix being inadvertently @@ -174,7 +187,9 @@ Example: docker run --rm --name postfix -e "SMTP_HEADER_CHECKS="regexp:/etc/postfix/smtp_header_checks" -e "ALLOWED_SENDER_DOMAINS=example.com example.org" -p 1587:587 boky/postfix ``` -## `DKIM` +## DKIM / DomainKeys + +### Supplying your own DKIM keys **This image is equiped with support for DKIM.** If you want to use DKIM you will need to generate DKIM keys yourself. You'll need to create a folder for every domain you want to send through Postfix and generate they key(s) with the following command, e.g. @@ -203,7 +218,19 @@ will be used automatically, e.g.: docker run --rm --name postfix -e "ALLOWED_SENDER_DOMAINS=example.com example.org" -v /host/keys:/etc/opendkim/keys -p 1587:587 boky/postfix ``` -**NOTE:** `mail` is the *default DKIM selector* and should be sufficient for most usages. If you wish to override the selector, +### Auto-generating the DKIM selectors with + +If you set the environment variable `DKIM_AUTOGENERATE` to a non-empty value +(e.g. `true` or `1`) the image will automatically generate the keys. + +**Be careful when using this option**. If you don't bind `/etc/opendkim/keys` +to a persistent volume, you will get new keys every single time. You will need +to take the generated public part of the key (the one in the `.txt` file) and +copy it over to your DNS server manually. + +### Changing the DKIM selector + +`mail` is the *default DKIM selector* and should be sufficient for most usages. If you wish to override the selector, set the environment variable `DKIM_SELECTOR`, e.g. `... -e DKIM_SELECTOR=postfix`. Note that the same DKIM selector will be applied to all found domains. To override a selector for a specific domain use the syntax `[=,...]`, e.g.: @@ -217,6 +244,11 @@ This means: - use `blah` for `example.com` domain - use `foo` if no domain matches +### Verifying your setup + +I strongly suggest using a service such as [dkimvalidator](https://dkimvalidator.com/) to make sure your keys are set up properly +and your DNS server is serving them with the correct records. + ## Extending the image ### Using custom init scripts @@ -250,6 +282,8 @@ postconf -e "address_verify_negative_cache=yes" Any Postfix [configuration option](http://www.postfix.org/postconf.5.html) can be overriden using `POSTFIX_` environment variables, e.g. `POSTFIX_allow_mail_to_commands=alias,forward,include`. Specifying no content (empty variable) will remove that variable from postfix config. +### Overriding specific OpenDKIM settings + Any OpenDKIM [configuration option](http://opendkim.org/opendkim.conf.5.html) can be overriden using `OPENDKIM_` environment variables, e.g. `OPENDKIM_RequireSafeKeys=yes`. Specifying no content (empty variable) will remove that variable from OpenDKIM config. @@ -270,7 +304,7 @@ which will use `UID:GID` of `100:101`. `opendkim` will run under account `102:10 There are may other project offering similar functionality. The aim of this project, however, is: - to make it as simple as possible to run the relay, without going too much into postfix configuration details -- to make as small image as possible (hence basing on Alpine linux) +- to make the image as small as possible (hence basing on Alpine linux) - to make the image and the corresponding code testable The other projects are, in completely random order: diff --git a/integration-test.sh b/integration-test.sh index 8a21e5e..a296fa1 100755 --- a/integration-test.sh +++ b/integration-test.sh @@ -1,14 +1,30 @@ -#!/bin/sh +#!/usr/bin/env bash set -e cd integration-tests -for i in `find -maxdepth 1 -type d`; do - i="$(basename "$i")" - if [ "$i" == "tester" ] || [ "$i" == "." ] || [ "$i" == ".." ]; then - continue - fi + +run_test() { + echo + echo + echo "☆☆☆☆☆☆☆☆☆☆ $1 ☆☆☆☆☆☆☆☆☆☆" + echo ( - echo "$i" - cd "$i" + cd "$1" docker-compose up --build --abort-on-container-exit --exit-code-from tests + docker-compose down ) -done \ No newline at end of file +} + +if [[ $# -gt 0 ]]; then + while [[ -n "$1" ]]; do + run_test "$1" + shift + done +else + for i in `find -maxdepth 1 -type d`; do + i="$(basename "$i")" + if [ "$i" == "tester" ] || [ "$i" == "." ] || [ "$i" == ".." ]; then + continue + fi + run_test $i + done +fi \ No newline at end of file diff --git a/integration-tests/basic-test/docker-compose.yml b/integration-tests/basic-test/docker-compose.yml index ef0f92a..ea492cf 100644 --- a/integration-tests/basic-test/docker-compose.yml +++ b/integration-tests/basic-test/docker-compose.yml @@ -16,6 +16,7 @@ services: - "./test-keys:/etc/opendkim/keys" - "./docker-init.db:/docker-init.db/" environment: + FORCE_COLOR: "1" ALLOWED_SENDER_DOMAINS: "example.org" tests: image: "boky/postfix-integration-test" diff --git a/integration-tests/basic-test/docker-init.db/relay_to_blackhole.sh b/integration-tests/basic-test/docker-init.db/relay_to_blackhole.sh index 75bf8d0..6b1b409 100755 --- a/integration-tests/basic-test/docker-init.db/relay_to_blackhole.sh +++ b/integration-tests/basic-test/docker-init.db/relay_to_blackhole.sh @@ -2,5 +2,5 @@ . /common.sh -echo -e "‣ $notice Relaying all mails to blackhole.${reset}" +notice " Relaying all mails to blackhole.${reset}" postconf -e "smtpd_end_of_data_restrictions=check_client_access static:discard" \ No newline at end of file diff --git a/integration-tests/deprecated/docker-compose.yml b/integration-tests/deprecated/docker-compose.yml new file mode 100644 index 0000000..82432fd --- /dev/null +++ b/integration-tests/deprecated/docker-compose.yml @@ -0,0 +1,32 @@ +version: '3.7' +services: + postfix_test_587: + hostname: "postfix" + image: "boky/postfix" + build: + context: ../.. + restart: always + healthcheck: + test: [ "CMD", "sh", "-c", "netstat -an | fgrep 587 | fgrep -q LISTEN" ] + interval: 10s + timeout: 5s + start_period: 10s + retries: 2 + environment: + FORCE_COLOR: "1" + MASQUERADED_DOMAINS: "postfix" + ALLOWED_SENDER_DOMAINS: "example.org" + POSTFIX_smtpd_end_of_data_restrictions: "check_client_access static:discard" + MYNETWORKS: "127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" + HOSTNAME: "postfix" + tests: + image: "boky/postfix-integration-test" + restart: "no" + volumes: + - "../tester:/code" + build: + context: ../tester + command: "/" + environment: + FROM: "demo@example.org" + TO: "test@gmail.com" diff --git a/integration-tests/generate-dkim-keys/docker-compose.yml b/integration-tests/generate-dkim-keys/docker-compose.yml new file mode 100644 index 0000000..16f3496 --- /dev/null +++ b/integration-tests/generate-dkim-keys/docker-compose.yml @@ -0,0 +1,30 @@ +version: '3.7' +services: + postfix_test_587: + hostname: "postfix" + image: "boky/postfix" + build: + context: ../.. + restart: always + healthcheck: + test: [ "CMD", "sh", "-c", "netstat -an | fgrep 587 | fgrep -q LISTEN" ] + interval: 10s + timeout: 5s + start_period: 10s + retries: 2 + environment: + FORCE_COLOR: "1" + ALLOWED_SENDER_DOMAINS: "example.org" + DKIM_AUTOGENERATE: "true" + POSTFIX_smtpd_end_of_data_restrictions: "check_client_access static:discard" + tests: + image: "boky/postfix-integration-test" + restart: "no" + volumes: + - "../tester:/code" + build: + context: ../tester + command: "/" + environment: + FROM: "demo@example.org" + TO: "test@gmail.com" \ No newline at end of file diff --git a/integration-tests/no-dkim-test/docker-compose.yml b/integration-tests/no-dkim-test/docker-compose.yml index adc527f..d8b8bd6 100644 --- a/integration-tests/no-dkim-test/docker-compose.yml +++ b/integration-tests/no-dkim-test/docker-compose.yml @@ -13,6 +13,7 @@ services: start_period: 10s retries: 2 environment: + FORCE_COLOR: "1" MASQUERADED_DOMAINS: "postfix" ALLOWED_SENDER_DOMAINS: "" ALLOW_EMPTY_SENDER_DOMAINS: "true" diff --git a/sample/docker-compose/sample.env b/sample/docker-compose/sample.env index d65ceba..75e0f6c 100644 --- a/sample/docker-compose/sample.env +++ b/sample/docker-compose/sample.env @@ -1,7 +1,7 @@ TZ=Europe/Amsterdam -HOSTNAME=smtp-relay +POSTFIX_myhostname=smtp-relay RELAYHOST=smtp.gmail.com:587 RELAYHOST_USERNAME=you@gmail.com RELAYHOST_PASSWORD=yourgmailapppassword -MESSAGE_SIZE_LIMIT=26214400 +POSTFIX_message_size_limit=26214400 ALLOWED_SENDER_DOMAINS=example.org diff --git a/scripts/common-run.sh b/scripts/common-run.sh index 4873532..a437611 100644 --- a/scripts/common-run.sh +++ b/scripts/common-run.sh @@ -1,23 +1,21 @@ #!/usr/bin/env bash announce_startup() { - echo -e "******************************" - echo -e "**** POSTFIX STARTING UP *****" - echo -e "******************************" + echo -e "${gray}${emphasis}★★★★★ ${reset}${lightblue}POSTFIX STARTING UP${reset}${gray}${emphasis} ★★★★★${reset}" } setup_timezone() { if [ ! -z "$TZ" ]; then TZ_FILE="/usr/share/zoneinfo/$TZ" if [ -f "$TZ_FILE" ]; then - echo -e "‣ $notice Setting container timezone to: ${emphasis}$TZ${reset}" + notice "Setting container timezone to: ${emphasis}$TZ${reset}" ln -snf "$TZ_FILE" /etc/localtime echo "$TZ" > /etc/timezone else - echo -e "‣ $warn Cannot set timezone to: ${emphasis}$TZ${reset} -- this timezone does not exist." + warn "Cannot set timezone to: ${emphasis}$TZ${reset} -- this timezone does not exist." fi else - echo -e "‣ $info Not setting any timezone for the container" + info "Not setting any timezone for the container" fi } @@ -26,7 +24,7 @@ rsyslog_log_format() { if [[ -z "${log_format}" ]]; then log_format="plain" fi - echo -e "‣ $info Using ${emphasis}${log_format}${reset} log format for rsyslog." + info "Using ${emphasis}${log_format}${reset} log format for rsyslog." sed -i -E "s//${log_format}/" /etc/rsyslog.conf } @@ -57,14 +55,16 @@ postfix_increase_header_size_limit() { } postfix_restrict_message_size() { - if [ ! -z "$MESSAGE_SIZE_LIMIT" ]; then - echo -e "‣ $notice Restricting message_size_limit to: ${emphasis}$MESSAGE_SIZE_LIMIT bytes${reset}" - postconf -e "message_size_limit=$MESSAGE_SIZE_LIMIT" + if [[ -n "${MESSAGE_SIZE_LIMIT}" ]]; then + deprecated "${emphasis}MESSAGE_SIZE_LIMIT${reset} variable is deprecated. Please use ${emphasis}POSTFIX_message_size_limit${reset} instead." + POSTFIX_message_size_limit="${MESSAGE_SIZE_LIMIT}" + fi + + if [[ -n "${POSTFIX_message_size_limit}" ]]; then + notice "Restricting message_size_limit to: ${emphasis}${POSTFIX_message_size_limit} bytes${reset}" else - # As this is a server-based service, allow any message size -- we hope the - # sender knows what he is doing. - echo -e "‣ $info Using ${emphasis}unlimited${reset} message size." - postconf -e "message_size_limit=0" + info "Using ${emphasis}unlimited${reset} message size." + POSTFIX_message_size_limit=0 fi } @@ -76,27 +76,25 @@ postfix_reject_invalid_helos() { } postfix_set_hostname() { - if [ ! -z "$HOSTNAME" ]; then - echo -e "‣ $notice Setting myhostname: ${emphasis}$HOSTNAME${reset}" - postconf -e myhostname="$HOSTNAME" - else - postconf -# myhostname + postconf -# myhostname + if [[ -z "$POSTFIX_myhostname" ]]; then + POSTFIX_myhostname="${HOSTNAME}" fi } postfix_set_relay_tls_level() { if [ -z "$RELAYHOST_TLS_LEVEL" ]; then - echo -e "‣ $info Setting smtp_tls_security_level: ${emphasis}may${reset}" + info "Setting smtp_tls_security_level: ${emphasis}may${reset}" postconf -e "smtp_tls_security_level=may" else - echo -e "‣ $notice Setting smtp_tls_security_level: ${emphasis}$RELAYHOST_TLS_LEVEL${reset}" + notice "Setting smtp_tls_security_level: ${emphasis}$RELAYHOST_TLS_LEVEL${reset}" postconf -e "smtp_tls_security_level=$RELAYHOST_TLS_LEVEL" fi } postfix_setup_relayhost() { if [ ! -z "$RELAYHOST" ]; then - echo -en "‣ $notice Forwarding all emails to ${emphasis}$RELAYHOST${reset}" + noticen "Forwarding all emails to ${emphasis}$RELAYHOST${reset}" postconf -e "relayhost=$RELAYHOST" # Alternately, this could be a folder, like this: # smtp_tls_CApath @@ -114,7 +112,7 @@ postfix_setup_relayhost() { echo -e " without any authentication. ${emphasis}Make sure your server is configured to accept emails coming from this IP.${reset}" fi else - echo -e "‣ $notice Will try to deliver emails directly to the final server. ${emphasis}Make sure your DNS is setup properly!${reset}" + notice "Will try to deliver emails directly to the final server. ${emphasis}Make sure your DNS is setup properly!${reset}" postconf -# relayhost postconf -# smtp_sasl_auth_enable postconf -# smtp_sasl_password_maps @@ -124,19 +122,21 @@ postfix_setup_relayhost() { postfix_setup_networks() { if [ ! -z "$MYNETWORKS" ]; then - echo -e "‣ $notice Using custom allowed networks: ${emphasis}$MYNETWORKS${reset}" + deprecated "${emphasis}MYNETWORKS${reset} variable is deprecated. Please use ${emphasis}POSTFIX_mynetworks${reset} instead." + notice "Using custom allowed networks: ${emphasis}$MYNETWORKS${reset}" + POSTFIX_mynetworks="$MYNETWORKS" + elif [ ! -z "$POSTFIX_mynetworks" ]; then + notice "Using custom allowed networks: ${emphasis}$POSTFIX_mynetworks${reset}" else - echo -e "‣ $info Using default private network list for trusted networks." - MYNETWORKS="127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" + info "Using default private network list for trusted networks." + POSTFIX_mynetworks="127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" fi - - postconf -e "mynetworks=$MYNETWORKS" } postfix_setup_debugging() { if [ ! -z "$INBOUND_DEBUGGING" ]; then - echo -e "‣ $notice Enabling additional debbuging for: ${emphasis}$MYNETWORKS${reset}, as INBOUND_DEBUGGING=''${INBOUND_DEBUGGING}''" - postconf -e "debug_peer_list=$MYNETWORKS" + notice "Enabling additional debbuging for: ${emphasis}$POSTFIX_mynetworks${reset}, as INBOUND_DEBUGGING=''${INBOUND_DEBUGGING}''" + postconf -e "debug_peer_list=$POSTFIX_mynetworks" sed -i -E 's/^[ \t]*#?[ \t]*LogWhy[ \t]*.+$/LogWhy yes/' /etc/opendkim/opendkim.conf if ! egrep -q '^LogWhy' /etc/opendkim/opendkim.conf; then @@ -144,7 +144,7 @@ postfix_setup_debugging() { echo "LogWhy yes" >> /etc/opendkim/opendkim.conf fi else - echo -e "‣ $info Debugging is disabled.${reset}" + info "Debugging is disabled.${reset}" sed -i -E 's/^[ \t]*#?[ \t]*LogWhy[ \t]*.+$/LogWhy no/' /etc/opendkim/opendkim.conf if ! egrep -q '^LogWhy' /etc/opendkim/opendkim.conf; then echo >> /etc/opendkim/opendkim.conf @@ -155,7 +155,7 @@ postfix_setup_debugging() { postfix_setup_sender_domains() { if [ ! -z "$ALLOWED_SENDER_DOMAINS" ]; then - echo -en "‣ $info Setting up allowed SENDER domains:" + infon "Setting up allowed SENDER domains:" allowed_senders=/etc/postfix/allowed_senders rm -f $allowed_senders $allowed_senders.db > /dev/null touch $allowed_senders @@ -178,7 +178,7 @@ postfix_setup_sender_domains() { postfix_setup_masquarading() { if [ ! -z "$MASQUERADED_DOMAINS" ]; then - echo -e "‣ $notice Setting up address masquerading: ${emphasis}$MASQUERADED_DOMAINS${reset}" + notice "Setting up address masquerading: ${emphasis}$MASQUERADED_DOMAINS${reset}" postconf -e "masquerade_domains = $MASQUERADED_DOMAINS" postconf -e "local_header_rewrite_clients = static:all" fi @@ -187,7 +187,7 @@ postfix_setup_masquarading() { postfix_setup_header_checks() { if [ ! -z "$SMTP_HEADER_CHECKS" ]; then if [ "$SMTP_HEADER_CHECKS" == "1" ]; then - echo -e "‣ $info Using default file for SMTP header checks" + info "Using default file for SMTP header checks" SMTP_HEADER_CHECKS="regexp:/etc/postfix/smtp_header_checks" fi @@ -195,26 +195,69 @@ postfix_setup_header_checks() { FILE=$(echo "$SMTP_HEADER_CHECKS" | cut -d: -f2-) if [ "$FORMAT" == "$FILE" ]; then - echo -e "‣ $warn No Postfix format defined for file ${emphasis}SMTP_HEADER_CHECKS${reset}. Using default ${emphasis}regexp${reset}. To avoid this message, set format explicitly, e.g. ${emphasis}SMTP_HEADER_CHECKS=regexp:$SMTP_HEADER_CHECKS${reset}." + warn "No Postfix format defined for file ${emphasis}SMTP_HEADER_CHECKS${reset}. Using default ${emphasis}regexp${reset}. To avoid this message, set format explicitly, e.g. ${emphasis}SMTP_HEADER_CHECKS=regexp:$SMTP_HEADER_CHECKS${reset}." FORMAT="regexp" fi if [ -f "$FILE" ]; then - echo -e "‣ $notice Setting up ${emphasis}smtp_header_checks${reset} to ${emphasis}$FORMAT:$FILE${reset}" + notice "Setting up ${emphasis}smtp_header_checks${reset} to ${emphasis}$FORMAT:$FILE${reset}" postconf -e "smtp_header_checks=$FORMAT:$FILE" else - echo -e "‣ $error File ${emphasis}$FILE${reset} cannot be found. Please make sure your SMTP_HEADER_CHECKS variable points to the right file. Startup aborted." + fatal "File ${emphasis}$FILE${reset} cannot be found. Please make sure your SMTP_HEADER_CHECKS variable points to the right file. Startup aborted." exit 2 fi fi } postfix_setup_dkim() { - local DKIM_ENABLED= - local domain_dkim_selector="mail" + local DKIM_ENABLED + local domain_dkim_selector + local private_key + local dkim_socket + local domain + local any_generated + local file + + if [[ -n "${DKIM_AUTOGENERATE}" ]]; then + info "${emphasis}DKIM_AUTOGENERATE${reset} set -- will try to auto-generate keys for ${emphasis}${ALLOWED_SENDER_DOMAINS}${reset}." + mkdir -p /etc/opendkim/keys + if [[ -n "${ALLOWED_SENDER_DOMAINS}" ]]; then + for domain in ${ALLOWED_SENDER_DOMAINS}; do + private_key=/etc/opendkim/keys/${domain}.private + if [[ -f "${private_key}" ]]; then + info "Key for domain ${emphasis}${domain}${reset} already exists in ${emphasis}${private_key}${reset}. Will not overwrite" + else + notice "Auto-generating DKIM key for ${emphasis}${domain}${reset} into ${private_key}." + ( + cd /tmp + domain_dkim_selector="$(get_dkim_selector "${domain}")" + opendkim-genkey -b 2048 -h rsa-sha256 -r -v --subdomains -s ${domain_dkim_selector} -d $domain + # Fixes https://github.com/linode/docs/pull/620 + sed -i 's/h=rsa-sha256/h=sha256/' ${domain_dkim_selector}.txt + mv -v ${domain_dkim_selector}.private /etc/opendkim/keys/${domain}.private + mv -v ${domain_dkim_selector}.txt /etc/opendkim/keys/${domain}.txt + ) | sed 's/^/ /' + any_generated=1 + fi + done + if [[ -n "${any_generated}" ]]; then + notice "New DKIM keys have been generated! Please make sure to update your DNS records! You need to add the following details:" + for file in /etc/opendkim/keys/*.txt; do + echo "====== $file ======" + cat $file + done + echo + fi + else + warn "DKIM auto-generate requested, but ${emphasis}ALLOWED_SENDER_DOMAINS${reset} not set. Nothing to generate!" + fi + else + debug "${emphasis}DKIM_AUTOGENERATE${reset} not set -- you will need to provide your own keys." + fi + if [ -d /etc/opendkim/keys ] && [ ! -z "$(find /etc/opendkim/keys -type f ! -name .)" ]; then DKIM_ENABLED=", ${emphasis}opendkim${reset}" - echo -e "‣ $notice Configuring OpenDKIM." + notice "Configuring OpenDKIM." mkdir -p /var/run/opendkim chown -R opendkim:opendkim /var/run/opendkim dkim_socket=$(cat /etc/opendkim/opendkim.conf | egrep ^Socket | awk '{ print $2 }') @@ -238,20 +281,20 @@ postfix_setup_dkim() { echo "0.0.0.0/0" > /etc/opendkim/TrustedHosts if [ ! -z "$ALLOWED_SENDER_DOMAINS" ]; then - for i in $ALLOWED_SENDER_DOMAINS; do - private_key=/etc/opendkim/keys/$i.private + for domain in $ALLOWED_SENDER_DOMAINS; do + private_key=/etc/opendkim/keys/${domain}.private if [ -f $private_key ]; then - domain_dkim_selector="$(get_dkim_selector "$i")" - echo -e " ...for domain ${emphasis}$i${reset} (selector: ${emphasis}${domain_dkim_selector}${reset})" - echo "${domain_dkim_selector}._domainkey.$i $i:mail:$private_key" >> /etc/opendkim/KeyTable - echo "*@$i ${domain_dkim_selector}._domainkey.$i" >> /etc/opendkim/SigningTable + domain_dkim_selector="$(get_dkim_selector "${domain}")" + echo -e " ...for domain ${emphasis}${domain}${reset} (selector: ${emphasis}${domain_dkim_selector}${reset})" + echo "${domain_dkim_selector}._domainkey.${domain} ${domain}:${domain_dkim_selector}:${private_key}" >> /etc/opendkim/KeyTable + echo "*@$i ${domain_dkim_selector}._domainkey.${domain}" >> /etc/opendkim/SigningTable else - echo " ...$warn skipping for domain ${emphasis}$i${reset}. File $private_key not found!" + error "Skipping DKIM for domain ${emphasis}${domain}${reset}. File ${private_key} not found!" fi done fi else - echo -e "‣ $info No DKIM keys found, will not use DKIM." + info "No DKIM keys found, will not use DKIM." postconf -# smtpd_milters postconf -# non_smtpd_milters fi @@ -272,15 +315,15 @@ opendkim_custom_commands() { padded_key="$(printf %-24s "${key}")" fi if cat /etc/opendkim/opendkim.conf | egrep -q "^[[:space:]]*#?[[:space:]]*${key}"; then - echo -e "‣ $info Updating custom OpenDKIM setting: ${emphasis}${key}=${value}${reset}" + info "Updating custom OpenDKIM setting: ${emphasis}${key}=${value}${reset}" sed -i -E "s/^[ \t]*#?[ \t]*${key}[ \t]*.+$/${padded_key}${value}/" /etc/opendkim/opendkim.conf else - echo -e "‣ $info Adding custom OpenDKIM setting: ${emphasis}${key}=${value}${reset}" + info "Adding custom OpenDKIM setting: ${emphasis}${key}=${value}${reset}" echo "Adding ${padded_key}${value}" echo "${padded_key}${value}" >> /etc/opendkim/opendkim.conf fi else - echo -e "‣ $info Deleting custom OpenDKIM setting: ${emphasis}${key}${reset}" + info "Deleting custom OpenDKIM setting: ${emphasis}${key}${reset}" sed -i -E "/^[ \t]*#?[ \t]*${key}[ \t]*.+$/d" /etc/opendkim/opendkim.conf fi done @@ -294,10 +337,10 @@ postfix_custom_commands() { key="${setting:8}" value="${!setting}" if [ -n "${value}" ]; then - echo -e "‣ $info Applying custom postfix setting: ${emphasis}${key}=${value}${reset}" + info "Applying custom postfix setting: ${emphasis}${key}=${value}${reset}" postconf -e "${key}=${value}" else - echo -e "‣ $info Deleting custom postfix setting: ${emphasis}${key}${reset}" + info "Deleting custom postfix setting: ${emphasis}${key}${reset}" postconf -# "${key}" fi done @@ -310,7 +353,7 @@ postfix_open_submission_port() { execute_post_init_scripts() { if [ -d /docker-init.db/ ]; then - echo -e "‣ $notice Executing any found custom scripts..." + notice "Executing any found custom scripts..." for f in /docker-init.db/*; do case "$f" in *.sh) chmod +x "$f"; echo -e "\trunning ${emphasis}$f${reset}"; . "$f" ;; diff --git a/scripts/common.sh b/scripts/common.sh index 0f65fe1..33d460a 100644 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -1,50 +1,111 @@ #!/usr/bin/env bash -reset="" -yellow="" -yellow_bold="" -red="" -orange="" - -# Returns 0 if the specified string contains the specified substring, otherwise returns 1. -# This exercise it required because we are using the sh-compatible interpretation instead -# of bash. +declare reset green yellow orange orange_emphasis lightblue red gray emphasis underline + +################################################################################## +# Check if one string is contained in another. +# Parameters: +# $1 string to check +# $2 the substring +# +# Exists: +# 0 (success) if $2 is in $1 +# 1 (fail) if $2 is NOT in $1 +# +# Example: +# contains "foobar" "bar" -> 0 (true) +# coinains "foobar" "e" -> 1 (false) +# +################################################################################## contains() { string="$1" substring="$2" - if test "${string#*$substring}" != "$string" - then - return 0 # $substring is in $string - else - return 1 # $substring is not in $string + if test "${string#*$substring}" != "$string"; then return 0; else return 1; fi +} + +################################################################################## +# Check if we're running on a color term or not and setup color codes appropriately +################################################################################## +is_color_term() { + if test -t 1 || [ -n "$FORCE_COLOR" ]; then + # Quick and dirty test for color support + if [ "$FORCE_COLOR" == "256" ] || contains "$TERM" "256" || contains "$COLORTERM" "256" || contains "$COLORTERM" "color" || contains "$COLORTERM" "24bit"; then + reset="$(printf '\033[0m')" + green="$(printf '\033[38;5;46m')" + yellow="$(printf '\033[38;5;178m')" + orange="$(printf '\033[38;5;208m')" + orange_emphasis="$(printf '\033[38;5;220m')" + lightblue="$(printf '\033[38;5;147m')" + red="$(printf '\033[91m')" + gray="$(printf '\033[38;5;245m')" + emphasis="$(printf '\033[38;5;111m')" + underline="$(printf '\033[4m')" + elif [ -n "$FORCE_COLOR" ] || contains "$TERM" "xterm"; then + reset="$(printf '\033[0m')" + green="$(printf '\033[32m')" + yellow="$(printf '\033[33m')" + orange="$(printf '\033[31m')" + orange_emphasis="$(printf '\033[31m\033[1m')" + lightblue="$(printf '\033[36;1m')" + red="$(printf '\033[31;1m')" + gray="$(printf '\033[30;1m')" + emphasis="$(printf '\033[1m')" + underline="$(printf '\033[4m')" + fi fi } +is_color_term -if test -t 1; then - # Quick and dirty test for color support - if contains "$TERM" "256" || contains "$COLORTERM" "256" || contains "$COLORTERM" "color" || contains "$COLORTERM" "24bit"; then - reset="\033[0m" - green="\033[38;5;46m" - yellow="\033[38;5;178m" - red="\033[91m" - orange="\033[38;5;208m" - - emphasis="\033[38;5;226m" - elif contains "$TERM" "xterm"; then - reset="\033[0m" - green="\033[32m" - yellow="\033[33m" - red="\033[31;1m" - orange="\033[31m" - - emphasis="\033[33;1m" - fi -fi -info="${green}INFO:${reset}" -notice="${yellow}NOTE:${reset}" -warn="${orange}WARN:${reset}" -error="${red}ERROR:${reset}" +deprecated() { + printf "${reset}‣ ${lightblue}DEPRECATED!${reset} " + echo -e "$@${reset}" +} + +debug() { + printf "${reset}‣ ${gray}DEBUG${reset} " + echo -e "$@${reset}" +} + +info() { + printf "${reset}‣ ${green}INFO ${reset} " + echo -e "$@${reset}" +} + +infon() { + printf "${reset}‣ ${green}INFO ${reset} " + echo -en "$@${reset}" +} + +notice() { + printf "${reset}‣ ${yellow}NOTE ${reset} " + echo -e "$@${reset}" +} + +noticen() { + printf "${reset}‣ ${yellow}NOTE ${reset} " + echo -en "$@${reset}" +} + +warn() { + printf "${reset}‣ ${orange}WARN ${reset} " + echo -e "$@${reset}" +} + +error() { + printf "${reset}‣ ${red}ERROR${reset} " >&2 + echo -e "$@${reset}" >&2 +} + +fatal_no_exit() { + printf "${reset}‣ ${red}FATAL${reset} " >&2 + echo -e "$@${reset}" >&2 +} + +fatal() { + fatal_no_exit $@ + exit 1 +} # Return a DKIM selector from DKIM_SELECTOR environment variable. # See README.md for details. @@ -74,4 +135,6 @@ get_dkim_selector() { IFS="${old}" echo "${no_domain_selector}" -} \ No newline at end of file +} + +export reset green yellow orange orange_emphasis lightblue red gray emphasis underline diff --git a/scripts/run.sh b/scripts/run.sh index d00c135..a86780f 100644 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -29,5 +29,5 @@ opendkim_custom_commands # Apply custom OpenDKIM settings postfix_open_submission_port # Enable the submission port execute_post_init_scripts # Execute any scripts found in /docker-init.db/ -echo -e "‣ $notice Starting: ${emphasis}rsyslog${reset}, ${emphasis}postfix${reset}$DKIM_ENABLED" +notice " Starting: ${emphasis}rsyslog${reset}, ${emphasis}postfix${reset}$DKIM_ENABLED" exec supervisord -c /etc/supervisord.conf