diff --git a/Dockerfile.apache b/Dockerfile.apache index f577d20e..930b749d 100644 --- a/Dockerfile.apache +++ b/Dockerfile.apache @@ -1,6 +1,7 @@ # DO NOT EDIT THIS FILE : Make yours changes in /utils/Dockerfile.*.blueprint) ARG INSTALL_CRON=1 ARG INSTALL_COMPOSER=1 +ARG INSTALL_DMA=1 ARG PHP_VERSION ARG GLOBAL_VERSION FROM thecodingmachine/php:${PHP_VERSION}-${GLOBAL_VERSION}-slim-apache diff --git a/Dockerfile.cli b/Dockerfile.cli index 4d4443a1..d118a13f 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -1,6 +1,7 @@ # DO NOT EDIT THIS FILE : Make yours changes in /utils/Dockerfile.*.blueprint) ARG INSTALL_CRON=1 ARG INSTALL_COMPOSER=1 +ARG INSTALL_DMA=1 ARG PHP_VERSION ARG GLOBAL_VERSION FROM thecodingmachine/php:${PHP_VERSION}-${GLOBAL_VERSION}-slim-cli diff --git a/Dockerfile.fpm b/Dockerfile.fpm index 7920c129..0b95a588 100644 --- a/Dockerfile.fpm +++ b/Dockerfile.fpm @@ -1,6 +1,7 @@ # DO NOT EDIT THIS FILE : Make yours changes in /utils/Dockerfile.*.blueprint) ARG INSTALL_CRON=1 ARG INSTALL_COMPOSER=1 +ARG INSTALL_DMA=1 ARG PHP_VERSION ARG GLOBAL_VERSION FROM thecodingmachine/php:${PHP_VERSION}-${GLOBAL_VERSION}-slim-fpm diff --git a/Dockerfile.slim.apache b/Dockerfile.slim.apache index 40fa6967..def1918d 100644 --- a/Dockerfile.slim.apache +++ b/Dockerfile.slim.apache @@ -291,6 +291,7 @@ ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini COPY utils/generate_cron.php /usr/local/bin/generate_cron.php +COPY utils/generate_dma.php /usr/local/bin/generate_dma.php COPY utils/startup_commands.php /usr/local/bin/startup_commands.php COPY utils/enable_apache_mods.php /usr/local/bin/enable_apache_mods.php @@ -385,3 +386,18 @@ ONBUILD RUN if [ -n "$NODE_VERSION" ]; then \ sudo apt-get clean && \ sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*; \ fi; + +# |-------------------------------------------------------------------------- +# | DragonFly Mail Agent +# |-------------------------------------------------------------------------- +# | +# | Installs DragonFly Mail Agent, a sendmail-compatible MTA that can be used +# | to send email from PHP, either using direct delivery or through an SMTP +# | smarthost. +# | + +ONBUILD ARG INSTALL_DMA +ONBUILD RUN if [ -n "$INSTALL_DMA" ]; then \ + sudo apt-get update && \ + sudo -E apt-get install -y --no-install-recommends dma busybox-syslogd; \ + fi; diff --git a/Dockerfile.slim.cli b/Dockerfile.slim.cli index 58d26a67..aa132b03 100644 --- a/Dockerfile.slim.cli +++ b/Dockerfile.slim.cli @@ -217,6 +217,7 @@ ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini COPY utils/generate_cron.php /usr/local/bin/generate_cron.php +COPY utils/generate_dma.php /usr/local/bin/generate_dma.php COPY utils/startup_commands.php /usr/local/bin/startup_commands.php COPY utils/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh @@ -290,3 +291,18 @@ ONBUILD RUN if [ -n "$NODE_VERSION" ]; then \ sudo apt-get clean && \ sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*; \ fi; + +# |-------------------------------------------------------------------------- +# | DragonFly Mail Agent +# |-------------------------------------------------------------------------- +# | +# | Installs DragonFly Mail Agent, a sendmail-compatible MTA that can be used +# | to send email from PHP, either using direct delivery or through an SMTP +# | smarthost. +# | + +ONBUILD ARG INSTALL_DMA +ONBUILD RUN if [ -n "$INSTALL_DMA" ]; then \ + sudo apt-get update && \ + sudo -E apt-get install -y --no-install-recommends dma busybox-syslogd; \ + fi; diff --git a/Dockerfile.slim.fpm b/Dockerfile.slim.fpm index dc6a70c0..628cb445 100644 --- a/Dockerfile.slim.fpm +++ b/Dockerfile.slim.fpm @@ -230,6 +230,7 @@ ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini COPY utils/generate_cron.php /usr/local/bin/generate_cron.php +COPY utils/generate_dma.php /usr/local/bin/generate_dma.php COPY utils/startup_commands.php /usr/local/bin/startup_commands.php COPY utils/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh @@ -313,3 +314,18 @@ ONBUILD RUN if [ -n "$NODE_VERSION" ]; then \ sudo apt-get clean && \ sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*; \ fi; + +# |-------------------------------------------------------------------------- +# | DragonFly Mail Agent +# |-------------------------------------------------------------------------- +# | +# | Installs DragonFly Mail Agent, a sendmail-compatible MTA that can be used +# | to send email from PHP, either using direct delivery or through an SMTP +# | smarthost. +# | + +ONBUILD ARG INSTALL_DMA +ONBUILD RUN if [ -n "$INSTALL_DMA" ]; then \ + sudo apt-get update && \ + sudo -E apt-get install -y --no-install-recommends dma busybox-syslogd; \ + fi; diff --git a/README.md b/README.md index 84a1962e..b4c06e2a 100644 --- a/README.md +++ b/README.md @@ -449,6 +449,37 @@ ENV APACHE_RUN_USER=www-data \ ``` +## Sending email + +The `sendmail` binary is available through [DragonFly Mail Agent (DMA)](https://wiki.mageia.org/en/Dma_Dragonfly_Mail_Agent) to make the [`mail()`](https://www.php.net/manual/de/function.mail.php) function work. Without configuration, it sends email directly, using `noreply@example.org` as the sender by default - this won't work for various reasons (greylisting, spam blacklists, SPF record of example.org, ...), and email is quite complex to set up correctly with direct delivery. + +**Important**: To reliably send email, you thus should specify a smarthost which will be used as an SMTP relay - all you need is an account at any email provider that supports SMTP (ideally suited for mass/transactional mails, according to your usecase - e.g. Mailgun, Postmark, or a self-hosted [Postal](https://github.com/postalhq/postal) instance). Then, to configure DMA, you'll just have to set the following environment variables: + +```bash +# your sender address +DMA_FROM=noreply@your-domain.example.org +# your smarthost settings +DMA_CONF_SMARTHOST=smtp.example.org +DMA_AUTH_USERNAME=noreply +DMA_AUTH_PASSWORD=helloworld123 +# further DMA settings, according to the DMA man page, prefixed with DMA_CONF_ +# (see https://dspinellis.github.io/manview/?src=https://raw.githubusercontent.com/corecode/dma/v0.12/dma.8) +# important for boolean settings: a specified but empty variable corresponds +# to true, an unspecified one to false! +DMA_CONF_SECURETRANSFER= +DMA_CONF_STARTTLS= +``` + +DMA is **installed by default in the fat images**. If you are using the "*slim*" images, you need to install it by passing +a single argument before the "FROM" clause in your Dockerfile: + +```Dockerfile +ARG INSTALL_DMA=1 +FROM thecodingmachine/php:8.0-v3-slim-apache +# The build triggers automatically the installation of DragonFly Mail Agent +``` + + ## Setting up CRON jobs You can set up CRON jobs using environment variables too. diff --git a/build-and-test.sh b/build-and-test.sh index 5f5938f5..c06245a0 100755 --- a/build-and-test.sh +++ b/build-and-test.sh @@ -213,6 +213,24 @@ docker build -t test/composer_with_gd --build-arg PHP_VERSION="${PHP_VERSION}" - docker run --rm test/composer_with_gd sudo composer update docker rmi test/composer_with_gd +# Let's check that we can send mail directly +TESTMAIL_ID_DIRECT=$RANDOM +docker run --rm -e DMA_BLOCKING=1 "thecodingmachine/php:${PHP_VERSION}-${BRANCH}-${BRANCH_VARIANT}" php -r "mail('lemuel.ratke46@ethereal.email', 'Hello World ${TESTMAIL_ID_DIRECT}', 'This is an automatic test email, using a direct connection.');" + +# Let's check that we can send mail through a smarthost with credentials +# Sending to an MCAST-TEST-NET IP address as that must not appear on the public internet according to RFC 5771, and will thus be unreachable for direct sending. +TESTMAIL_ID_SMARTHOST=$RANDOM +docker run --rm -e DMA_BLOCKING=1 -e DMA_CONF_SMARTHOST=smtp.ethereal.email -e DMA_CONF_PORT=587 -e DMA_CONF_SECURETRANSFER= -e DMA_CONF_STARTTLS= -e DMA_AUTH_USERNAME=lemuel.ratke46@ethereal.email -e DMA_AUTH_PASSWORD=pjxG3kc3VR31jQUvHz "thecodingmachine/php:${PHP_VERSION}-${BRANCH}-${BRANCH_VARIANT}" php -r "mail('lemuel.ratke46@233.252.0.0', 'Hello World ${TESTMAIL_ID_SMARTHOST}', 'This is an automatic test email, using a smarthost.');" + +# Let's check that the mails came through +ETHEREAL_COOKIEJAR=$(mktemp --tmpdir ethereal-cookies.XXXXXXXXXX) +ETHEREAL_CSRF=$(curl https://ethereal.email/login -s --fail -c "${ETHEREAL_COOKIEJAR}" | grep -Pom1 '\bname="_csrf" value="\K[^"]+') +curl https://ethereal.email/login -s --fail -X POST --data-urlencode "address=lemuel.ratke46@ethereal.email" --data-urlencode "password=pjxG3kc3VR31jQUvHz" --data-urlencode "_csrf=${ETHEREAL_CSRF}" -b "${ETHEREAL_COOKIEJAR}" -c "${ETHEREAL_COOKIEJAR}" >/dev/null +ETHEREAL_INBOX=$(curl https://ethereal.email/messages -s --fail -b "${ETHEREAL_COOKIEJAR}") +rm "${ETHEREAL_COOKIEJAR}" +echo "${ETHEREAL_INBOX}" | grep -F ">Hello World ${TESTMAIL_ID_DIRECT}<" +echo "${ETHEREAL_INBOX}" | grep -F ">Hello World ${TESTMAIL_ID_SMARTHOST}<" + ################################# # Let's build the "node" images ################################# diff --git a/utils/Dockerfile.blueprint b/utils/Dockerfile.blueprint index 81cbcb08..50a25822 100644 --- a/utils/Dockerfile.blueprint +++ b/utils/Dockerfile.blueprint @@ -2,6 +2,7 @@ ARG INSTALL_CRON=1 ARG INSTALL_COMPOSER=1 +ARG INSTALL_DMA=1 ARG PHP_VERSION ARG GLOBAL_VERSION FROM thecodingmachine/php:${PHP_VERSION}-${GLOBAL_VERSION}-slim-{{ $variant }} diff --git a/utils/Dockerfile.slim.blueprint b/utils/Dockerfile.slim.blueprint index a0c41581..4d0ebf75 100644 --- a/utils/Dockerfile.slim.blueprint +++ b/utils/Dockerfile.slim.blueprint @@ -308,6 +308,7 @@ ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini COPY utils/generate_cron.php /usr/local/bin/generate_cron.php +COPY utils/generate_dma.php /usr/local/bin/generate_dma.php COPY utils/startup_commands.php /usr/local/bin/startup_commands.php {{if eq $variant "apache" }} COPY utils/enable_apache_mods.php /usr/local/bin/enable_apache_mods.php @@ -416,3 +417,18 @@ ONBUILD RUN if [ -n "$NODE_VERSION" ]; then \ sudo apt-get clean && \ sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*; \ fi; + +# |-------------------------------------------------------------------------- +# | DragonFly Mail Agent +# |-------------------------------------------------------------------------- +# | +# | Installs DragonFly Mail Agent, a sendmail-compatible MTA that can be used +# | to send email from PHP, either using direct delivery or through an SMTP +# | smarthost. +# | + +ONBUILD ARG INSTALL_DMA +ONBUILD RUN if [ -n "$INSTALL_DMA" ]; then \ + sudo apt-get update && \ + sudo -E apt-get install -y --no-install-recommends dma busybox-syslogd; \ + fi; diff --git a/utils/README.blueprint.md b/utils/README.blueprint.md index 7bb2b790..7437f57e 100644 --- a/utils/README.blueprint.md +++ b/utils/README.blueprint.md @@ -367,6 +367,37 @@ ENV APACHE_RUN_USER=www-data \ ``` +## Sending email + +The `sendmail` binary is available through [DragonFly Mail Agent (DMA)](https://wiki.mageia.org/en/Dma_Dragonfly_Mail_Agent) to make the [`mail()`](https://www.php.net/manual/de/function.mail.php) function work. Without configuration, it sends email directly, using `noreply@example.org` as the sender by default - this won't work for various reasons (greylisting, spam blacklists, SPF record of example.org, ...), and email is quite complex to set up correctly with direct delivery. + +**Important**: To reliably send email, you thus should specify a smarthost which will be used as an SMTP relay - all you need is an account at any email provider that supports SMTP (ideally suited for mass/transactional mails, according to your usecase - e.g. Mailgun, Postmark, or a self-hosted [Postal](https://github.com/postalhq/postal) instance). Then, to configure DMA, you'll just have to set the following environment variables: + +```bash +# your sender address +DMA_FROM=noreply@your-domain.example.org +# your smarthost settings +DMA_CONF_SMARTHOST=smtp.example.org +DMA_AUTH_USERNAME=noreply +DMA_AUTH_PASSWORD=helloworld123 +# further DMA settings, according to the DMA man page, prefixed with DMA_CONF_ +# (see https://dspinellis.github.io/manview/?src=https://raw.githubusercontent.com/corecode/dma/v0.12/dma.8) +# important for boolean settings: a specified but empty variable corresponds +# to true, an unspecified one to false! +DMA_CONF_SECURETRANSFER= +DMA_CONF_STARTTLS= +``` + +DMA is **installed by default in the fat images**. If you are using the "*slim*" images, you need to install it by passing +a single argument before the "FROM" clause in your Dockerfile: + +```Dockerfile +ARG INSTALL_DMA=1 +FROM thecodingmachine/php:{{ $image.php_version }}-v3-slim-apache +# The build triggers automatically the installation of DragonFly Mail Agent +``` + + ## Setting up CRON jobs You can set up CRON jobs using environment variables too. diff --git a/utils/docker-entrypoint-as-root.sh b/utils/docker-entrypoint-as-root.sh index 960e9eb8..d3738056 100755 --- a/utils/docker-entrypoint-as-root.sh +++ b/utils/docker-entrypoint-as-root.sh @@ -116,6 +116,37 @@ fi unset DOCKER_FOR_MAC_REMOTE_HOST unset REMOTE_HOST_FOUND +if [ -e /usr/sbin/dma ]; then + # set sendmail path for PHP + if [ "$DMA_FROM" = "" ]; then + DMA_FROM=noreply@example.org + fi + export PHP_INI_SENDMAIL_PATH="/usr/sbin/sendmail -t -i -f'$DMA_FROM'" + if [[ "$DMA_BLOCKING" == "1" ]]; then + # run in foreground & block until the email really has been sent + # only documented here as it should not normally be used in production; it's mostly used for testing + export PHP_INI_SENDMAIL_PATH="${PHP_INI_SENDMAIL_PATH} -D" + fi + + # generate DMA config based on DMA_CONF_... environment variables + php /usr/local/bin/generate_dma.php > /etc/dma/dma.conf + + # generate DMA authentication file based on DMA_AUTH_... environment variables + if [ -n "$DMA_AUTH_USERNAME" ] && [ -n "$DMA_AUTH_PASSWORD" ]; then + if [ -z "$DMA_CONF_SMARTHOST" ]; then + echo "DMA_AUTH_USERNAME and DMA_AUTH_PASSWORD are set, but DMA_CONF_SMARTHOST is empty - not attempting authentication" >&2 + else + echo "$DMA_AUTH_USERNAME|$DMA_CONF_SMARTHOST:$DMA_AUTH_PASSWORD" > /etc/dma/auth.conf + echo "AUTHPATH /etc/dma/auth.conf" >> /etc/dma/dma.conf + fi + fi + + # start BusyBox syslogd to log DMA errors to STDERR + # unfortunately DMA doesn't support any other way of logging + # tini will luckily make sure that syslogd will be killed together with any other processes + syslogd -n -O - -l 6 | grep --color=never -E '\bmail\.\S+\s+dma\b' >&2 & +fi + sudo chown docker:docker /opt/php_env_var_cache.php /usr/bin/real_php /usr/local/bin/check_php_env_var_changes.php &> /dev/null diff --git a/utils/generate_dma.php b/utils/generate_dma.php new file mode 100644 index 00000000..cc85c295 --- /dev/null +++ b/utils/generate_dma.php @@ -0,0 +1,26 @@ + $value) { + if (strpos($key, 'DMA_') === 0) { + $found = true; + } + if (strpos($key, 'DMA_CONF_') === 0) { + $suffix = substr($key, 9); + + echo $suffix." ".$value."\n"; + } +} + +if (($found === true) && !file_exists('/usr/sbin/dma')) { + // Let's check DMA is installed (it could be not installed is we are using the slim version...) + error_log('DMA is not available in this image. If you are using the thecodingmachine/php "slim" variant, do not forget to add "ARG INSTALL_DMA=1" in your Dockerfile. Check the documentation for more details.'); + exit(1); +}