4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Postfixメールサーバーを構築し、メール受信をトリガーにPythonコードを起動し、メールをSlackに投稿する(ローカル環境編)

Last updated at Posted at 2020-06-04

記事の内容

Postfixでメールサーバーを構築し、メールが受信される度にサーバー側で自作プログラムを動かします。
自作プログラムは受信メールをインプットデータにして何らかの処理を行います。これはどの言語で実装しても良いのですが、本記事ではPythonを使ってSlackへの投稿を実装します。
メールサーバーは今回はローカル環境で最低限動くように構築し、メールの送受信はローカル環境内でのみ行います。セキュリティ等、詳細な設定は行いません。

環境

CentOS 8とUbuntu 20.04の両方の手順を示します。
BIND9、Postfix、Dovecot は Linuxパッケージからインストールします。
メールサーバーを構築するサーバーの環境は以下の通りです。

  • ドメイン名:localdomain
  • ホスト名:localhost
  • Linuxの一般ユーザー名とパスワード:usrname、secret

CentOS の Python バージョン


$ python3 -V
Python 3.6.8

Ubuntu の Python バージョン


$ python3 -V
Python 3.8.2

CentOS の ifconfig 結果


ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.79.128  netmask 255.255.255.0  broadcast 192.168.79.255
        inet6 fe80::59ac:7015:10c9:543c  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:93:f6:a7  txqueuelen 1000  (Ethernet)
        RX packets 914  bytes 156651 (152.9 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 284  bytes 25365 (24.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

virbr0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.122.1  netmask 255.255.255.0  broadcast 192.168.122.255
        ether 52:54:00:be:03:9b  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Ubuntu の ifconfig 結果


docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:cd:65:85:ad  txqueuelen 0  (イーサネット)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.79.130  netmask 255.255.255.0  broadcast 192.168.79.255
        inet6 fe80::f8e9:90fd:cfc4:280e  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:cc:76:18  txqueuelen 1000  (イーサネット)
        RX packets 159457  bytes 233626546 (233.6 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10206  bytes 690515 (690.5 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (ローカルループバック)
        RX packets 514  bytes 48478 (48.4 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 514  bytes 48478 (48.4 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Postfixメールサーバーを構築する

以下の2冊の書籍を見ながら、Postfix メールサーバーを構築します。
これらの書籍には、構築手順と解説が書かれていますので、本記事ではローカル環境構築に必要な手順のみ示します。
両書籍ともにCentOS 5.4を使用していますが、本記事では CentOS 8 と Ubuntu 20.04 用にコマンドを書き換えます。


  • 書名:BIND9によるDNSサーバ構築(改定新版)
  • 著者:伊藤宏通/著 川原龍人/著 野津新/著
  • 出版者:技術評論社
  • 出版年月:2010-07
  • ISBN:9784774142753
  • https://www.amazon.co.jp/dp/4774142751/

DNSの設定

書籍「BIND9によるDNSサーバ構築」p.84~p.86

rootユーザーで、BIND9 をインストールします。
CentOSでは書籍に従って chroot を使用します。
Ubuntuでは今回は chroot を使用しません。

CentOS


yum install bind bind-chroot

cd /var/named/chroot/etc
mv /etc/named.conf .
ln -s /var/named/chroot/etc/named.conf /etc/

Ubuntu


apt install bind9 bind9utils

書籍「BIND9によるDNSサーバ構築」p.86、p.96

rootユーザーで、/etc/resolv.conf を編集します。


editor /etc/resolv.conf

CentOS

/etc/resolv.conf

# Generated by NetworkManager
search localdomain
-nameserver 192.168.79.2
+nameserver 127.0.0.1

Ubuntu

/etc/resolv.conf

-nameserver 127.0.0.53
+nameserver 127.0.0.1
options edns0
search localdomain

書籍「BIND9によるDNSサーバ構築」p.87

rootユーザーで、named.conf を編集します。

CentOS


editor /etc/named.conf
/etc/named.conf

(前略)

options {
-        listen-on port 53 { 127.0.0.1; };
+        //listen-on port 53 { 127.0.0.1; };
-        listen-on-v6 port 53 { ::1; };
+        //listen-on-v6 port 53 { ::1; };
        directory       "/var/named";
        dump-file       "/var/named/data/cache_dump.db";
        statistics-file "/var/named/data/named_stats.txt";
        memstatistics-file "/var/named/data/named_mem_stats.txt";
        secroots-file   "/var/named/data/named.secroots";
        recursing-file  "/var/named/data/named.recursing";
-        allow-query     { localhost; };
+        //allow-query     { localhost; };

(中略)

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";
+
+zone "localdomain" IN {
+        type master;
+        file "localdomain.zone";
+        allow-update { none; };
+};

Ubuntu


editor /etc/bind/named.conf.local
/etc/bind/named.conf.local

//
// Do any local configuration here
//

// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";
+zone "localdomain" IN {
+        type master;
+        file "localdomain.zone";
+        allow-update { none; };
+};

書籍「BIND9によるDNSサーバ構築」p.89

rootユーザーで、named.conf の構文チェックを行います。

CentOS


/usr/sbin/named-checkconf /etc/named.conf

Ubuntu


/usr/sbin/named-checkconf /etc/bind/named.conf

書籍「BIND9によるDNSサーバ構築」p.89

rootユーザーで、localdomain.zone を新規作成します。

CentOS


editor /var/named/chroot/var/named/localdomain.zone
/var/named/chroot/var/named/localdomain.zone

$TTL 10800
localdomain. 1D IN SOA ns.localdomain. root.localdomain. (
        2020052601    ; serial
        43200         ; refresh
        5400          ; retry
        3600000       ; expiry
        3600 )        ; minimum

localdomain. 1D IN NS ns.localdomain.

localdomain. 1D IN MX 10 mail01.localdomain.

ns.localdomain. 1D IN A 192.168.79.128
mail01.localdomain. 1D IN A 192.168.79.128

Ubuntu


editor /var/cache/bind/localdomain.zone
/var/cache/bind/localdomain.zone

$TTL 10800
localdomain. 1D IN SOA ns.localdomain. root.localdomain. (
        2020052601    ; serial
        43200         ; refresh
        5400          ; retry
        3600000       ; expiry
        3600 )        ; minimum

localdomain. 1D IN NS ns.localdomain.

localdomain. 1D IN MX 10 mail01.localdomain.

ns.localdomain. 1D IN A 192.168.79.130
mail01.localdomain. 1D IN A 192.168.79.130

書籍「BIND9によるDNSサーバ構築」p.91

rootユーザーで、ゾーンファイルのパーミッション設定を行います。

CentOS


chmod 640 /var/named/chroot/var/named/localdomain.zone
chown root:named /var/named/chroot/var/named/localdomain.zone
ln -s /var/named/chroot/var/named/localdomain.zone /var/named/

Ubuntu


chmod 640 /var/cache/bind/localdomain.zone
chown root:bind /var/cache/bind/localdomain.zone

書籍「BIND9によるDNSサーバ構築」p.93

rootユーザーで、ゾーンファイルの構文チェックを行います。

CentOS


/usr/sbin/named-checkzone localdomain /var/named/chroot/var/named/localdomain.zone

Ubuntu


/usr/sbin/named-checkzone localdomain /var/cache/bind/localdomain.zone

書籍「BIND9によるDNSサーバ構築」p.94

rootユーザーで、BIND を起動します。

CentOS、Ubuntu共通


# 現在の状態を調べたい時
systemctl status named.service

# 停止してから起動したい時
systemctl stop named.service
systemctl start named.service

# OSブート時に自動起動したい時
systemctl enable named.service

書籍「BIND9によるDNSサーバ構築」p.100

rootユーザーで、ゾーン転送をテストします。

CentOS


dig @192.168.79.128 localdomain AXFR

CentOS 結果


; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el8 <<>> @192.168.79.128 localdomain AXFR
; (1 server found)
;; global options: +cmd
localdomain.		86400	IN	SOA	ns.localdomain. root.localdomain. 2020052601 43200 5400 3600000 3600
localdomain.		86400	IN	NS	ns.localdomain.
localdomain.		86400	IN	MX	10 mail01.localdomain.
mail01.localdomain.	86400	IN	A	192.168.79.128
ns.localdomain.		86400	IN	A	192.168.79.128
localdomain.		86400	IN	SOA	ns.localdomain. root.localdomain. 2020052601 43200 5400 3600000 3600
;; Query time: 1 msec
;; SERVER: 192.168.79.128#53(192.168.79.128)
;; WHEN: 木  6月 04 15:35:22 JST 2020
;; XFR size: 6 records (messages 1, bytes 217)

Ubuntu


dig @192.168.79.130 localdomain AXFR

Ubuntu 結果


; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.79.130 localdomain AXFR
; (1 server found)
;; global options: +cmd
localdomain.		86400	IN	SOA	ns.localdomain. root.localdomain. 2020052601 43200 5400 3600000 3600
localdomain.		86400	IN	NS	ns.localdomain.
localdomain.		86400	IN	MX	10 mail01.localdomain.
mail01.localdomain.	86400	IN	A	192.168.79.130
ns.localdomain.		86400	IN	A	192.168.79.130
localdomain.		86400	IN	SOA	ns.localdomain. root.localdomain. 2020052601 43200 5400 3600000 3600
;; Query time: 3 msec
;; SERVER: 192.168.79.130#53(192.168.79.130)
;; WHEN: 木  6月 04 15:36:11 JST 2020
;; XFR size: 6 records (messages 1, bytes 217)

Postfixの設定

書籍「Postfix実践入門」p.89

rootユーザーで、sendmail が動作していたら停止します。

CentOS、Ubuntu 共通


ps ax | grep sendmail

当記事の環境では sendmail は動作していなかったので、何もしませんでした。

書籍「Postfix実践入門」p.104

rootユーザーで、Postfixをインストールします。

CentOS


yum install postfix

Ubuntu


apt install postfix

書籍「Postfix実践入門」p.105(CentOS のみ)

rootユーザーで、alternatives コマンドで Postfix を選択します。

CentOS


alternatives --config mta

当記事の環境では、選択肢は Postfix のみでしたので、何もしませんでした。

書籍「Postfix実践入門」p.121

rootユーザーで、main.cf を編集します。

CentOS、Ubuntu 共通


cd /etc/postfix
cp main.cf main.cf.org
editor main.cf

CentOS

/etc/postfix/main.cf

(前略)

#myhostname = host.domain.tld
#myhostname = virtual.domain.tld
+myhostname = mail01.localdomain

(中略)

#mydomain = domain.tld
+mydomain = localdomain

(中略)

-#myorigin = $mydomain
+myorigin = $mydomain

(中略)

-mydestination = $myhostname, localhost.$mydomain, localhost
-#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+#mydestination = $myhostname, localhost.$mydomain, localhost
+mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain

(以下をファイル末尾に追加)

+# Allowed to run :include: method in /etc/aliases
+alias_maps = hash:/etc/aliases
+alias_database = hash:/etc/aliases
+allow_mail_to_commands = alias,forward,include
+allow_mail_to_files    = alias,forward,include

Ubuntu

/etc/postfix/main.cf

# See /usr/share/postfix/main.cf.dist for a commented, more complete version


# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 2



# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_security_level=may

smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache


smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
-#myhostname = sample.localdomain
+myhostname = mail01.localdomain
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
-mydestination = mail01.localhost.localdomain, $myhostname, sample, localhost.localdomain, localhost
+mydestination = mail01.localhost.localdomain, $myhostname, mail01, localhost.localdomain, localhost localdomain
relayhost = 
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = loopback-only
default_transport = error
relay_transport = error
inet_protocols = all
+allow_mail_to_commands = alias,forward,include
+allow_mail_to_files    = alias,forward,include

書籍「Postfix実践入門」p.130

rootユーザーで、Postfixを起動します。

CentOS、Ubuntu 共通


# 設定をチェックしたい時
postfix check

# 現在の状態を調べたい時
postfix status

# 停止してから起動したい時
postfix stop
postfix start

# リロードしたい時
postfix reload

書籍「Postfix実践入門」p.134

一般ユーザーで、telnet で SMTP 接続して Postfix の動作確認を行います。

CentOS、Ubuntu 共通


telnet localhost 25

EHLO localdomain

MAIL FROM:<usrname@localdomain>

RCPT TO:<usrname@localdomain>

DATA

This is test1
This is test2
This is test3
.

NOOP

QUIT

途中で以下のエラーが出た場合は、


RCPT TO:<usrname@localdomain>
451 4.3.0 <usrname@localdomain>: Temporary lookup failure

rootユーザーで以下を実行し(書籍「Postfix実践入門」p.142)、一般ユーザーに戻って telnet から再実行します。


postalias /etc/aliases
postfix reload

実行した時の画面イメージは以下の通り。

CentOS


[usrname@localhost ~]$ telnet localhost 25
Trying ::1...
Connected to localhost.
Escape character is '^]'.
220 mail01.localdomain ESMTP Postfix
EHLO localdomain
250-mail01.localdomain
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250 SMTPUTF8
MAIL FROM:<usrname@localdomain>
250 2.1.0 Ok
RCPT TO:<usrname@localdomain>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
This is test1
This is test2
This is test3
.
250 2.0.0 Ok: queued as 39B219AF86
NOOP
250 2.0.0 Ok
QUIT
221 2.0.0 Bye
Connection closed by foreign host.

Ubuntu


usrname@localhost:~$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail01.localdomain ESMTP Postfix (Ubuntu)
EHLO localdomain
250-mail01.localdomain
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250-SMTPUTF8
250 CHUNKING
MAIL FROM:<usrname@localdomain>
250 2.1.0 Ok
RCPT TO:<usrname@localdomain>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
This is test1
This is test2
This is test3
.
250 2.0.0 Ok: queued as 49F8B2F816F8
NOOP
250 2.0.0 Ok
QUIT
221 2.0.0 Bye
Connection closed by foreign host.

書籍「Postfix実践入門」p.141

rootユーザーで、エイリアスファイルを設定します。
書籍の手順に加えて、メール受信時に起動させるコマンドもここで指定しておきます。当記事では、メール受信の度に「/home/usrname/email_hook/hook」を起動することにします。このhookという名前のファイルは後に作成します。

CentOS、Ubuntu 共通


editor /etc/aliases

CentOS


(ファイル末尾)

# Person who should get root's mail
#root:          marc
+root:           usrname
+
+# hook
+usrname: usrname, :include:/home/usrname/email_hook/hook

Ubuntu


# See man 5 aliases for format
postmaster:    root

# Person who should get root's mail
root:           usrname
+
+# hook
+usrname: usrname, :include:/home/usrname/email_hook/hook

書籍「Postfix実践入門」p.142

rootユーザーで、aliases.db を更新して Postfix をリロードします。

CentOS、Ubuntu 共通


postalias /etc/aliases
postfix reload

Dovecotの設定

書籍「Postfix実践入門」p.215

rootユーザーで、Dovecot をインストールします。

CentOS


yum install dovecot

Ubuntu


apt install dovecot-core dovecot-imapd dovecot-pop3d

書籍「Postfix実践入門」p.222

rootユーザーで、dovecot-openssl.cnf を各々の状況に合わせて編集します。

CentOS


editor /etc/pki/dovecot/dovecot-openssl.cnf

CentOS は私の場合は以下の通り。

/etc/pki/dovecot/dovecot-openssl.cnf

[ req ]
default_bits = 3072
encrypt_key = yes
distinguished_name = req_dn
x509_extensions = cert_type
prompt = no

[ req_dn ]
# country (2 letter code)
#C=FI
+C=JP

# State or Province Name (full name)
#ST=
+ST=TOKYO

# Locality Name (eg. city)
#L=Helsinki
+L=Chofu

# Organization (eg. company)
#O=Dovecot
+O=Kanedaq Office

# Organizational Unit Name (eg. section)
OU=IMAP server

# Common Name (*.example.com is also possible)
-CN=imap.example.com
+CN=mail01.localdomain

# E-mail contact
-emailAddress=postmaster@example.com
+emailAddress=postmaster@localdomain

[ cert_type ]
nsCertType = server

Ubuntu


editor /usr/share/dovecot/dovecot-openssl.cnf

Ubuntu は今回は内容を変更しませんでした。

/usr/share/dovecot/dovecot-openssl.cnf

#
# SSLeay configuration file for Dovecot.
#

RANDFILE                = /dev/urandom

[ req ]
default_bits            = 2048
default_keyfile         = privkey.pem
distinguished_name      = req_distinguished_name
prompt                  = no
policy                  = policy_anything
req_extensions          = v3_req
x509_extensions         = v3_req

[ req_distinguished_name ]
organizationName = Dovecot mail server
organizationalUnitName = @commonName@
commonName = @commonName@
emailAddress = @emailAddress@

[ v3_req ]
basicConstraints        = CA:FALSE

書籍「Postfix実践入門」p.223

rootユーザーで、証明書を再作成します。

CentOS


sh /usr/share/doc/dovecot/mkcert.sh

Ubuntu は、実行するなら以下のコマンドですが、今回は実行しませんでした。


sh /usr/share/dovecot/mkcert.sh

書籍「Postfix実践入門」p.227

rootユーザーで、dovecot.conf を編集します。

CentOS、Ubuntu 共通


editor /etc/dovecot/dovecot.conf

CentOS

/etc/dovecot/dovecot.conf

(前略)

# Protocols we want to be serving.
#protocols = imap pop3 lmtp
+protocols = pop3

(以下をファイル末尾に追加)

+log_path = /var/log/dovecot.log
+disable_plaintext_auth = no    # とりあえず平文パスワードを許可(安全でない)

Ubuntu

/etc/dovecot/dovecot.conf

(前略)

# Enable installed protocols
-!include_try /usr/share/dovecot/protocols.d/*.protocol
+!include_try /usr/share/dovecot/protocols.d/pop3d.protocol

(以下をファイル末尾に追加)

+log_path = /var/log/dovecot.log
+disable_plaintext_auth = no    # とりあえず平文パスワードを許可(安全でない)

書籍「Postfix実践入門」p.228

rootユーザーで、メールボックスの位置を編集します。

CentOS、Ubuntu 共通


editor /etc/dovecot/conf.d/10-mail.conf

CentOS

/etc/dovecot/conf.d/10-mail.conf

(前略)

#mail_location = 
+mail_location = mbox:~/mail:INBOX=/var/mail/%u

(後略)

Ubuntu は、以下のように既に意図通りに設定されていたので、変更しませんでした。

/etc/dovecot/conf.d/10-mail.conf

(前略)

mail_location = mbox:~/mail:INBOX=/var/mail/%u

(後略)

書籍「Postfix実践入門」p.229

rootユーザーで、Dovecot を起動します。

CentOS、Ubuntu 共通


# 現在の状態を調べたい時
systemctl status dovecot.service

# 停止してから起動したい時
systemctl stop dovecot.service
systemctl start dovecot.service

# OSブート時に自動起動したい時
systemctl enable dovecot.service

書籍「Postfix実践入門」p.230

rootユーザーで、Dovecotの起動設定を確認します。

CentOS、Ubuntu 共通


systemctl list-unit-files -t service | grep dovecot

書籍「Postfix実践入門」p.231

rootユーザーで、開いているポートを確認します。

CentOS、Ubuntu 共通


netstat -ln | grep tcp

CentOS 結果


tcp        0      0 0.0.0.0:5355            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:110             0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN     
tcp        0      0 192.168.79.128:53       0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:53            0.0.0.0:*               LISTEN     
tcp        0      0 192.168.122.1:53        0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:953           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:995             0.0.0.0:*               LISTEN     
tcp6       0      0 :::5355                 :::*                    LISTEN     
tcp6       0      0 :::110                  :::*                    LISTEN     
tcp6       0      0 :::111                  :::*                    LISTEN     
tcp6       0      0 :::53                   :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN     
tcp6       0      0 ::1:631                 :::*                    LISTEN     
tcp6       0      0 ::1:25                  :::*                    LISTEN     
tcp6       0      0 ::1:953                 :::*                    LISTEN     
tcp6       0      0 :::995                  :::*                    LISTEN     

Ubuntu 結果


tcp        0      0 172.17.0.1:53           0.0.0.0:*               LISTEN     
tcp        0      0 192.168.79.130:53       0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:53            0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:953           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:995             0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:110             0.0.0.0:*               LISTEN     
tcp6       0      0 fe80::f8e9:90fd:cfc4:53 :::*                    LISTEN     
tcp6       0      0 ::1:53                  :::*                    LISTEN     
tcp6       0      0 ::1:631                 :::*                    LISTEN     
tcp6       0      0 ::1:25                  :::*                    LISTEN     
tcp6       0      0 ::1:953                 :::*                    LISTEN     
tcp6       0      0 :::995                  :::*                    LISTEN     
tcp6       0      0 :::110                  :::*                    LISTEN     

書籍「Postfix実践入門」p.232

一般ユーザーで、telnet で POP サーバーの動作確認を行います。
その前に、CentOS では root ユーザーで以下の処理が必要でした。

CentOS


chmod 0600 /var/mail/*
chmod 0600 /var/spool/mail/*

それでは telnet を起動します。

CentOS、Ubuntu 共通


telnet localhost 110

USER usrname

PASS secret

LIST

RETR 1

QUIT

実行した時の画面イメージは以下の通り。

CentOS


[usrname@localhost ~]$ telnet localhost 110
Trying ::1...
Connected to localhost.
Escape character is '^]'.
+OK Dovecot ready.
USER usrname
+OK
PASS secret
+OK Logged in.
LIST
+OK 1 messages:
1 463
.
RETR 1
+OK 463 octets
Return-Path: <usrname@localdomain>
X-Original-To: usrname@localdomain
Delivered-To: usrname@localdomain
Received: from localdomain (localhost [IPv6:::1])
	by mail01.localdomain (Postfix) with ESMTP id 39B219AF86
	for <usrname@localdomain>; Thu,  4 Jun 2020 17:52:38 +0900 (JST)
Message-Id: <20200604085241.39B219AF86@mail01.localdomain>
Date: Thu,  4 Jun 2020 17:52:38 +0900 (JST)
From: usrname@localdomain

This is test1
This is test2
This is test3
.
QUIT
+OK Logging out.
Connection closed by foreign host.

Ubuntu


usrname@localhost:~$ telnet localhost 110
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
+OK Dovecot (Ubuntu) ready.
USER usrname
+OK
PASS secret
+OK Logged in.
LIST
+OK 1 messages:
1 468
.
RETR 1
+OK 468 octets
Return-Path: <usrname@localdomain>
X-Original-To: usrname@localdomain
Delivered-To: usrname@localdomain
Received: from localdomain (localhost [127.0.0.1])
	by mail01.localdomain (Postfix) with ESMTP id 49F8B2F816F8
	for <usrname@localdomain>; Thu,  4 Jun 2020 17:08:33 +0900 (JST)
Message-Id: <20200604080837.49F8B2F816F8@mail01.localdomain>
Date: Thu,  4 Jun 2020 17:08:33 +0900 (JST)
From: usrname@localdomain

This is test1
This is test2
This is test3
.
QUIT
+OK Logging out.
Connection closed by foreign host.

以上で Postfix メールサーバーの構築は完了です。

メール受信時に起動されるコードの実装

以降の作業は一般ユーザーで行います。
CentOS と Ubuntu で、作業に違いはありません。

ホームディレクトリ( /home/usrname )の下にサブディレクトリ( email_hook )を作成し、ここに全てのファイルを置くことにします。


cd
mkdir email_hook
cd email_hook

以降の作業は /home/usrname/email_hook の下で行います。

メール受信時に起動されるコマンドの新規作成

hookという名前で(名前は何でも良いですが)、ファイルを新規作成し、そこにコマンドを記述します。


editor hook

コマンドの内容は、Postfix が受信したメールを、パイプラインを通して hook_slack.pyという Python コード(後で実装します)に渡します。


"|LC_CTYPE='C.UTF-8' /usr/bin/python3 /home/usrname/email_hook/hook_slack.py || true"

メール受信時に起動されるPythonコードの新規作成

hook_slack.py というファイル名(名前は何でも良いです)で新規作成します。


editor hook_slack.py

内容は以下の通りです。
コードの行儀があまり良くないのはお許しを。
Slack Incoming Webhooks の URL は適切なものを設定してください。

/home/usrname/email_hook/hook_slack.py

import logging
import os
import sys
import time
import datetime
import json
import email.parser
import urllib.request
import urllib.parse
from pathlib import Path


def make_logfile_path(file):
    directory = Path("/home/usrname/email_hook/log")

    # ディレクトリがなければ作成
    directory.mkdir(parents=True, exist_ok=True)

    # フルパスのログファイル名を返す
    return directory / os.path.basename(os.path.splitext(file)[0] + datetime.datetime.today().strftime("_%Y%m%d_%H%M%S.log"))


def get_logger(name, filepath):
    LOG_LEVEL_FILE = logging.DEBUG
    LOG_LEVEL_CONSOLE = logging.INFO

    _detail_formatting = "\n%(asctime)s %(levelname)-8s [%(module)s#%(funcName)s %(lineno)d]\n%(message)s"

    logging.basicConfig(
        level=LOG_LEVEL_FILE,
        format=_detail_formatting,
        filename=filepath
    )

    # ログをコンソールに送るハンドラconsoleを作成
    console = logging.StreamHandler()
    console.setLevel(LOG_LEVEL_CONSOLE)
    console_formatter = logging.Formatter(_detail_formatting)
    console.setFormatter(console_formatter)

    # ロガーを取得し、consoleハンドラを追加する
    logger = logging.getLogger(name)
    logger.addHandler(console)
    return console, logger


# ロガー
console, logger = get_logger(__name__, make_logfile_path(__file__))


def main():
    start = time.time()
    logger.info(f"hook started : {time.strftime('%d %b %X', time.localtime(start))}")

    # Postfixが受信したメールを標準入力から受け取る
    mime_str = sys.stdin.read()
    logger.debug(f"mime_str={mime_str}")
    message = email.parser.Parser().parsestr(mime_str)
    logger.debug(f"message={message}")

    ## Slack Incoming Webhooks の URL
    url = "内緒"

    payload = {}
    logger.debug(f'message.get("Subject")={message.get("Subject")}')
    logger.debug(f"message.get_payload()={message.get_payload()}")

    try:
        body = message.get_payload(0).get_payload()
    except:
        body = message.get_payload()
    payload["text"] = "Subject: " + message.get("Subject") + "\n\n" + body
    logger.debug(f"payload={payload}")

    data = json.dumps(payload).encode("utf-8")
    logger.debug(f"data={data}")

    request = urllib.request.Request(url, data)
    urllib.request.urlopen(request)

    stop = time.time()
    delta = stop - start
    logger.info(f"hook started : {time.strftime('%d %b %X', time.localtime(start))}")
    logger.info(f"hook finished : {time.strftime('%d %b %X', time.localtime(stop))}")
    logger.info("hook duration : {:0.3} seconds".format(delta))

if __name__ == "__main__":
    try:
        main()
    except Exception as ee:
        logger.exception(ee)

hook_slack.py.pyのSlack投稿テスト用データを用意しました。


editor hook_test_stdin.txt
/home/usrname/email_hook/hook_test_stdin.txt

From usrname@localdomain  Wed Jun  3 21:41:32 2020
Return-Path: <usrname@localdomain>
X-Original-To: usrname@localdomain
Delivered-To: usrname@localdomain
Received: from localhost.localdomain (localhost [127.0.0.1])
	by mail01.localdomain (Postfix) with ESMTPS id 9630E40D18C9
	for <usrname@localdomain>; Wed,  3 Jun 2020 21:41:32 +0900 (JST)
Content-Type: multipart/mixed; boundary="===============8887878637416477407=="
MIME-Version: 1.0
Subject: Slack投稿テストsubject
From: usrname@localdomain
To: usrname@localdomain
Date: Wed, 03 Jun 2020 12:41:32 -0000
Message-Id: <20200603124132.9630E40D18C9@mail01.localdomain>

--===============8887878637416477407==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

Slack投稿テストbody
--===============8887878637416477407==--

hook_slack.py を動かして、Slackに投稿されるかテストしてみます。


cat hook_test_stdin.txt | LC_CTYPE='C.UTF-8' /usr/bin/python3 ./hook_slack.py || true

logサブディレクトリの下にログファイルが出力され、Slackには以下のように投稿されました。

slack1.png

テストメールを送信するPythonコードの新規作成

send_testmail.py というファイル名(名前は何でも良いです)で新規作成します。


editor send_testmail.py

内容は以下の通りです。

/home/usrname/email_hook/send_testmail.py

import smtplib
from email.mime.text import MIMEText
from email.utils import formatdate
from email.mime.multipart import MIMEMultipart


def create_message(from_addr, to_addr, subject, body):
    # ヘッダー
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Date'] = formatdate()

    # 本文
    msg.attach(MIMEText(body))

    return msg


def send_mail(from_addr, to_addr, body_msg):
    smtpobj = smtplib.SMTP("localhost", 25)
    smtpobj.ehlo()
    smtpobj.starttls()
    smtpobj.ehlo()
    smtpobj.sendmail(from_addr, to_addr, body_msg.as_string())
    smtpobj.close()


MAIL_ADDRESS = "usrname@localdomain"

from_addr = MAIL_ADDRESS
to_addr = MAIL_ADDRESS
subject = "test mail"
body = "We'll send you a test email."

msg = create_message(from_addr, to_addr, subject, body)
send_mail(from_addr, to_addr, msg)

このプログラムを動かしてメールを送信し、hookが起動するかテストします。


python3 ./send_testmail.py

Slackには以下のように投稿されました。

slack2.png

今後やってみたいことリスト

  • IMAP IDLEを使ってプッシュメール
  • VPSにメールサーバーを立てて、セキュリティ等しっかり設定する
  • MetaTraderのインジケーターやEAには、メール送信機能がついているものが多いらしいので、EAにローカルサーバー宛にメールを送信してもらって、hookプログラムで何らかの処理を行いたい。

以上です。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?