📜 ⬆️ ⬇️

Migrating from MDaemon to Exim + Dovecot

I welcome you, residents of Habr.

Recently, a situation has arisen at work: it was necessary to raise a new mail server and transfer all current accounts and the structure of their mail folders from the old server to it. This need arose for several reasons:
  1. The number of accounts was limited to 250 accounts;
  2. Recently, the mail server began to constantly hang so that only a hard reset helped (at one time noticed that before this HDD was rather persistently used);

Here's what happened at that time (on the SuperMicro platform):

About 240 accounts were used and the Users folder (all mail here) weighed about 200 GB.

What happened (Dell PowerEdge 2850 platform)

Well, now everything is in order.
')
1. Installing FreeBSD, Exim, DoveCot, SpamAssassin
1.1. Installing FreeBSD
The choice fell on FreeBSD 9.0. No problems with installation. I downloaded an iso-image from ftp, cut it to disk and booted from it. The installation process begins (sorry, that without the screenshots).

We wait for a while, then a window appears to begin the installation of the system. Click the Install button, select the keyboard layout, encoding, or just click No. Next, select the components of interest for installation. I left everything that was chosen, except for games (why do we need games on the server, Exim is also a good toy), added ports and continued on. I made the disk layout manually, according to the following scheme:

# Device Mountpoint FStype Size /dev/mfid0p2 / ufs 20G /dev/mfid0p3 /var ufs 50G /dev/mfid0p4 /home ufs 328G /dev/mfid0p5 none swap 4G 


/ Home contains all the maildir's.

After marking we wait for the installation of the system, enter the password for root. Next, configure the network, users and reboot (there is no need for a detailed description of the installation, since all this can be found on the Internet).

After installation, respectively, setting up, installing any sudo, perl, etc.

1.2. Install Exim
I’ll focus on the Exim installation.
To begin with, we will define the tasks and necessary conditions:

1) Mailbox format - maildir;
2) SMTP authorization;
3) Support for SPF, DKIM (in the future, has not yet turned);
4) quote support (for now only in appendfile, then dovecot quota plugin);
5) MySQL support;
6) SSL support.

Start by installing MySQL 5.1.
 echo "DEFAULT_MYSQL_VER=51" >> /etc/make.conf cd /usr/ports/databases/mysql51-server make install clean #   Mysql mysql_install_db #   echo 'mysql_enable="YES"' >> /etc/rc.conf echo 'mysql_db_dir="/var/db/mysql"' >> /etc/rc.conf /usr/local/etc/rc.d/mysql start 

If everything went smoothly, the muscle will start, and it remains only to set the root password
 mysqladmin -u root password my_password 

Create a user for exim
 pw useradd exim -s /sbin/nologin -b /var/mail/mqueue -g mail 

Create a database:
 mysqladmin -u root -p create database exim_db 

