Mail服务器:
基础准备:
postfix     发送 pop
dovect   imap 
postfixadmin  管理
MySQL    数据库
apache/nginx 
PHP
useradd -u 2000 -g 2000 apache
centos7

操作
一、解压MySQL
tar zxvf mysql-5.7.19

#tar zxf mysql-5.7.19-linux-glibc2.12-x86_64.tar
# tar zxf mysql-5.7.19-linux-glibc2.12-x86_64.tar.gz
# tar zxf mysql-test-5.7.19-linux-glibc2.12-x86_64.tar.gz
# mv mysql-5.7.19-linux-glibc2.12-x86_64 mysql
# mkdir basedata basedir
#useradd mysql
#vim /etc/my.cnf

[mysqld]
datadir=/data/basedata
basedir=/data/basedir
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
[mysql-client]
port = 3306
[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

# chown -R mysql.mysql basedata/
# chown -R mysql.mysql basedir/
# chown -R mysql.mysql mysql
# bin/mysqld --initialize --user=mysql --basedir=/data/basedir/ --datadir=/data/basedata/
[root@iZ88mizdnctZ mysql]# bin/mysqld_safe --user=mysql  &
2017-10-09T06:40:40.028358Z mysqld_safe Logging to '/var/log/mysqld.log'.
2017-10-09T06:40:40.049500Z mysqld_safe Starting mysqld daemon with databases from /data/basedata
2017-10-09T06:40:40.565019Z mysqld_safe mysqld from pid file /var/run/mysqld/mysqld.pid ended
[root@iZ88mizdnctZ mysql]# mkdir /var/run/mysqld
[root@iZ88mizdnctZ mysql]# chown -R mysql.mysql /var/run/mysqld/

# bin/mysql -uroot -p                                     --初始化密码
mysql>alter user 'root'@'localhost' identified by 'mysql123';
mysql>flush privileges;

# cp support-files/mysql.server /etc/init.d/mysqld

二、postfix安装
#yum -y install httpd mysql mysql-devel mysql-server php php-pecl-Fileinfo php-mcrypt php-devel php-mysql php-common php-mbstring php-gd php-imap php-ldap php-odbc php-pear php-xml php-xmlrpc pcre pcre-devel

#vim /etc/httpd/conf/httpd.conf

AddType application/x-httpd-php .php
PHPIniDir "/etc/php.ini"

DirectoryIndex index.php index.html index.html.var

# vim /var/www/html/index.php 
<?php
                phpinfo();
?>

#tar -zxvf postfixadmin-3.1.tar.gz  -C /var/www/html/
#mv postfixadmin-3.1/ postfixadmin
# vim config.inc.php
$CONF['configured'] = true;
$CONF['database_type'] = 'mysql';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfix';
$CONF['database_password'] = 'postfix';
$CONF['database_name'] = 'postfix';
$CONF['admin_email'] = 'postmaster@free.com';
$CONF['encrypt'] = 'dovecot:CRAM-MD5';
$CONF['dovecotpw'] = "/usr/bin/doveadm pw";
$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['aliases'] = '1000';
$CONF['mailboxes'] = '1000';
$CONF['maxquota'] = '1000';
$CONF['fetchmail'] = 'NO';
$CONF['quota'] = 'YES';
$CONF['used_quotas'] = 'YES';
$CONF['new_quota_table'] = 'YES';

mysql> create database postfix;
Query OK, 1 row affected (0.00 sec)

mysql> create user postfix@localhost identified by 'postfixadmin';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on postfix.* to postfix@localhost identified by 'postfixadmin';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

mysql> commit
        -> ;
Query OK, 0 rows affected (0.00 sec)

dns配置
# vim /etc/named.conf
options {
    listen-on port 53 { 172.24.17.62; };
    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";
    allow-query     { any; };
    recursion yes;
    dnssec-enable yes;
    dnssec-validation yes;
    dnssec-lookaside auto;
    /* Path to ISC DLV key */
    bindkeys-file "/etc/named.iscdlv.key";
};
logging {
                channel default_debug {
                                file "data/named.run";
                                severity dynamic;
                };
};
zone "." IN {
    type hint;
    file "named.ca";
};
include "/etc/named.rfc1912.zones"

#vim /etc/named.rfc1912.zones
zone "dahan.org" IN {
    type master;
    file "dahan.org.zone";
    allow-update { none; };
};

# vim /var/named/dahan.org.zone
$TTL 1D
@   IN SOA  dahan.org. root.dahan.org. (
                    0   ; serial
                    1D  ; refresh
                    1H  ; retry
                    1W  ; expire
                    3H )    ; minimum
    IN  NS  ns.dahan.org.
    A   127.0.0.1
    AAAA    ::1
postfix IN  A   172.24.17.62
ns  IN  A   172.24.17.62
Z88a04bir7Z IN  A   172.24.17.62

postfixadmin配置

#tar -zxvf postfixadmin-3.1.tar.gz -C /var/www/html/
#cd /var/www/html
#mv postfixadmin-3.1 postfixadmin
# ll
total 32
-rwxr-xr-x  1 root   root   23834 Oct 11 15:23 config.inc.php
-rwxrwxr-x  1     48     48    20 Oct 11 14:55 index.php
drwxrwxr-x 16 apache apache  4096 Oct 13 11:06 postfixadmin

#vim /var/www/html/postfixadmin/config.inc.php
<?php
/** 
 * Postfix Admin 
 * 
 * LICENSE 
 * This source file is subject to the GPL license that is bundled with  
 * this package in the file LICENSE.TXT. 
 * 
 * 
 * @version $Id$ 
 * @license GNU GPL v2 or later. 
 * 
 * File: config.inc.php
 * Contains configuration options.
 */
/*****************************************************************
 *  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
 * You have to set $CONF['configured'] = true; before the
 * application will run!
 * Doing this implies you have changed this file as required.
 * i.e. configuring database etc; specifying setup.php password etc.
 */
$CONF['configured'] = true;
$CONF['setup_password'] = '{CRAM-MD5}f41aeb7fceda68c8d8ceaf23af5436253c60edddb62e6fb8ccec9b34c44be4e1';
$CONF['default_language'] = 'en';
$CONF['language_hook'] = '';
/*
        language_hook example function

        Called if $CONF['language_hook'] == '<name_of_the_function>'
        Allows to add or override $PALANG interface texts.
        If you add new texts, please always prefix them with 'x_' (for example 
        $PALANG['x_mytext'] = 'foo') to avoid they clash with texts that might be
        added to languages/*.lang in future versions of PostfixAdmin.
        Please also make sure that all your added texts are included in all
        sections - that includes all 'case "XY":' sections and the 'default:'
        section (for users that don't have any of the languages specified
        in the 'case "XY":' section). 
        Usually the 'default:' section should contain english text.
        If you modify an existing text/translation, please consider to report it
        can benefit from the corrected text/translation.
        Returns: modified $PALANG array
*/
/*
function language_hook($PALANG, $language) {
        switch ($language) {
                case "de":
                        $PALANG['x_whatever'] = 'foo';
                        break;
                case "fr":
                        $PALANG['x_whatever'] = 'bar';
                        break;
                default:
                        $PALANG['x_whatever'] = 'foobar';
        }
        return $PALANG;
}
*/
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfix';
$CONF['database_password'] = 'postfixadmin';
$CONF['database_name'] = 'postfix';
$CONF['database_prefix'] = '';
$CONF['database_tables'] = array (
        'admin' => 'admin',
        'alias' => 'alias',
        'alias_domain' => 'alias_domain',
        'config' => 'config',
        'domain' => 'domain',
        'domain_admins' => 'domain_admins',
        'fetchmail' => 'fetchmail',
        'log' => 'log',
        'mailbox' => 'mailbox',
        'vacation' => 'vacation',
        'vacation_notification' => 'vacation_notification',
        'quota' => 'quota',
    'quota2' => 'quota2',
);
$CONF['admin_email'] = 'postfix@dahan.org';
$CONF['smtp_server'] = 'localhost';
$CONF['smtp_port'] = '25';
$CONF['smtp_client'] = '';
$CONF['encrypt'] = 'dovecot:CRAM-MD5';
$CONF['authlib_default_flavor'] = 'md5raw';
$CONF['dovecotpw'] = "/usr/bin/doveadm pw";
if(file_exists('/usr/bin/doveadm')) {
        $CONF['dovecotpw'] = "/usr/bin/doveadm pw"; # debian
}
$CONF['password_validation'] = array(
        '/.{5}/'                => 'password_too_short 5',      # minimum length 5 characters
        '/([a-zA-Z].*){3}/'     => 'password_no_characters 3',  # must contain at least 3 characters
        '/([0-9].*){2}/'        => 'password_no_digits 2',      # must contain at least 2 digits
);
$CONF['generate_password'] = 'NO';
$CONF['show_password'] = 'NO';
$CONF['page_size'] = '10';
$CONF['default_aliases'] = array (
        'abuse' => 'abuse@change-this-to-your.domain.tld',
        'hostmaster' => 'hostmaster@change-this-to-your.domain.tld',
        'postmaster' => 'postmaster@change-this-to-your.domain.tld',
        'webmaster' => 'webmaster@change-this-to-your.domain.tld'
);
$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['maildir_name_hook'] = 'NO';
/*
        maildir_name_hook example function

        Called when creating a mailbox if $CONF['maildir_name_hook'] == '<name_of_the_function>'
        - allows for customized maildir paths determined by a custom function
        - the example below will prepend a single-character directory to the
            beginning of the maildir, splitting domains more or less evenly over
            36 directories for improved filesystem performance with large numbers
            of domains.
        Returns: maildir path
        ie. I/example.com/user/
*/
/*
function maildir_name_hook($domain, $user) {
        $chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        $dir_index = hexdec(substr(md5($domain), 28)) % strlen($chars);
        $dir = substr($chars, $dir_index, 1);
        return sprintf("%s/%s/%s/", $dir, $domain, $user);
}
*/
/*  
        *_struct_hook - change, add or remove fields
        If you need additional fields or want to change or remove existing fields,
        you can write a hook function to modify $struct in the *Handler classes. 
        The edit form will automatically be updated according to the modified
        $struct. The list page is not yet updated automatically.
        You can define one hook function per class, named like the primary database
        table of that class.
        The hook function is called with $struct as parameter and must return the
        modified $struct. 
        Note: Adding a field to $struct adds the handling of this field in
        PostfixAdmin, but it does not create it in the database. You have to do
        that yourself. 
        Please follow the naming policy for custom database fields and tables on
        to avoid clashes with future versions of PostfixAdmin.
        See initStruct() in the *Handler class for the default $struct.
        See pacol() in functions.inc.php for the available flags on each column.

        Example:
        function x_struct_admin_modify($struct) {
                $struct['superadmin']['editable'] = 0;          # make the 'superadmin' flag read-only
                $struct['superadmin']['display_in_form'] = 0;   # don't display the 'superadmin' flag in edit form
                $struct['x_newfield'] = pacol( [...] );        # additional field 'x_newfield'
                return $struct; # important!
        }
        $CONF['admin_struct_hook'] = 'x_struct_admin_modify';
*/
$CONF['admin_struct_hook']          = '';
$CONF['domain_struct_hook']         = '';
$CONF['alias_struct_hook']          = '';
$CONF['mailbox_struct_hook']        = '';
$CONF['alias_domain_struct_hook']   = '';
$CONF['fetchmail_struct_hook']      = '';
$CONF['aliases'] = '1000';
$CONF['mailboxes'] = '1000';
$CONF['maxquota'] = '1000';
$CONF['domain_quota_default'] = '2048';
$CONF['quota'] = 'YES';
$CONF['domain_quota'] = 'YES';
$CONF['quota_multiplier'] = '1024000';
$CONF['transport'] = 'NO';
$CONF['transport_options'] = array (
);
$CONF['transport_default'] = 'virtual';
$CONF['vacation'] = 'NO';
$CONF['vacation_domain'] = 'autoreply.change-this-to-your.domain.tld';
$CONF['vacation_control'] ='YES';
$CONF['vacation_control_admin'] = 'YES';
$CONF['vacation_choice_of_reply'] = array (
     # considered annoying - only send a reply on every mail if you really need it
);
$CONF['alias_control'] = 'YES';
$CONF['alias_control_admin'] = 'YES';
$CONF['special_alias_control'] = 'NO';
$CONF['alias_goto_limit'] = '0';
$CONF['alias_domain'] = 'YES';
$CONF['backup'] = 'NO';
$CONF['sendmail'] = 'YES';
$CONF['sendmail_all_admins'] = 'NO';
$CONF['logging'] = 'YES';
$CONF['fetchmail'] = 'NO';
$CONF['fetchmail_extra_options'] = 'NO';
$CONF['show_header_text'] = 'NO';
$CONF['header_text'] = ':: Postfix Admin ::';
$CONF['show_footer_text'] = 'YES';
$CONF['footer_text'] = 'Return to change-this-to-your.domain.tld';
$CONF['motd_user'] = '';
$CONF['motd_admin'] = '';
$CONF['motd_superadmin'] = '';
$CONF['welcome_text'] = <<<EOM
Hi,
Welcome to your new account.
EOM;
$CONF['emailcheck_resolve_domain']='YES';
$CONF['show_status']='YES';
$CONF['show_status_key']='YES';
$CONF['show_status_text']='  ';
$CONF['show_undeliverable']='YES';
$CONF['show_undeliverable_color']='tomato';
$CONF['show_undeliverable_exceptions']=array("unixmail.domain.ext","exchangeserver.domain.ext");
$CONF['show_popimap']='YES';
$CONF['show_popimap_color']='darkgrey';
$CONF['show_custom_domains']=array("subdomain.domain.ext","domain2.ext");
$CONF['show_custom_colors']=array("lightgreen","lightblue");
$CONF['recipient_delimiter'] = "";
$CONF['mailbox_postcreation_script'] = '';
$CONF['mailbox_postedit_script'] = '';
$CONF['mailbox_postdeletion_script'] = '';
$CONF['domain_postcreation_script'] = '';
$CONF['domain_postdeletion_script'] = '';
$CONF['create_mailbox_subdirs'] = array();
$CONF['create_mailbox_subdirs_host']='localhost';
$CONF['create_mailbox_subdirs_prefix']='INBOX.';
$CONF['used_quotas'] = 'YES';
$CONF['new_quota_table'] = 'YES';
$CONF['create_mailbox_subdirs_hostoptions'] = array();
$CONF['theme_logo'] = 'images/logo-default.png';
$CONF['theme_css'] = 'css/default.css';
$CONF['theme_custom_css'] = '';
$CONF['xmlrpc_enabled'] = false;
if (file_exists(dirname(__FILE__) . '/config.local.php')) {
        require_once(dirname(__FILE__) . '/config.local.php');
}
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */

ip/setup.php
ip/login.php

postfix配置

# vim /etc/postfix/main.cf
queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
data_directory = /var/lib/postfix
mail_owner = postfix
myhostname = postfix.dahan.org
mydomain = dahan.org
myorigin = $mydomain
inet_interfaces = all
inet_protocols = ipv4
mydestination = localhost
unknown_local_recipient_reject_code = 550
mynetworks_style = host
mynetworks = 0.0.0.0/0,127.0.0.0/8
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases  
debug_peer_level = 2
debugger_command =
     PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
     ddd $daemon_directory/$process_name $process_id & sleep 5
sendmail_path = /usr/sbin/sendmail.postfix
newaliases_path = /usr/bin/newaliases.postfix
mailq_path = /usr/bin/mailq.postfix
setgid_group = postdrop
html_directory = no
manpage_directory = /usr/share/man
sample_directory = /usr/share/doc/postfix-2.6.6/samples
readme_directory = /usr/share/doc/postfix-2.6.6/README_FILES
virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql_virtual_domains_maps.cf
virtual_alias_maps = proxy:mysql:/etc/postfix/mysql_virtual_alias_maps.cf
virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf
virtual_create_maildirsize = yes
virtual_mailbox_extended = yes
virtual_mailbox_limit_maps = mysql:/etc/postfix/mysql_virtual_mailbox_limit_maps.cf
virtual_mailbox_limit_override = yes
virtual_maildir_limit_message = Sorry, this user has exceeded their disk space quota, please try again later.
virtual_overquota_bounce = yes
virtual_uid_maps = static:2000
virtual_gid_maps = static:2000
proxy_read_maps = $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $virtual_mailbox_limit_maps
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = /var/run/dovecot/auth-client
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_recipient_restrictions =  permit_mynetworks,
                                                             permit_sasl_authenticated,
                                                                reject_invalid_hostname,
                                                                reject_non_fqdn_hostname,
                                                                reject_unknown_sender_domain,
                                                                reject_non_fqdn_sender,
                                                                reject_non_fqdn_recipient,
                                                                reject_unknown_recipient_domain,
                                                                reject_unauth_pipelining,
                                                                reject_unauth_destination
virtual_transport = dovecot
dovecot_destination_recipient_limit = 1
disable_dns_lookups = yes
#vim /etc/postfix/mysql_virtual_domains_maps.cf
user = postfix
password = postfixadmin
hosts = localhost
dbname = postfix
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'

#cat /etc/postfix/mysql_virtual_alias_maps.cf |grep -v "^#"|grep -v "^$"
user = postfix
password = postfixadmin
hosts = localhost
dbname = postfix
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'

# cat /etc/postfix/mysql_virtual_mailbox_maps.cf |grep -v "^#"|grep -v "^$"
user = postfix
password = postfixadmin
hosts = localhost
dbname = postfix
query = SELECT CONCAT(domain,'/',maildir) FROM mailbox WHERE username='%s' AND active = '1'

# cat /etc/postfix/mysql_virtual_mailbox_limit_maps.cf |grep -v "^#"|grep -v "^$"
user = postfix
password = postfixadmin
hosts = localhost
dbname = postfix
query = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'
# postconf -a
cyrus
dovecot
dovecot配置
# cat /etc/dovecot/dovecot.conf |grep -v "^#"|grep -v "^$"
protocols = imap pop3
listen = *
dict {
    #quota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
    quota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
    #expire = sqlite:/etc/dovecot/dovecot-dict-sql.conf.ext
}
!include conf.d/*.conf
# cat /etc/dovecot/conf.d/10-mail.conf |grep -v "#"|grep -v "^$"
mail_location = maildir:%hMaildir
mbox_write_locks = fcntl

# cat /etc/dovecot/conf.d/10-master.conf |grep -v "#"|grep -v "^$"
service imap-login {
    inet_listener imap {
    }
    inet_listener imaps {
    }
}
service pop3-login {
    inet_listener pop3 {
    }
    inet_listener pop3s {
    }
}
service lmtp {
    unix_listener lmtp {
    }
}
service imap {
}
service pop3 {
}
service auth {
    unix_listener auth-userdb {
        mode = 0600
        user = apache
        group = apache
        }
    unix_listener auth-client {
        mode = 0600
        user = postfix
        group = postfix
    }

}
service auth-worker {
}
service dict {
    unix_listener dict {
        mode = 0600
        user = apache
        group = apache
    }
}

# cat /etc/dovecot/conf.d/15-lda.conf |grep -v "#"|grep -v "^$"
protocol lda {
    mail_plugins = quota
    postmaster_address = postmaster@dahan.org
}

# cat /etc/dovecot/conf.d/20-imap.conf |grep -v "#"|grep -v "^$"
protocol imap {
    mail_plugins = quota imap_quota
}

# cat /etc/dovecot/conf.d/20-pop3.conf |grep -v "#"|grep -v "^$"
protocol pop3 {
    pop3_uidl_format = %08Xu%08Xv
    mail_plugins = quota
}

# cat /etc/dovecot/conf.d/90-quota.conf |grep -v "#"|grep -v "^$"
plugin {
    quota_rule = *:storage=1G
}
plugin {
}
plugin {
    quota = dict:User quota::proxy::quota
}
plugin {
}

# cat /etc/dovecot/dovecot-sql.conf.ext |grep -v "#"|grep -v "^$"
driver = mysql
connect = host=localhost dbname=postfix user=postfix password=postfixadmin
default_pass_scheme = CRAM-MD5
user_query = SELECT CONCAT('/var/vmail/', maildir) AS home, 2000 AS uid, 2000 AS gid, CONCAT('*:bytes=', quota) as quota_rule FROM mailbox WHERE username = '%u' AND active='1'
password_query = SELECT username AS user, password, CONCAT('/var/vmail/', maildir) AS userdb_home, 2000 AS userdb_uid, 2000 AS userdb_gid, CONCAT('*:bytes=', quota) as userdb_quota_rule FROM mailbox WHERE username = '%u' AND active='1'

# cat /etc/dovecot/dovecot-dict-sql.conf.ext |grep -v "#"|grep -v "^$"
connect = host=localhost dbname=postfix user=postfix password=postfixadmin
map {
    pattern = priv/quota/storage
    table = quota2
    username_field = username
    value_field = bytes
}
map {
    pattern = priv/quota/messages
    table = quota2
    username_field = username
    value_field = messages
}

# service postfix start

# service dovecot start