Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DMA for sending email (revisit #217) #288

Open
wants to merge 5 commits into
base: v4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile.apache
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.cli
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.fpm
Original file line number Diff line number Diff line change
@@ -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
Expand Down
16 changes: 16 additions & 0 deletions Dockerfile.slim.apache
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
16 changes: 16 additions & 0 deletions Dockerfile.slim.cli
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
16 changes: 16 additions & 0 deletions Dockerfile.slim.fpm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `[email protected]` 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
[email protected]
# 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.
Expand Down
18 changes: 18 additions & 0 deletions build-and-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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('[email protected]', '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 [email protected] -e DMA_AUTH_PASSWORD=pjxG3kc3VR31jQUvHz "thecodingmachine/php:${PHP_VERSION}-${BRANCH}-${BRANCH_VARIANT}" php -r "mail('[email protected]', '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 "[email protected]" --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
#################################
Expand Down
1 change: 1 addition & 0 deletions utils/Dockerfile.blueprint
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
16 changes: 16 additions & 0 deletions utils/Dockerfile.slim.blueprint
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
31 changes: 31 additions & 0 deletions utils/README.blueprint.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `[email protected]` 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
[email protected]
# 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.
Expand Down
31 changes: 31 additions & 0 deletions utils/docker-entrypoint-as-root.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
[email protected]
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

Expand Down
26 changes: 26 additions & 0 deletions utils/generate_dma.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* A very simple script in charge of generating the DMA configuration based on environment variables.
* The script is run on each start of the container.
*/

require __DIR__.'/utils.php';

$found = false;

foreach ($_SERVER as $key => $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);
}