DB structure:
 DROP TABLE IF EXISTS `accounts`; CREATE TABLE `accounts` ( `login` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '', `password` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '', `uid` int(11) NOT NULL DEFAULT '1002', `gid` int(11) NOT NULL DEFAULT '6', `domain` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT 'domain.ru', `quota` varchar(16) COLLATE utf8_bin NOT NULL DEFAULT '250M', `status` int(11) NOT NULL DEFAULT '1', PRIMARY KEY (`login`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; DROP TABLE IF EXISTS `aliases`; CREATE TABLE `aliases` ( `address` varchar(128) COLLATE utf8_bin DEFAULT NULL, `goto` varchar(128) COLLATE utf8_bin DEFAULT NULL, `domain` varchar(128) COLLATE utf8_bin DEFAULT 'domain.ru' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; DROP TABLE IF EXISTS `domains`; CREATE TABLE `domains` ( `domain` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '', `status` int(11) NOT NULL DEFAULT '1', PRIMARY KEY (`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 

The uid and gid fields are the UID and GID of an Exim user in our system. I turned out to be UID = 1002 and GID = 6. For convenience, we make the table fields set by default with these values.
Add Exim DB User
 mysql -u root -p Password: *** mysql> grant all privileges on exim_newdb.* to exim@localhost identified by 'exim_password'; mysql> \q 

Create a certificate for SSL connections:
 mkdir -p /etc/ssl/certs cd /etc/ssl/certs openssl req -x509 -newkey rsa:1024 -keyout favmail.pem -out favmail.pem -days 9999 -nodes 

We answer a bunch of questions, and the certificate is ready.
Preliminary steps for installing Exim have been completed; now you can proceed with its installation and configuration.
Install Exim:
 cd /usr/ports/mail/exim make install clean 

Select the options that interest us and remove unnecessary ones. I chose maildir, spf, dkim, mysql support. We are waiting for the end of the installation. Exim builds with a bunch of dependencies, but it doesn't take that long.
Turn off sendmail
 echo 'sendmail_enable="NONE"' >> /etc/rc.conf /etc/rc.d/sendmail stop 

and rule /etc/mail/mailer.conf
 cat /etc/mail/mailer.conf # $FreeBSD: release/9.0.0/etc/mail/mailer.conf 93858 2002-04-05 04:25:14Z gshapiro $ # # Execute the "real" sendmail program, named /usr/libexec/sendmail/sendmail # sendmail /usr/local/sbin/exim send-mail /usr/local/sbin/exim mailq /usr/local/sbin/exim -bp newaliases /usr/local/sbin/exim -bi hoststat /usr/local/sbin/exim purgestat /usr/local/sbin/exim 


After installation, go to edit the config. I set up a config for three different manuals, here are their sources:

Here's what happened:
 cat /usr/local/etc/exim/configure primary_hostname = domain.ru hide mysql_servers = localhost/exim_db/exim/exim_password domainlist local_domains = ${lookup mysql{select domain from domains where domain='${domain}'}} domainlist relay_to_domains = ${lookup mysql{select domain from domains where domain='${domain}'}} hostlist relay_from_hosts = localhost : 127.0.0.1 acl_smtp_rcpt = acl_check_rcpt acl_smtp_data = acl_check_data tls_certificate = /etc/ssl/certs/favmail.pem tls_privatekey = /etc/ssl/certs/favmail.pem daemon_smtp_ports = 25 : 465 tls_on_connect_ports = 465 qualify_domain = domain.ru allow_domain_literals = false exim_user = exim exim_group = mail never_users = root host_lookup = * rfc1413_hosts = * rfc1413_query_timeout = 5s ignore_bounce_errors_after = 2h timeout_frozen_after = 7d return_size_limit = 10K split_spool_directory = true syslog_timestamp = no begin acl acl_check_rcpt: acl_check_rcpt: accept hosts = : deny domains = +local_domains local_parts = ^[.] : ^.*[@%!/|] deny domains = !+local_domains local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./ accept senders=${lookup mysql{SELECT senders FROM whitelist \ WHERE senders='${quote_mysql:$sender_address}'}} deny message = HELO/EHLO required by SMTP RFC condition = ${if eq{$sender_helo_name}{}{yes}{no}} deny message = Go Away! You are spammer. condition = ${if match{$sender_host_name} \ {bezeqint\\.net|net\\.il|dialup|dsl|pool|peer|dhcp} \ {yes}{no}} deny message = rejected because $sender_host_address \ is in a black list at $dnslist_domain\n$dnslist_text hosts = !+relay_from_hosts !authenticated = * log_message = found in $dnslist_domain dnslists = bl.spamcop.net : \ cbl.abuseat.org : \ dnsbl.njabl.org : \ pbl.spamhaus.org warn set acl_m0 = 25s warn hosts = +relay_from_hosts set acl_m0 = 0s warn authenticated = * set acl_m0 = 0s warn logwrite = Delay $acl_m0 for $sender_host_name \ [$sender_host_address] with HELO=$sender_helo_name. Mail \ from $sender_address to $local_part@$domain. delay = $acl_m0 drop message = Rejected - Sender Verify Failed log_message = Rejected - Sender Verify Failed hosts = * !verify = sender/no_details/callout=2m,defer_ok !condition = ${if eq{$sender_verify_failure}{}} accept domains = +local_domains endpass message = unknown user verify = recipient accept domains = +relay_to_domains endpass message = unrouteable address verify = recipient accept hosts = +relay_from_hosts accept authenticated = * deny message = relay not permitted acl_check_data: warn message = X-Spam-Score: $spam_score ($spam_bar) hosts = !+relay_from_hosts spam = nobody:true warn message = X-Spam-Report: $spam_report hosts = !+relay_from_hosts spam = nobody:true warn message = Subject: [***SPAM***] $h_Subject: hosts = !+relay_from_hosts spam = nobody deny message = This message scored $spam_score spam points. spam = nobody:true hosts = !+relay_from_hosts condition = ${if >{$spam_score_int}{120}{1}{0}} deny message = Blacklisted file extension detected ($mime_filename) condition = ${if match \ {${lc:$mime_filename}} \ {\N(\.exe|\.pif|\.bat|\.scr|\.lnk|\.com|\.vbs|\.cpl)$\N} \ {1}{0}} accept begin routers dnslookup: driver = dnslookup domains = ! +local_domains transport = remote_smtp ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 no_more system_aliases: driver = redirect allow_fail allow_defer data = ${lookup mysql{select goto from aliases where address='${quote_mysql:$local_part}' and domain='${quote_mysql:$domain}'}} user = exim group = mail file_transport = address_file pipe_transport = address_pipe userforward: driver = redirect check_local_user no_verify no_expn check_ancestor file_transport = address_file pipe_transport = address_pipe reply_transport = address_reply data = ${lookup mysql{select goto from aliases where address='${quote_mysql:$local_part}' and domain='${quote_mysql:$domain}'}} localuser: driver = accept domains = ${lookup mysql{select domain from domains where domain='${domain}'}} local_parts = ${lookup mysql{select login from accounts where login='${local_part}' and domain='${domain}'}} transport = local_delivery cannot_route_message = Unknown user begin transports remote_smtp: driver = smtp local_delivery: driver = appendfile maildir_format maildir_tag = ,S=$message_size directory = /home/mail/$domain/$local_part create_directory delivery_date_add envelope_to_add return_path_add group = mail mode = 0660 no_mode_fail_narrower address_pipe: driver = pipe return_output address_file: driver = appendfile delivery_date_add envelope_to_add return_path_add address_reply: driver = autoreply begin retry * * F,2h,15m; G,16h,1h,1.5; F,4d,6h begin rewrite begin authenticators auth_plain: driver = plaintext server_set_id = $2 server_prompts = : public_name = PLAIN server_condition = ${lookup mysql{select login from accounts where login='${quote_mysql:${local_part:$2}}' and password='${quote_mysql:$3}'}{yes}{no}} auth_login: driver = plaintext public_name = LOGIN server_set_id = $1 server_prompts = Username:: : Password:: server_condition = ${lookup mysql{select login from accounts where login='${quote_mysql:${local_part:$1}}' and password='${quote_mysql:$2}'}{yes}{no}} auth_cram_md5: driver = cram_md5 public_name = CRAM-MD5 server_secret = ${lookup mysql{select password from accounts where login='${quote_mysql:${local_part:$1}}'}{$value}fail} server_set_id = $1 

Checking the config:
 exim -bV Exim version 4.77 #0 (FreeBSD 9.0) built 04-Jul-2012 19:16:56 Copyright (c) University of Cambridge, 1995 - 2007 Probably Berkeley DB version 1.8x (native mode) Support for: crypteq iconv() use_setclassresources PAM Perl Expand_dlfunc OpenSSL Content_Scanning DKIM Experimental_SPF Lookups (built-in): lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmnz dnsdb dsearch mysql nis nis0 passwd Authenticators: cram_md5 dovecot plaintext spa Routers: accept dnslookup ipliteral manualroute queryprogram redirect Transports: appendfile/maildir autoreply lmtp pipe smtp Fixed never_users: 0 Size of off_t: 8 Configuration file is /usr/local/etc/exim/configure 

OK, no error was detected. Rotate the logs for exim:
 crontab -u exim -e @daily /usr/local/exim/exicyclog 

We check the recognition of local users:
 exim -bt admin admin@domain.ru router = localuser, transport = local_delivery 

And non-local
 exim -bt user@www.com user@www.com router = dnslookup, transport = remote_smtp host ASPMX.L.GOOGLE.com [173.194.71.27] MX=10 host ALT2.ASPMX.L.GOOGLE.com [209.85.225.26] MX=20 host ALT1.ASPMX.L.GOOGLE.com [173.194.77.26] MX=20 host ASPMX4.GOOGLEMAIL.com [173.194.78.27] MX=30 host ASPMX5.GOOGLEMAIL.com [74.125.130.27] MX=30 host ASPMX3.GOOGLEMAIL.com [74.125.127.27] MX=30 host ASPMX2.GOOGLEMAIL.com [173.194.69.27] MX=30 

Great, you can run
 echo 'exim_enable="YES"' >> /etc/rc.conf /usr/local/etc/rc.d/exim start 

Check Exim authorization. How to do this is described in detail here .
At this point you can finish exim configuration.
1.3. Dovecot installation
We put dovecot also from ports
 cd /usr/ports/mail/dovecot make install clean 

And customizable (also configured on the above links)
 cat /usr/local/etc/dovecot.conf base_dir=/var/run/dovecot/ protocols=imap imaps pop3 pop3s ssl=yes ssl_cert_file=/etc/ssl/certs/favmail.pem ssl_key_file=/etc/ssl/certs/favmail.pem protocol imap { listen=*:143 ssl_listen=*:993 } protocol pop3 { listen=*:110 ssl_listen=*:995 } disable_plaintext_auth=no shutdown_clients=yes log_timestamp="%b %d %H:%M:%S " syslog_facility=mail login_dir=/var/run/dovecot/login login_chroot=yes login_user=dovecot login_process_size=64 login_process_per_connection=yes login_processes_count=3 login_max_processes_count=3 login_greeting=Dovecot ready login_log_format_elements=user=<%u> method=%m rip=%r lip=%l %c login_log_format=%$: %s first_valid_uid=1002 first_valid_gid=6 mail_access_groups=mail mail_debug=yes verbose_proctitle = yes mail_location=maildir:/home/mail/%d/%n protocol imap { imap_client_workarounds=delay-newmail outlook-idle netscape-eoh tb-extra-mailbox-sep } protocol pop3 { pop3_uidl_format=%08Xu%08Xv pop3_client_workarounds=outlook-no-nuls oe-ns-eoh } protocol lda { postmaster_address=admin@domain.ru auth_socket_path=/var/run/dovecot/auth-master } auth default { default_realm=domain.ru mechanisms=plain socket listen { master { path=/var/run/dovecot/auth-master mode=0600 user=exim } } passdb sql { args=/usr/local/etc/dovecot-sql.conf } userdb sql { args=/usr/local/etc/dovecot-sql.conf } user=root verbose=yes } plugin { } ####################################### cat /usr/local/etc/dovecot-sql.conf driver=mysql connect=host=127.0.0.1 dbname=exim_db user=exim password=exim_password! default_pass_scheme=PLAIN password_query=select password from accounts where login='%n' and domain='%d' user_query=select uid, gid from accounts where login='%n' and domain='%d' 

Run the daemon:
 echo 'dovecot_enable="YES"' >> /etc/rc.conf /usr/local/etc/rc.d/dovecot start 

Check POP (S), IMAP (S) telnet or any client. If everything is OK, then you can finish the configuration of Dovecot.

1.4. Install SpamAssassin and configure Exim for it
SpamAssassin can be installed in two ways:

1) perl -MCPAN -e shell;
2) From ports.

I set out the ports. SpamAssassin is a barley module, and installing it requires a bunch of dependencies from the same barley modules. Install them:
 cd /usr/ports/security/p5-Digest-MD5 make install clean cd /usr/ports/www/p5-HTML-Parser make install clean cd /usr/ports/dns/p5-Net-DNS make install clean cd /usr/ports/japanese/p5-Mail-SpamAssassin make install clean 

During the installation there will be a huge pile of issues related to the functionality of various modules.
After installation, configure SpamAssassin. Here I will give my local.cf, which I have been using for several years:
 cat /usr/local/etc/mail/spamassassin/local.cf trusted_networks 192.168.2/24 required_score 5.0 report_safe 0 rewrite_header subject [***SPAM***] use_bayes 1 bayes_path /usr/local/etc/mail/spamassassin/bayes/ bayes_file_mode 0666 bayes_min_spam_num 1 bayes_min_ham_num 1 bayes_learn_to_journal 1 skip_rbl_checks 0 bayes_auto_learn 0 ok_languages ru en ok_locales ru_en score BAYES_00 0.0001 0.0001 -6.0 -6.0 score BAYES_05 0.0001 0.0001 -3.0 -3.0 score BAYES_20 0.0001 0.0001 -1.0 -1.0 score BAYES_50 0.0001 0.0001 1.6 1.6 score BAYES_60 0.0001 0.0001 2.0 2.0 score BAYES_80 0.0001 0.0001 4.0 4.0 score BAYES_95 0.0001 0.0001 6.5 6.5 score BAYES_99 0.0001 0.0001 10.0 10.0 score RDNS_NONE 0.0001 0.0001 3.0 3.0 score SUBJ_FULL_OF_8BITS 0.00 score HTML_COMMENT_8BITS 0.01 score HEADER_8BITS 0.00 score TO_NO_USER 0.01 score FORGED_MUA_OUTLOOK 0.5 score X_AUTH_WARNING 0.01 score SUBJ_HAS_UNIQ_ID 9.99 score HTTP_USERNAME_USED 9.99 score FORGED_YAHOO_RCVD 9.99 score FORGED_JUNO_RCVD 16 score UNWANTED_LANGUAGE_BODY 1.02 score MLM 5.55 score RCVD_NUMERIC_HELO 4.95 

I transferred it from my old server to MDaemon, since SpamD is a SpamAssassin stripped down to disgrace.
Now we add a line to the Exim config to acl
 spamd_address = 127.0.0.1 783 

and in ACLs in the acl_check_data rule
 acl_check_data: #deny message = Virus found ($malware_name) #malware = * warn message = X-Spam-Score: $spam_score ($spam_bar) hosts = !+relay_from_hosts spam = nobody:true warn message = X-Spam-Report: $spam_report hosts = !+relay_from_hosts spam = nobody:true warn message = Subject: [***SPAM***] $h_Subject: hosts = !+relay_from_hosts spam = nobody deny message = This message scored $spam_score spam points. spam = nobody:true hosts = !+relay_from_hosts condition = ${if >{$spam_score_int}{120}{1}{0}} accept 

Run spamd and restart exim
 echo 'spamd_enable="YES"' >> /etc/rc.conf /usr/local/etc/rc.d/sa-spamd start /usr/local/etc/rc.d/exim restart 

Is done. Now we have a new mail server and you can do the migration of users

2. Migrate users from MDaemon to Exim
2.1. Accounts and Aliases

Mdaemon stores all user accounts in DAT files that are in his app directory. In fact, this is a plain text file and it would be possible to parse it directly, but the same MDaemon can export the account to a CSV file. So, we export all accounts using the menu command: Accounts-> Export-> Export accounts to a text file separated by commas (yes, a very long name for the menu. And religion did not allow writing “Export to CSV” ). In the app directory, an Accounts.csv file will be created, so we need it.
There are accounts, now aliases. Here you have to take the dat file. He is called Alias.dat. We copy it and rule it to the state so that only aliases remain in it, and all comments are in the firebox.
To parse these files and add uchetok to the Exim database, I wore a small script on Perl, here is its contents:
 use strict; use Digest::MD5 qw(md5_hex); use DBI; my $dsn='DBI:mysql:exim_db:192.168.2.7'; my $db_user='exim'; my $db_pass='exim_password!'; my $dbh=DBI->connect($dsn, $db_user, $db_pass) or die "Cann\'t connect to DB\n"; my $acc_file="c:\\Accounts.csv"; my $alias_file="c:\\Alias.dat"; open(ACFILE, $acc_file); open(ALIASFILE, $alias_file); my @line=<ACFILE>; my @alias=<ALIASFILE>; my @arr; my @arr1; my $login; my $password; my $mquota; my $mdir; my $sq sub sql_query { my ($query)=@_; #print $query."\n"; my $sth=$dbh->prepare($query); $sth->execute; $sth->finish; } print "Adding user accounts\n"; foreach my $s(@line) { @arr=split(/,/, $s); if ($arr[0] =~ m/Email/g) { next; } $login=$arr[0]; $password=$arr[5]; $login =~ s/\"//g; $login =~ s/\@domain.ru//; if($arr[17]>0) { $mquota=$arr[17]/1000; } else { $mquota=0; } $sql=qq(INSERT INTO accounts(login, password, quota) VALUES("$login", $password, $mquota)); sql_query($sql); print $login." added OK\n"; } print "\nAdding aliases\n"; foreach my $s(@alias) { @arr1=split(/ /, $s); $arr1[2] =~ s/\n//; $arr1[0] =~ s/\@domain.ru//; $arr1[2] =~ s/\@domain.ru//; my $goto=$arr1[2]; my $address=$arr1[0]; #print $goto."\n"; $sql=qq(insert into aliases(address, goto) values('$address', '$goto')); sql_query($sql); print "Alias ".$address." => ".$goto." added OK\n"; } 

Well, almost everything. It remains to drag mail users.

2.2. Mailbox sync
To synchronize user boxes, I used the imapsync utility, which I was told about on LORa, when I raised the topic of migration there.
We put imapsync from ports
 cd /usr/ports/mail/imapsync make install clean 

Next, I wrote a couple of scripts, although it would be possible to do with one. The first script generates a csv file for imapsync, and the second launches it. Here are their contents.
 # Creating CSV file for imapsync # #!//usr/bin/perl use strict; use DBI; my $dsn="DBI:mysql:exim_db:localhost"; my $db_user="exim"; my $db_password="exim_password"; my $dbh=DBI->connect($dsn, $db_user, $db_password) or die "Cannt connect to DB"; my $f_csv="imap_users.csv"; open(CSV, ">".$f_csv); my $sql="select login, password from accounts"; my $result=$dbh->prepare($sql); $result->execute; while ((my $login, my $password)=$result->fetchrow_array) { print CSV $login.";".$password.";".$login.";".$password."\n"; } print "File $f_csv is created\n"; close(CSV); $result->finish; #------------------------------------------------------------------- # imapsync #!/usr/bin/perl my $f_csv="imap_users.csv"; my $src="192.168.2.3"; my $dst="127.0.0.1"; open(CSV, $f_csv); while(<CSV>) { chomp; my ($u1, $p1, $u2, $p2)=split(";"); #print $p1."\n"; print "Sync user $u1\n"; my $r_sync=system("imapsync --debug --host1 $src --user1 $u1 --password1 $p1 --host2 $dst --user2 $u2 --password2 $p2"); } close(CSV); 

After the launch of the latter, we wait for a long, long, long time until all 200GB migrate from the old server to the new one.

3. Results
A few hours later, I just finished this article, was synchronized. I checked several mailboxes with a variety of clients. Changes are not noticed, everything as it was, remains. All letters are in place. The only drawback is that if you use POP3, then all the letters will be supposedly unread, but it does not matter.
I hope this article will help someone. If you have any questions, ask them in the comments.

Thanks for attention.

Source: https://habr.com/ru/post/154281/


All Articles