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


18

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

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

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

有什么方法可以让容器sendmail在主机上运行可执行文件?

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

这里的邮件日志内容

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

请张贴您电子邮件的标头(被GMAIL误认为是垃圾邮件的标头)
masegaloeh 2014年

我尝试发送的电子邮件只有一个To标头,Subject标头和一个一行的正文。我不确定在Postfix将其通过milter后如何知道它具有什么标题,您知道如何吗?这是/ var / log / syslog中的输出,显示了Postfix如何处理它以及Gmail拒绝了它: gist.github.com/DavidEGrayson/fbf65c8290c049a1f262
David Grayson

Answers:


8

因为您有一个可行的解决方案,所以在这里我将尝试解释通过Telnet到后缀(SMTP)以及使用sendmail(非SMTP)时的不同行为。

仅供参考,OpenDKIM将由带有Milter机制的postfix调用。您可以通过此官方文档获得一些信息,了解如何在postfix中实现更好的实现。这是后缀中的斜接钩的示意图。

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

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

  • 非SMTP电子邮件在注入milter之前将通过清理处理。清理守护程序负责添加缺少的标头:(Resent-)发件人:,收件人:,消息ID:日期:。因此,即使原始电子邮件的标题不完整,当您将其电子邮件注入OpenDKIM milter时,它的标题也将完整。

  • SMTP电子邮件将在进行任何清除处理之前注入到OpenDKIM milter中。因此,如果您的原始电子邮件的标题不完整,则opendkim可能会拒绝对该电子邮件签名。“ 发件人:”标头是强制性的(请参阅RFC 6376),如果电子邮件中没有标头,则OpenDKIM将拒绝对电子邮件签名并警告您

    can't determine message sender; accepting
    

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


那很启发。谢谢。不幸的是,我仍然没有看到比当前解决方案更好的解决方案(在我的答案中进行了描述)。
David Grayson 2014年

显而易见的原因是将应用程序修复为From:在您的电子邮件中添加标题:)
masegaloeh 2014年

但是我还必须添加一些Message-Id我不太了解的东西,而且我可能会弄错了……让清理守护程序来解决这个问题似乎更容易。
大卫·格雷森

实际上,正如RFC 6376所说,Message-ID不是强制性的。默认情况下,强制标头仅是From标头。但是,如果您想生成自己的Message-ID,则可以使用类似IETF草案的
masegaloeh 2014年


5

这是一个半答案,或者至少是一个半测试的答案,因为我目前正在解决相同的问题。我希望有人能帮助充实我错过的一切。

OP的答案(David Grayson)对我来说听起来像是对发布后邮件后台处理的重新发明,但是使用该邮件后台处理听起来像是一种有前途的方法,所以这就是我要去的地方。

postfix提供的/ usr / bin / sendmail兼容性接口将邮件传递到sgid postdrop的postdrop,从而允许将邮件存储到/ var / spool / postfix / maildrop的maildrop队列中。这应该在docker容器中发生。希望其余的postfix不必在容器中运行。

因此,我正在托管/ var / spool / postfix / maildrop和/ var / spool / postfix / public。因为已经安装了maildrop队列目录,所以我可以将邮件传递到主机环境中的/ var / spool / postfix / maildrop。因为我已经安装了/var/spool/postfix/publicmaildrop可以发出信号pickup从队列中收集邮件。不幸的是,除非我照顾好它,否则涉及的uid和gids意味着主机目录中的提取无法读取假脱机文件,更糟糕的是,后缀安装会破坏主机环境中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

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

我确实想知道这是否真的是一个好方法。使用这种简单的邮件配置,并且容器上没有守护程序可用于重试传递,因此更简单的本地MTA(如msmtp)可能更合适。它会通过TCP传递到同一主机上的中继,在此发生假脱机。

与msmtp方法有关的问题包括:

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

通常,共享的后缀假脱机方法似乎更可能是脆弱的设置,但不太可能在运行时失败(中继不可用,因此邮件被丢弃)。


4

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

我制作了一个名为mailsender.sh的外壳脚本,该脚本从指定目录读取邮件,将其发送到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

Ubuntu使用新贵,因此我创建了一个名为的文件/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

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

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


也许像mini_sendmail这样的东西会有所帮助?它用于容器中,就像容器隔离的应用程序与容器宿主系统上的sendmail服务器守护程序之间的桥梁。cyberciti.biz/tips/... acme.com/software/mini_sendmail
Mikl

如果它通过SMTP将电子邮件发送到Postfix,我认为Postfix不会清理电子邮件。也许如果您的MTA具有更高的可配置性(或者我们想出了如何更好地配置Postfix),它会起作用。
David Grayson 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.