10. nginx のインストール
#-- 必要な情報を変数に設定
# DOMAIN=sacloud.ma3ki.net
# HTTP_DOCROOT=/var/www/html/http_root
# HTTPS_DOCROOT=/var/www/html/https_root
# source /etc/sysconfig/network-scripts/ifcfg-eth0
#-- nginx と php-fpm のインストール
# dnf install -y nginx php php-{fpm,ldap,devel,xml,pear,json}
#-- php, php-fpm の設定
# cp -p /etc/php.ini{,.org}
# sed -i -e "s/^max_execution_time = 30/max_execution_time = 300/" \
-e "s/^max_input_time = 60/max_input_time = 300/" \
-e "s/^post_max_size = 8M/post_max_size = 20M/" \
-e "s/^upload_max_filesize = 2M/upload_max_filesize = 20M/" \
-e "s/^;date.timezone =/date.timezone = 'Asia\/Tokyo'/" /etc/php.ini
# cp -p /etc/php-fpm.d/www.conf{,.org}
# sed -i -e "s/user = apache/user = nginx/" -e "s/group = apache/group = nginx/" /etc/php-fpm.d/www.conf
#-- phpldapadminの10000ユーザ対応
# sed -i -e 's/^;php_admin_value\[memory_limit\] = 128M/php_admin_value\[memory_limit\] = 256M/' /etc/php-fpm.d/www.conf
# chown nginx /var/log/php-fpm
# chgrp nginx /var/lib/php/*
#-- php を update すると、permission が apache に戻るので、その対策
# cat <<_EOL_>> /etc/cron.d/sacloud
* * * * * root chown nginx /var/log/php-fpm >/dev/null 2>&1
* * * * * root chgrp nginx /var/lib/php/{opcache,session,wsdlcache} >/dev/null 2>&1
_EOL_
#-- nginx mail proxy の設定
# cat <<_EOL_> /etc/nginx/mail.conf
mail {
auth_http 127.0.0.1/nginx_mail_proxy/ldap_authentication.php ;
proxy on;
proxy_pass_error_message on;
include /etc/nginx/default.d/${DOMAIN}_ssl.conf;
ssl_session_cache shared:MAIL:10m;
smtp_capabilities PIPELINING 8BITMIME "SIZE 20480000";
pop3_capabilities TOP USER UIDL;
imap_capabilities IMAP4rev1;
smtp_auth login plain;
server {
listen ${IPADDR}:587;
protocol smtp;
starttls on;
xclient on;
resolver 8.8.8.8 8.8.4.4;
auth_http_header PORT 587;
}
server {
listen ${IPADDR}:465;
protocol smtp;
ssl on;
xclient on;
resolver 8.8.8.8 8.8.4.4;
auth_http_header PORT 465;
}
server {
listen ${IPADDR}:995;
protocol pop3;
ssl on;
auth_http_header PORT 995;
}
server {
listen ${IPADDR}:993;
protocol imap;
ssl on;
auth_http_header PORT 993;
}
}
_EOL_
#-- nginx の設定
# cp -p /etc/nginx/nginx.conf{,.org}
# cat <<'_EOL_'> /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
}
include /etc/nginx/mail.conf;
_EOL_
# cat <<_EOL_> /etc/nginx/default.d/${DOMAIN}_ssl.conf
ssl_protocols TLSv1.2 TLSv1.3 ;
ssl_ciphers EECDH+AESGCM;
ssl_ecdh_curve prime256v1;
ssl_prefer_server_ciphers on;
ssl_session_timeout 5m;
ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/${DOMAIN}/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 10s;
_EOL_
# cat <<_EOL_> /etc/nginx/conf.d/http.conf
server {
listen ${IPADDR}:80;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 127.0.0.1:80;
server_name _;
index index.html, index.php;
access_log /var/log/nginx/access_auth.log main;
error_log /var/log/nginx/error_auth.log error;
root ${HTTP_DOCROOT};
server_tokens off;
charset utf-8;
location ~ \.php\$ {
allow 127.0.0.1;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
deny all;
}
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
_EOL_
# mkdir -p ${HTTP_DOCROOT} ${HTTPS_DOCROOT} /var/log/nginx /var/cache/nginx/client_temp /etc/nginx/conf.d/https.d
# cp -p /usr/share/nginx/html/[45]*.html /usr/share/nginx/html/*.png ${HTTP_DOCROOT}
# cp -p /usr/share/nginx/html/[45]*.html /usr/share/nginx/html/*.png ${HTTPS_DOCROOT}
# chown -R nginx. /var/www/html/ /var/log/nginx /var/cache/nginx/client_temp
# cat <<_EOL_> /etc/nginx/conf.d/https.conf
server {
listen 443 ssl http2 default_server;
server_name ${DOMAIN};
include /etc/nginx/default.d/${DOMAIN}_ssl.conf;
index index.html, index.php;
access_log /var/log/nginx/access_ssl.log main;
error_log /var/log/nginx/error_ssl.log error;
root ${HTTPS_DOCROOT};
server_tokens off;
charset utf-8;
ssl_session_cache shared:WEB:10m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_stapling on ;
ssl_stapling_verify on ;
include /etc/nginx/conf.d/https.d/*.conf;
location ~ \.php\$ {
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root/\$fastcgi_script_name;
include fastcgi_params;
}
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
_EOL_
#-- mta-sts へ対応
mkdir -p ${HTTPS_DOCROOT}/.well-known
cat <<_EOL_> ${HTTPS_DOCROOT}/.well-known/mta-sts.txt
version: STSv1
mx: ${DOMAIN}
mode: enforce
max_age: 10368000
_EOL_
#-- nginx-mail-proxy用の認証スクリプト設置先の作成と必要なモジュールのインストール
# mkdir -p ${HTTP_DOCROOT}/nginx_mail_proxy
# echo "no" | pecl install redis
# echo extension=redis.so >> /etc/php.d/99-redis.ini
#-- nginx, php-fpm の起動
# systemctl enable nginx php-fpm
# systemctl start nginx php-fpm
#-- OS再起動時にnginxの起動に失敗することがあるので、その対応
# sed -i -e "s/^\(After=network.target remote-fs.target nss-lookup.target\)/\1 network-online.target\nWants=network-online.target/" /usr/lib/systemd/system/nginx.service
# systemctl daemon-reload
#-- firewall の設定
# firewall-cmd --permanent --add-port={25,587,465,993,995,80,443}/tcp
# firewall-cmd --reload
11. nginx-mail-proxy用の認証スクリプト作成
# cd ${HTTP_DOCROOT}/nginx_mail_proxy
# cat <<'_EOL_'>> ldap_authentication.php
<?php
error_reporting(0);
// write syslog
function _writelog($message) {
openlog("nginx-mail-proxy", LOG_PID, LOG_MAIL);
syslog(LOG_INFO, "$message");
closelog();
}
// ldap authentication
function _ldapauth($server,$port,$dn,$passwd) {
$conn = ldap_connect($server, $port);
if ($conn) {
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
$bind = ldap_bind($conn, $dn, $passwd);
if ($bind) {
ldap_close($conn);
return true;
} else {
ldap_close($conn);
return false;
}
} else {
return false;
}
}
function _mail_proxy($server,$port,$base,$filter,$attribute,$proxyport) {
$message = "" ;
$proxyhost = _ldapsearch($server, $port, $base, $filter, $attribute);
if ($proxyhost === '' ) {
// proxyhost is not found
$message = "proxy=failure" ;
header('Content-type: text/html');
header('Auth-Status: Invalid login');
} else {
// proxyhost is found
$proxyip = gethostbyname($proxyhost);
$message = sprintf('proxy=%s:%s', $proxyhost, $proxyport);
header('Content-type: text/html');
header('Auth-Status: OK');
header("Auth-Server: $proxyip");
header("Auth-Port: $proxyport");
}
return $message ;
}
// ldap search
function _ldapsearch($server,$port,$base,$filter,$attribute) {
$conn = ldap_connect($server, $port);
if ($conn) {
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
$sresult = ldap_search($conn, $base, $filter, array($attribute));
$info = ldap_get_entries($conn, $sresult);
if ($info[0][$attribute][0] != "" ) {
return $info[0][$attribute][0];
}
}
return "" ;
}
// set $env from nginx
$env['meth'] = getenv('HTTP_AUTH_METHOD');
$env['user'] = getenv('HTTP_AUTH_USER');
$env['passwd'] = getenv('HTTP_AUTH_PASS');
$env['salt'] = getenv('HTTP_AUTH_SALT');
$env['proto'] = getenv('HTTP_AUTH_PROTOCOL');
$env['attempt'] = getenv('HTTP_AUTH_LOGIN_ATTEMPT');
$env['client'] = getenv('HTTP_CLIENT_IP');
$env['host'] = getenv('HTTP_CLIENT_HOST');
$env['port'] = getenv('HTTP_PORT');
$log = "" ;
// protocol port map
$portmap = array(
"smtp" => 25,
"pop3" => 110,
"imap" => 143,
);
// port searvice name map
$protomap = array(
"995" => "pops",
"993" => "imaps",
"110" => "pop",
"143" => "imap",
"587" => "smtp",
"465" => "smtps",
);
// ldap setting // atribuite は全て小文字で記述すること
$ldap = array(
"host" => "127.0.0.1",
"port" => 389,
"basedn" => "",
"filter" => "(mailRoutingAddress=" . $env['user'] . ")",
"attribute" => "mailmessagestore",
"dn" => "",
"passwd" => "",
);
// split uid and domain
$spmra = preg_split('/\@/', $env['user']);
// make dn
foreach (preg_split("/\./", $spmra[1]) as $value) {
$ldap['dn'] = $ldap['dn'] . 'dc=' . $value . ',' ;
}
$tmpdn = preg_split('/,$/',$ldap['dn']);
$ldap['basedn'] = $tmpdn[0];
$ldap['dn'] = 'uid=' . $spmra[0] . ',ou=People,' . $tmpdn[0];
// set search attribute
if ($env['proto'] === 'smtp' ) {
$ldap['attribute'] = 'mailhost' ;
}
// set log
$log = sprintf('meth=%s, user=%s, client=%s, proto=%s', $env['meth'], $env['user'], $env['client'], $protomap[$env['port']]);
// set password
$ldap['passwd'] = urldecode($env['passwd']);
// set ratelimit
$max_failcnt = 3 ;
$max_rejectcnt = 3 ;
$expire_time = 120 ;
$reject_time = 600 ;
$max_reject_time = 86400 ;
$whitelist = [
"127.0.0.1",
"#IPV4"
];
// check whitelist
if ( ! preg_grep("/^$clientip$/", $whitelist) ) {
$redis = new Redis();
try {
$redis->connect('127.0.0.1', 6379);
$key = $protomap[$env['port']] . ":" . $env['client'] ;
$clientip = $env['client'] ;
$failcnt = $redis->Get($key);
$ttl = $redis->ttl($key);
$rejectcnt = $redis->hGet('blacklist', $key);
if ($failcnt >= $max_failcnt && $ttl > 0 ) {
// $log = sprintf('auth=reject, %s, failcnt=%s, rejectcnt=%s, ttl=%s',$log,$failcnt,$rejectcnt,$ttl);
$log = sprintf('auth=reject, %s, passwd=%s, failcnt=%s, rejectcnt=%s, ttl=%s',$log,$ldap['passwd'],$failcnt,$rejectcnt,$ttl);
header('Content-type: text/html');
header('Auth-Status: Invalid login');
_writelog($log);
exit;
}
}
catch(Exception $e) {
}
$redis->close();
}
// ldap authentication
if (_ldapauth($ldap['host'], $ldap['port'], $ldap['dn'], $ldap['passwd'])) {
// authentication successful
$log = sprintf('auth=successful, %s', $log);
$proxyport = $portmap[$env['proto']];
$result = _mail_proxy($ldap['host'], $ldap['port'], $ldap['basedn'], $ldap['filter'], $ldap['attribute'], $proxyport);
$log = sprintf('%s, %s', $log, $result);
} else {
// authentication failure
// $log = sprintf('auth=failure, %s', $log);
$log = sprintf('auth=failure, %s, passwd=%s', $log, $ldap['passwd']);
// check whitelist
if ( ! preg_grep("/^$clientip$/", $whitelist) ) {
// set failcnt to redis
$redis = new Redis();
try {
$redis->connect('127.0.0.1', 6379);
$key = $protomap[$env['port']] . ":" . $env['client'] ;
$ttl = $redis->ttl($key);
$failcnt = $redis->Get($key) + 1;
$rejectcnt = $redis->hGet('blacklist', $key);
if ( $failcnt > $max_failcnt ) {
$failcnt = 1 ;
}
if ( $failcnt < $max_failcnt ) {
$redis->Set($key,$failcnt,$expire_time + $ttl);
if ( empty($rejectcnt) ) {
$rejectcnt = 0;
$redis->hSet('blacklist', $key, $rejectcnt);
}
} else {
$rejectcnt += 1;
$redis->hSet('blacklist', $key, $rejectcnt);
if ( $rejectcnt >= $max_rejectcnt ) {
$reject_time = $max_reject_time ;
$redis->hSet('blacklist', $key, 0);
}
$redis->Set($key, $failcnt, $reject_time);
}
$ttl = $redis->ttl($key);
$log = sprintf('%s, failcnt=%s, rejectcnt=%s, ttl=%s',$log,$failcnt,$rejectcnt,$ttl);
}
catch(Exception $e) {
}
$redis->close();
}
header('Content-type: text/html');
header('Auth-Status: Invalid login');
}
_writelog($log);
exit;
?>
_EOL_
# sed -i "s/#IPV4/${IPADDR}/" ${HTTP_DOCROOT}/nginx_mail_proxy/ldap_authentication.php
これでメールの送受信ができるようになりました。
12. Thunderbird 用の autoconfig を作成
#-- thunderbird 用 autoconfig を作成
# mkdir -p ${HTTPS_DOCROOT}/.well-known/autoconfig/mail
# chown -R nginx. ${HTTPS_DOCROOT}/.well-known
# cat <<'_EOL_'>${HTTPS_DOCROOT}/.well-known/autoconfig/mail/config-v1.1.xml
<?xml version="1.0"?>
<clientConfig version="1.1">
<emailProvider id="sakuravps">
<domain>_DOMAIN_</domain>
<displayName>_DOMAIN_</displayName>
<displayShortName>_DOMAIN_</displayShortName>
<incomingServer type="imap">
<username>%EMAILADDRESS%</username>
<hostname>_DOMAIN_</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
</incomingServer>
<incomingServer type="pop3">
<username>%EMAILADDRESS%</username>
<hostname>_DOMAIN_</hostname>
<port>995</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<pop3>
<leaveMessagesOnServer>true</leaveMessagesOnServer>
<downloadOnBiff>true</downloadOnBiff>
<daysToLeaveMessagesOnServer>14</daysToLeaveMessagesOnServer>
</pop3>
</incomingServer>
<outgoingServer type="smtp">
<username>%EMAILADDRESS%</username>
<hostname>_DOMAIN_</hostname>
<port>465</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
</outgoingServer>
</emailProvider>
<clientConfigUpdate url="https://_DOMAIN_/.well-known/autoconfig/mail/config-v1.1.xml" />
</clientConfig>
_EOL_
# sed -i "s/_DOMAIN_/${DOMAIN}/g" ${HTTPS_DOCROOT}/.well-known/autoconfig/mail/config-v1.1.xml
次の投稿では mysql と phpldapadmin をセットアップします。