使用主机的 Postfix 从 Docker 容器发送邮件

Dav*_*son 25 postfix sendmail docker

我正在运行 Ubuntu 14.04 (Linux) 服务器。我已经在服务器上很好地安装和配置了PostfixOpenDKIM;我可以发送电子邮件给自己用,如命令echo hi | sendmail root,和后缀/ opendkim将添加页眉,如Message-IdDateDKIM-Signature,将电子邮件转发到我的个人电子邮件地址,一切都很正常。

现在我想创建一个在Docker容器中运行的应用程序,并且可以同样轻松地发送电子邮件。特别是,我不想担心添加像 的标头Message-Id,也不想在容器本身内部进行太多配置或软件安装。

做这个的最好方式是什么?

有没有办法让容器sendmail在主机上运行可执行文件?

我尝试使用端口 25 上的 SMTP 协议从容器连接到 Postfix,但 Postfix 似乎以不同的方式处理以这种方式接收的消息;我认为它没有添加任何标题,因此该邮件被 gmail 完全拒绝为垃圾邮件(它甚至不足以放入我的垃圾邮件文件夹中)。

这里的邮件日志内容

Sep 28 23:35:52 dantooine postfix/smtpd[4306]: connect from unknown[172.17.0.95]
Sep 28 23:35:52 dantooine postfix/smtpd[4306]: DD457889B: client=unknown[172.17.0.95]
Sep 28 23:35:52 dantooine postfix/cleanup[4309]: DD457889B: message-id=<>
Sep 28 23:35:52 dantooine spamd[3175]: spamd: connection from localhost [::1]:59471 to port 783, fd 6
Sep 28 23:35:52 dantooine spamd[3175]: spamd: handle_user (getpwnam) unable to find user: 'someone'
Sep 28 23:35:52 dantooine spamd[3175]: spamd: still running as root: user not specified with -u, not found, or set to root, falling back to nobody
Sep 28 23:35:52 dantooine spamd[3175]: spamd: processing message (unknown) for someone:65534
Sep 28 23:35:52 dantooine spamd[3175]: spamd: clean message (2.5/5.0) for someone:65534 in 0.0 seconds, 331 bytes.
Sep 28 23:35:52 dantooine spamd[3175]: spamd: result: . 2 - MISSING_DATE,MISSING_FROM,MISSING_MID,UNPARSEABLE_RELAY scantime=0.0,size=331,user=someone,uid=65534,required_score=5.0,rhost=localhost,raddr=::1,rport=59471,mid=(unknown),autolearn=no autolearn_force=no
Sep 28 23:35:52 dantooine opendkim[3179]: DD457889B: can't determine message sender; accepting
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: DD457889B: from=<whoever@example.com>, size=275, nrcpt=1 (queue active)
Sep 28 23:35:53 dantooine postfix/smtpd[4306]: disconnect from unknown[172.17.0.95]
Sep 28 23:35:53 dantooine postfix/smtp[4311]: DD457889B: to=<someone@gmail.com>, relay=gmail-smtp-in.l.google.com[2607:f8b0:4003:c05::1b]:25, delay=0.25, delays=0.12/0.01/0.03/0.09, dsn=5.7.1, status=bounced (host gmail-smtp-in.l.google.com[2607:f8b0:4003:c05::1b] said: 550-5.7.1 [fd17:8b70:893a:44bf:fe73:6c21] Our system has detected that 550-5.7.1 this message is likely unsolicited mail. To reduce the amount of spam 550-5.7.1 sent to Gmail, this message has been blocked. Please visit 550-5.7.1 http://support.google.com/mail/bin/answer.py?hl=en&answer=188131 for 550 5.7.1 more information. su20si7357528oeb.94 - gsmtp (in reply to end of DATA command))
Sep 28 23:35:53 dantooine postfix/cleanup[4309]: 254E688A0: message-id=<20140928233553.254E688A0@myserver.example.com>
Sep 28 23:35:53 dantooine postfix/bounce[4330]: DD457889B: sender non-delivery notification: 254E688A0
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: 254E688A0: from=<>, size=3374, nrcpt=1 (queue active)
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: DD457889B: removed
Sep 28 23:35:53 dantooine postfix/virtual[4331]: 254E688A0: to=<whoever@example.com>, relay=virtual, delay=0.01, delays=0/0/0/0, dsn=2.0.0, status=sent (delivered to maildir)
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: 254E688A0: removed
Run Code Online (Sandbox Code Playgroud)

mas*_*oeh 9

因为您有一个可行的解决方案,所以在这里我将尝试解释当您 telnet 到 postfix (SMTP) 和使用 sendmail(非 SMTP)时的不同行为。

仅供参考,OpenDKIM 将通过带有Milter 机制的 postfix 调用。你可以通过这个官方文档获得一些关于如何在 postfix 中实现 milter 的信息。这是 postfix 中milter hook 的示意图。

             SMTP-only       non-SMTP
             filters         filters
                ^ |            ^ |
                | v            | |
Network ->  smtpd(8)           | |
                       \       | V
Network ->  qmqpd(8)    ->  cleanup(8)  ->  incoming
                       /
            pickup(8)
               :
Local   ->  sendmail(1)
Run Code Online (Sandbox Code Playgroud)

可以看到sendmail-way(非SMTP)和telnet-way(SMTP)处理顺序不同。

  • 非 SMTP 电子邮件在注入 milter 之前将通过清理进行处理。清理守护进程负责添加丢失的标头:(Resent-) From:、To:、Message-Id:Date:。因此,当注入 OpenDKIM milter 时,您的电子邮件将具有完整的标头,即使原始电子邮件的标头不完整。

  • 在进行任何清理处理之前,SMTP 电子邮件将注入 OpenDKIM milter。因此,如果您的原始电子邮件的标题不完整,那么 opendkim 可能会拒绝对电子邮件进行签名。在发件人:头是强制性的(参见RFC 6376),如果一封电子邮件,没有它,OpenDKIM会拒绝签署的电子邮件,给你一个警告

    can't determine message sender; accepting
    
    Run Code Online (Sandbox Code Playgroud)

因为我从不使用 docker,所以我不知道容器内的 sendmail/pickup 有什么限制。我认为 David Grayson 的解决方法足够安全,可以确保 OpenDKIM 对消息进行签名。


小智 9

您必须在位于 set 的 postfix 配置中指向inet_interfacesdocker bridge ( docker0)/etc/postfix/main.cf

inet_interfaces = <docker0_ip>
Run Code Online (Sandbox Code Playgroud)

send-email-from-docker-through-postfix-installed-on-the-host 中有更多内部工作细节

  • 感谢您的链接!对我来说相关的部分是将诸如“172.17.0.0/16”之类的内容添加到“/etc/postfix/main.cf”中的“mynetworks”和“service postfix restart”。 (3认同)

Dav*_*son 5

我决定容器发送邮件的方式是将其写入特定目录中的文件,容器和主机都可以将其作为 Docker“卷”访问。

我制作了一个名为 mailsender.sh 的 shell 脚本,它从指定目录读取邮件,将它们发送到 sendmail,然后删除它们:

#!/bin/bash
# Runs on the host system, reading mails files from a directory
# and piping them to sendmail -t and then deleting them.

DIR=$1

if [ \! \( -d "$DIR" -a -w "$DIR" \) ]
then
  echo "Invalid directory given: $DIR"
  exit 1
fi

echo "`date`: Starting mailsender on directory $DIR"

cd $DIR

while :
do
  for file in `find . -maxdepth 1 -type f`
  do
    echo "`date`: Sending $file"
    sendmail -t < $file
    rm $file
  done
  sleep 1
done
Run Code Online (Sandbox Code Playgroud)

Ubuntu 使用 upstart,所以我创建了一个名为的文件/etc/init/mailsender.conf来将此脚本转换为守护程序:

description "sends mails from directory"
start on stopped rc RUNLEVEL=[2345]
stop on runlevel[!2345]
respawn
exec start-stop-daemon --start --make-pidfile --pidfile /var/run/mailsender.pid --exec
/path/to/mailsender.sh /var/mailsend
Run Code Online (Sandbox Code Playgroud)

我可以用 启动start mailsender和停止服务stop mailsender。我可以在 中查看它的日志/var/log/upstart/mailsender.log,当然我可以使用 PID 文件对其进行监控。

您需要创建/var/mailsend目录,然后通过将参数添加-v /var/mailsend:/var/mailsend到您的docker run命令,使其可从 Docker 容器访问。


mc0*_*c0e 5

这是一个半答案,或者至少是半个答案,因为我目前正在解决同样的问题。我希望有人可以帮助充实我错过的内容。

来自 OP (David Grayson) 的回答对我来说听起来像是重新发明了 postdrop mail-spool,但使用邮件 spool 听起来像是一种很有前途的方法,所以这就是我所达到的。

postfix提供的/usr/bin/sendmail兼容接口将邮件传递给postdrop,也就是sgid postdrop,允许它把邮件存入/var/spool/postfix/maildrop的maildrop队列中。这应该发生在 docker 容器中。希望后缀的其余部分不必在容器中运行。

所以,我是主机挂载 /var/spool/postfix/maildrop 和 /var/spool/postfix/public。我可以将邮件发送到主机环境中的 /var/spool/postfix/maildrop,因为我已经挂载了 maildrop 队列目录。因为我已经挂载了/var/spool/postfix/publicmaildrop可以发信号pickup从队列中收集邮件。不幸的是,除非我注意,否则会涉及 uids 和 gids,这意味着主机目录中的拾取无法读取假脱机文件,更糟糕的是 postfix 安装会弄乱主机环境中 maildrop 目录的权限。

不过,这似乎有效:

$ cat Dockerfile 
FROM debian:jessie
# Ids from parent environment

    RUN groupadd -g 124 postfix && \
        groupadd -g 125 postdrop && \
    useradd -u 116 -g 124 postfix

    RUN apt-get update && \
      DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
        postfix \
        bsd-mailx

    CMD echo test mail | mail myemail@example.com

$ sudo docker build   .
...
Successfully built 16316fcd44b6

$ sudo docker run   -v /var/spool/postfix/maildrop:/var/spool/postfix/maildrop \
  -v /var/spool/postfix/public:/var/spool/postfix/public 16316fcd44b6
Run Code Online (Sandbox Code Playgroud)

虽然它有效,但我对 uids 和 gids 的硬编码并不是很满意。这意味着不能将同一个容器计算为在任何地方都运行相同。我认为,如果不是从主机安装卷,而是从运行 postfix 的容器安装它,那么它永远不会发生冲突,而且我只需要安装一个 postfix 即可从许多容器中获取邮件。我将这些 uids 和 gids 设置在我所有容器都继承的基本映像中。

我确实想知道这是否真的是一个好方法。有了这样一个简单的邮件配置,并且容器上没有使用守护程序来重新尝试传递,像 msmtp 这样更简单的本地 MTA 可能更合适。它将通过 TCP 传送到同一主机上的中继,在那里会发生假脱机。

对 msmtp 方法的关注包括:

  • 如果它发送到的 smtp 中继不可用,则丢失邮件的可能性更大。如果这是同一主机上的中继,则网络问题的可能性很小,但是我必须小心重新启动中继容器的方式。
  • 表现?
  • 如果大量邮件通过,邮件是否开始被丢弃?

一般来说,共享 postfix spool 方法似乎更可能是一个脆弱的配置,但在运行时不太可能失败(中继不可用,因此邮件被丢弃)。