| Both sides previous revisionPrevious revisionNext revision | Previous revision |
| computing:mailserver-trixie [2025/11/08 16:27] – oemb1905 | computing:mailserver-trixie [2026/08/31 00:07] (current) – oemb1905 |
|---|
| ------------------------------------------- | ------------------------------------------- |
| |
| Slides: [[https://nextcloud.haacksnetworking.org/index.php/s/CeyT95TRLNw4AaP|Your Email, Your Rules: Self-Hosting Simplified]] | * [[https://cloud.haacksnetworking.org/index.php/s/P255xsGX37We4BS|Slides]] |
| | * [[https://content.haacksnetworking.org/w/erLgeVDEdeUf84FXq2Xk4f|SeaGL Presentation]] |
| | * [[https://tech.haacksnetworking.org/2025/06/10/your-email-your-rules-self-hosting-simplified/|Blog Post]] |
| |
| ====== Introduction ====== | ====== Introduction ====== |
| Results: [[https://www.mail-tester.com/test-8hia4koy2|Mail Tester Results]] | Results: [[https://www.mail-tester.com/test-8hia4koy2|Mail Tester Results]] |
| |
| In my case, the servers I built are on VMs that reside on a [[https://wiki.haacksnetworking.org/doku.php?id=computing:vmserver|custom virtualization stack stack]] that uses virsh and kvm/qemu on a Debian SuperMicro server (Xeon Silvers) that I co-locate at Brown Rice data center. My virtualization stack has roughly 30 VMs at present, with the host boasting over 500 virtual cores and 384GB of RAM. My primary business email server, for haacksnetworking.org, is an 8-core Virtual Machine with 8GB of RAM that resides on that same SuperMicro. Remember, though, as long as your VPS host provides PTR (reverse DNS) access, you can do this on a very basic ($5-$10 per month) VPS. My Taos server is also the proud host of the following services: | In my case, the servers I built are on VMs that reside on a [[https://wiki.haacksnetworking.org/doku.php?id=computing:vmserver|custom virtualization stack]] that uses virsh and kvm/qemu on a Debian SuperMicro server (Xeon Silvers) that I co-locate at Brown Rice data center. My virtualization stack has roughly 30 VMs at present, with the host boasting over 500 virtual cores and 384GB of RAM. My primary business email server, for haacksnetworking.org, is an 8-core Virtual Machine with 8GB of RAM that resides on that same SuperMicro. Remember, though, as long as your VPS host provides PTR (reverse DNS) access, you can do this on a very basic ($5-$10 per month) VPS. My Taos server is also the proud host of the following services: |
| |
| * **Mastodon**: [[https://gnulinux.social|GNU/Linux Social]] | * **Mastodon**: [[https://gnulinux.social|GNU/Linux Social]] |
| |
| sudo apt update && sudo apt upgrade -y | sudo apt update && sudo apt upgrade -y |
| sudo apt install mailutils postfix ufw fail2ban nginx apache2 php8.4-fpm php8.4-mysql php8.4-curl php8.4-gd php8.4-mbstring php8.4-xml php8.4-zip dovecot-core dovecot-imapd dovecot-lmtpd | sudo apt install mailutils postfix ufw fail2ban apache2 php8.4-fpm php8.4-mysql php8.4-curl php8.4-gd php8.4-mbstring php8.4-xml php8.4-zip dovecot-core dovecot-imapd dovecot-lmtpd |
| |
| It's also important that that the host knows how to identify itself properly. Let's open it up ''/etc/hosts'' and make it is setup correctly. | It's also important that that the host knows how to identify itself properly. Let's open it up ''/etc/hosts'' and make it is setup correctly. |
| |
| <code bash> | <code> |
| 127.0.0.1 localhost | 127.0.0.1 localhost |
| 127.0.1.1 mail.haacksnetworking.org haacksnetworking | 127.0.1.1 mail.haacksnetworking.org haacksnetworking |
| Make sure to create ''vhost-mail.conf'' and enable it with ''a2ensite'' for the ''mail.haacksnetworking.org'' or ''mail.domain.com'' and run the same ''certbot'' command again for Let's Encrypt to create a cert for this subdomain as well. You need to specify and create a different webroot for ''ServerName'', ''ServerAdmin'', and ''DocumentRoot'' this subdomain, just like the parent domain. Don't run the certbot commands on both domains together as it might interpret that command as being wildcards and/or nested certificates (e.g., multi-site WordPress). When you installed postfix, it automatically populated the global config file over in ''/etc/postfix/main.cf'' with the specified choices. It's now time to configure individual services for postfix (smtpd, submission, etc.), so open up ''/etc/postfix/master.cf'' and add these blocks where appropriate. You can view an actual config of mine in the link below this block. | Make sure to create ''vhost-mail.conf'' and enable it with ''a2ensite'' for the ''mail.haacksnetworking.org'' or ''mail.domain.com'' and run the same ''certbot'' command again for Let's Encrypt to create a cert for this subdomain as well. You need to specify and create a different webroot for ''ServerName'', ''ServerAdmin'', and ''DocumentRoot'' this subdomain, just like the parent domain. Don't run the certbot commands on both domains together as it might interpret that command as being wildcards and/or nested certificates (e.g., multi-site WordPress). When you installed postfix, it automatically populated the global config file over in ''/etc/postfix/main.cf'' with the specified choices. It's now time to configure individual services for postfix (smtpd, submission, etc.), so open up ''/etc/postfix/master.cf'' and add these blocks where appropriate. You can view an actual config of mine in the link below this block. |
| |
| <code bash> | <code> |
| submission inet n - y - - smtpd | submission inet n - y - - smtpd |
| -o syslog_name=postfix/submission | -o syslog_name=postfix/submission |
| The changes above add service definitions and configurations for smtps (465) and submission (587), both of which handle mail submission, or the sending of email. Examples of these configurations can be found in ''/usr/share/doc/postfix/examples/''. Submission is the newer protocol, but I retain smtps for compatibility with older clients. Once that's done, it's time to edit ''/etc/postfix/main.cf'' to account for the TLS certificate we just cut with Let's Encrypt a few steps earlier. We can additionally specify for postfix to use auth mechanisms that leverage this cert and are consistent with the services setup in master.cf. Let's open ''/etc/postfix/main.cf'' and review/add/edit the following: | The changes above add service definitions and configurations for smtps (465) and submission (587), both of which handle mail submission, or the sending of email. Examples of these configurations can be found in ''/usr/share/doc/postfix/examples/''. Submission is the newer protocol, but I retain smtps for compatibility with older clients. Once that's done, it's time to edit ''/etc/postfix/main.cf'' to account for the TLS certificate we just cut with Let's Encrypt a few steps earlier. We can additionally specify for postfix to use auth mechanisms that leverage this cert and are consistent with the services setup in master.cf. Let's open ''/etc/postfix/main.cf'' and review/add/edit the following: |
| |
| <code bash> | <code> |
| # A) Leave the following upper-block defaults | # A) Leave the following upper-block defaults |
| smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) | smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) |
| |
| # F) Primary Block; additional services go below or in dedicated config | # F) Primary Block; additional services go below or in dedicated config |
| smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination | smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination |
| myhostname = mail.haacksnetworking.org | myhostname = mail.haacksnetworking.org |
| alias_maps = hash:/etc/aliases | alias_maps = hash:/etc/aliases |
| inet_protocols = all | inet_protocols = all |
| message_size_limit = 52428800 | message_size_limit = 52428800 |
| | cyrus_sasl_config_path = /etc/postfix/sasl #now default since early 2026 |
| </code> | </code> |
| |
| In order for dovecot to work, we need to specify which protocols we allow, the mail directory location, ensure that dovecot is a member of the mail group, and enable lmtp the for delivery agent. Lmtp is a local delivery agent and hands off processed incoming emails to the storage directories without requiring external authentication. This ensures everything is delivered locally, or within the hardened host. Larger distributed email systems require smtp for network hand offs between servers, but that's not required or helpful for mail server self-hosters. Open up ''/etc/dovecot/dovecot.conf'' and specify the protocols. It is also required to specfify the storage and config versions (updated for Trixie): | In order for dovecot to work, we need to specify which protocols we allow, the mail directory location, ensure that dovecot is a member of the mail group, and enable lmtp the for delivery agent. Lmtp is a local delivery agent and hands off processed incoming emails to the storage directories without requiring external authentication. This ensures everything is delivered locally, or within the hardened host. Larger distributed email systems require smtp for network hand offs between servers, but that's not required or helpful for mail server self-hosters. Open up ''/etc/dovecot/dovecot.conf'' and specify the protocols. It is also required to specfify the storage and config versions (updated for Trixie): |
| |
| <code bash> | <code> |
| protocols = imap lmtpdovecot_storage_version = 2.4.1 | protocols = imap lmtp |
| dovecot_config_version = 2.4 | dovecot_storage_version = 2.4.1 |
| | dovecot_config_version = 2.4.1 |
| # If you upgraded from Bookworm, comment out dictionary | # If you upgraded from Bookworm, comment out dictionary |
| # dict { | # dict { |
| |
| |
| <code bash> | <code> |
| mail_driver = maildirmail_path = ~/Maildirmail_inbox_path = ~/Maildir/.INBOX | mail_driver = maildir |
| | mail_path = ~/Maildir |
| | mail_inbox_path = ~/Maildir/.INBOX |
| </code> | </code> |
| |
| Make sure that the dovecot user and the simple UNIX users are both part of the mail group. Remember, this tutorial does not use virtual users and/or an associated database. This tutorial uses simple UNIX user names which means the yourusername listed below will have the email ''yourusername@haacksnetworking.org'', or ''yourusername@domain.com''. | Make sure that the dovecot user and the simple UNIX users are both part of the mail group. Remember, this tutorial does not use virtual users and/or an associated database. This tutorial uses simple UNIX user names which means the yourusername listed below will have the email ''yourusername@haacksnetworking.org'', or ''yourusername@domain.com''. |
| |
| sudo adduser yourusernamesudo adduser dovecot mailsudo adduser yourusername mail | sudo adduser yourusername |
| | sudo adduser dovecot mail |
| | sudo adduser yourusername mail |
| |
| It is required that lmtp be configured for use with dovecot. To do that, edit ''/etc/dovecot/conf.d/10-master.conf'': | It is required that lmtp be configured for use with dovecot. To do that, edit ''/etc/dovecot/conf.d/10-master.conf'': |
| |
| <code bash> | <code> |
| service lmtp { | service lmtp { |
| unix_listener /var/spool/postfix/private/dovecot-lmtp { | unix_listener /var/spool/postfix/private/dovecot-lmtp { |
| Postfix also needs to know the delivery agent that's being used, otherwise it won't know what service to which it should send incoming email. In short, lmtp hands off emails from postfix's mail queue to dovecot's mail storage. This is done via a socket, so that needs to be configured in the main postfix configuration. Open up ''/etc/postfix/main.cf'' and define the socket as follows. It is recommended that you also disable UTF-8 support in headers and addresses so that legacy mail servers can handle emails from your server. | Postfix also needs to know the delivery agent that's being used, otherwise it won't know what service to which it should send incoming email. In short, lmtp hands off emails from postfix's mail queue to dovecot's mail storage. This is done via a socket, so that needs to be configured in the main postfix configuration. Open up ''/etc/postfix/main.cf'' and define the socket as follows. It is recommended that you also disable UTF-8 support in headers and addresses so that legacy mail servers can handle emails from your server. |
| |
| <code bash> | <code> |
| mailbox_transport = lmtp:unix:private/dovecot-lmtpsmtputf8_enable = no | mailbox_transport = lmtp:unix:private/dovecot-lmtp |
| | smtputf8_enable = no |
| </code> | </code> |
| |
| It's now time to instruct dovecot as to what authorization mechanisms to accept. Remember, plain login, contrary to popular belief, is not in any way insecure. Remeber that they both use Base64 encoding to begin with, which is then wrapped in TLS. Therefore, as long as you enforce TLS, there's very little security concern. Adding strong passwords with 14 or more characters and regularly rotating client credentials limits the surface vector even moreso. Okay, to setup the login mechanisms, open up ''/etc/dovecot/conf.d/10-auth.conf'' and configure the following parameters (updated for Trixie): | It's now time to instruct dovecot as to what authorization mechanisms to accept. Remember, plain login, contrary to popular belief, is not in any way insecure. Remeber that they both use Base64 encoding to begin with, which is then wrapped in TLS. Therefore, as long as you enforce TLS, there's very little security concern. Adding strong passwords with 14 or more characters and regularly rotating client credentials limits the surface vector even moreso. Okay, to setup the login mechanisms, open up ''/etc/dovecot/conf.d/10-auth.conf'' and configure the following parameters (updated for Trixie): |
| |
| <code bash> | <code> |
| auth_username_format = %{user|username|lower} | auth_username_format = %{user|username|lower} |
| auth_mechanisms = plain login | auth_mechanisms = plain login |
| Now, let's enforce ssl so that the plain and login mechanisms above are wrapped - end to end - in TLS handshakes. It is also important to explicitly define the least secure and/or oldest TLS handshake your server will permit. Additionally, make sure that you comment out ssl_prefer_server_ciphers and/or anything like ''ssl_server_dh'' file, both of which are now deprecated. To do this, open up ''/etc/dovecot/conf.d/10-ssl.conf'' and edit the following parameters (updated for Trixie): | Now, let's enforce ssl so that the plain and login mechanisms above are wrapped - end to end - in TLS handshakes. It is also important to explicitly define the least secure and/or oldest TLS handshake your server will permit. Additionally, make sure that you comment out ssl_prefer_server_ciphers and/or anything like ''ssl_server_dh'' file, both of which are now deprecated. To do this, open up ''/etc/dovecot/conf.d/10-ssl.conf'' and edit the following parameters (updated for Trixie): |
| |
| <code bash> | <code> |
| ssl = required | ssl = required |
| ssl_server_cert_file = /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem | ssl_server_cert_file = /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem |
| Add SASL listener in ''/etc/dovecot/conf.d/10-master.conf'' so that postfix can query dovecot and this socket for user credentials for incoming email. Combined with the configuration ''smtpd_sasl_path=private/auth'' in ''main.cf'', this also allows postfix to authenticate smtp requests. In all cases, dovecot leverages the UNIX users for credentials. | Add SASL listener in ''/etc/dovecot/conf.d/10-master.conf'' so that postfix can query dovecot and this socket for user credentials for incoming email. Combined with the configuration ''smtpd_sasl_path=private/auth'' in ''main.cf'', this also allows postfix to authenticate smtp requests. In all cases, dovecot leverages the UNIX users for credentials. |
| |
| <code bash> | <code> |
| service auth { | service auth { |
| unix_listener /var/spool/postfix/private/auth { | unix_listener /var/spool/postfix/private/auth { |
| </code> | </code> |
| |
| Lastly, before testing, make sure that you only authorize your mynetworks and properly authenticated users. Failing to do this will mean your server could potentially be used for public relay. This block rejects any unauthenticated senders (besides localhost) and requires senders to be authenticated (or to be localhost) while only permitting incoming email directed to ''@haacksnetworking.org'' or ''@domain.com''. Please note that if you continue with the optional configurations later in this tutorial, you will integrate these stanzas into other blocks. | If you have IMAP folders with lots of email, consider adjusting the ''imap'' block in ''/etc/dovecot/conf.d/10-master.conf''. Make sure you are not editing ''imap-login'', but rather ''service imap {''. This increases virtual memory and processes for dovecot. |
| |
| <code bash> | <code> |
| smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, rejectsmtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination | service imap { |
| | vsz_limit = 4096M |
| | process_limit = 2048 |
| | } |
| | </code> |
| | |
| | Similarly, for large IMAP folders, you also want to adjust IMAP's logic to provide 256k (vs. 64k) to each single IMAP command. Additionally, make sure that you raise simultaneous connections from a single IP to 25, 50, or more, so your client can handle more in parallel. Lastly, adjust the idle time notification as well for less latency and snappier results. The default is 2mins, but if this is your server only, you can safely go as low as 15s or 30s. This lowering not only helps Roundcube, but helps Delta Chat and other email clients as well. Open up ''/etc/dovecot/conf.d/20-imap.conf'' and add the following towards the bottom: |
| | |
| | <code> |
| | protocol imap { |
| | imap_max_line_length = 256k |
| | mail_max_userip_connections = 50 |
| | imap_idle_notify_interval = 15s |
| | } |
| | </code> |
| | |
| | Lastly, before testing, make sure that you only authorize your mynetworks and properly authenticated users. Failing to do this will mean your server could potentially be used for public relay. This block rejects any unauthenticated senders (besides localhost) and requires senders to be authenticated (or to be localhost) while only permitting incoming email directed to ''@haacksnetworking.org'' or ''@domain.com''. These blocks will be added later in the tutorial, but/and I bring them up now to emphasize that you should close off the public relay. |
| | |
| | <code> |
| | smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, permit |
| | smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination |
| </code> | </code> |
| |
| At this time, the bare minimum requirements are in place for sending and receiving email. You can either fire up Thunderbird and field test it all, or use swaks, ncat, telnet, or openssl to test the TLS handshakes. Here's the ones I use in addition to field testing with Thunderbird: | At this time, the bare minimum requirements are in place for sending and receiving email. You can either fire up Thunderbird and field test it all, or use swaks, ncat, telnet, or openssl to test the TLS handshakes. Here's the ones I use in addition to field testing with Thunderbird: |
| |
| <code bash> | <code> |
| openssl s_client -connect mail.yourdomain.com:465openssl s_client -connect mail.yourdomain.com:587 | openssl s_client -connect mail.yourdomain.com:465 |
| openssl s_client -connect mail.yourdomain.com:993openssl s_client -starttls smtp -connect mail.yourdomain.com:25swaks --tls --server mail.yourdomain.com -p 587swaks --tls --server mail.yourdomain.com -p 465 | openssl s_client -connect mail.yourdomain.com:587 |
| | openssl s_client -connect mail.yourdomain.com:993 |
| | openssl s_client -starttls smtp -connect mail.yourdomain.com:25 |
| | swaks --tls --server mail.yourdomain.com -p 587 |
| | swaks --tls --server mail.yourdomain.com -p 465 |
| </code> | </code> |
| |
| As with elsewhere, this tutorial does not use any of the policies for rejecting email - everything is accepted. As your server matures and you complete testing, you can optionally harden your server's policies, but bear in mind, this means you are explicitly configuring it to reject incoming email in some cases. So, if those rules aren't perfect, then you are guaranteeing the possibility of an invalid rejection, or a false negative. In lay terms, that means you are choosing to not get emails from people that send to you. So, proceed with caution if you choose to make server-wide reject rules, either in postfix generally and/or with your policies. Personally, I don't ever do that. I want to receive any and all incoming emails. Let's start this second part by installing our spf policy with ''sudo apt install postfix-policyd-spf-python''. After it's installed, you can configure the policy daemon and socket in ''/etc/postfix/master.cf'' by adding this at the bottom of the config: | As with elsewhere, this tutorial does not use any of the policies for rejecting email - everything is accepted. As your server matures and you complete testing, you can optionally harden your server's policies, but bear in mind, this means you are explicitly configuring it to reject incoming email in some cases. So, if those rules aren't perfect, then you are guaranteeing the possibility of an invalid rejection, or a false negative. In lay terms, that means you are choosing to not get emails from people that send to you. So, proceed with caution if you choose to make server-wide reject rules, either in postfix generally and/or with your policies. Personally, I don't ever do that. I want to receive any and all incoming emails. Let's start this second part by installing our spf policy with ''sudo apt install postfix-policyd-spf-python''. After it's installed, you can configure the policy daemon and socket in ''/etc/postfix/master.cf'' by adding this at the bottom of the config: |
| |
| <code bash> | <code> |
| #spf policy\ | #spf policy\ |
| policyd-spf unix - n n - 0 spawn\ | policyd-spf unix - n n - 0 spawn |
| user=policyd-spf argv=/usr/bin/policyd-spf | user=policyd-spf argv=/usr/bin/policyd-spf |
| </code> | </code> |
| |
| After that, open up ''/etc/postfix/main.cf'' and put the spf policy together with postfix's recipient restrictions. The reason I organize the block this way is because it makes logical sense to group together the criteria under which email is received, whether because it is postfix's general policy and/or your SPF policy. | After that, open up ''/etc/postfix/main.cf'' and put the spf policy together with postfix's recipient restrictions. The reason I organize the block this way is because it makes logical sense to group together the criteria under which email is received, whether because it is postfix's general policy and/or your SPF policy. |
| |
| <code bash> | <code> |
| #spf incoming policy and recipient restrictions\ | #spf incoming policy and recipient restrictions |
| policyd-spf_time_limit = 3600\ | policyd-spf_time_limit = 3600 |
| smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policyd-spf | smtpd_recipient_restrictions = |
| | permit_mynetworks, |
| | permit_sasl_authenticated, |
| | reject_unauth_destination, |
| | reject_non_fqdn_recipient, |
| | reject_unknown_recipient_domain, |
| | check_policy_service unix:private/policyd-spf |
| </code> | </code> |
| |
| Since I just took care of recipient restrictions, it makes sense to cover sender restrictions. Remember, in the base server, both recipient and sender configurations were covered in brief. If you do these recommended and optional steps, you should expand your sender restrictions block and include other forms of checking. Some of the those options are below. Please note that similar hardening can also be done via postfix on the recipient-side as well: | Since I just took care of recipient restrictions, it makes sense to cover sender restrictions. Remember, in the base server, both recipient and sender configurations were covered in brief. If you do these recommended and optional steps, you should expand your sender restrictions block and include other forms of checking. Some of the those options are below. Please note that similar hardening can also be done via postfix on the recipient-side as well: |
| |
| <code bash> | <code> |
| smtpd_sender_restrictions = | smtpd_sender_restrictions = |
| permit_mynetworks, | permit_mynetworks, |
| permit_sasl_authenticated, | permit_sasl_authenticated, |
| #reject_unknown_reverse_client_hostname, | #reject_unknown_reverse_client_hostname, |
| #reject_unknown_client_hostname, | #reject_unknown_client_hostname, |
| #reject_unknown_sender_domain, | #reject_unknown_sender_domain, |
| reject_unauthenticated_sender_login_mismatch, | reject_unauthenticated_sender_login_mismatch, |
| reject_sender_login_mismatch, | reject_sender_login_mismatch, |
| permit | check_sender_access hash:/etc/postfix/sender_access, |
| | permit |
| </code> | </code> |
| | |
| | The sender_access file is super helpful for repeat offenders. Sure, you can also just blacklist those in sieve rules, but that means that they still arrive and fill up your Junk folder. In some cases, spammers will send 100s of the same message per day. In those cases, it is helpful to stop them at the gates with postfix's sender_access: |
| | |
| | nano /etc/postfix/sender_access |
| | <spam@domain.com REJECT> #block exact email |
| | <domain.com REJECT> #block all users @domain.com |
| | <.domain.com REJECT> #block all users @sub.domain.com |
| | sudo postmap /etc/postfix/sender_access |
| | sudo postfix reload |
| | sudo systemctl restart postfix |
| |
| Now that the SPF/recipient and PTRI am now going to set up the DKIM policy for incoming email but/and, I will also use the same package to cut a DKIM keypair for use with the server's outgoing email. First, I install the policy and add opendkim to the postfix group. | Now that the SPF/recipient and PTRI am now going to set up the DKIM policy for incoming email but/and, I will also use the same package to cut a DKIM keypair for use with the server's outgoing email. First, I install the policy and add opendkim to the postfix group. |
| |
| <code bash> | <code> |
| sudo apt install opendkim opendkim-tools | sudo apt install opendkim opendkim-tools |
| sudo adduser postfix opendkim | sudo adduser postfix opendkim |
| Once that's done, it's time to open ''/etc/opendkim.conf'' and setup the configuration for the server's keypair: | Once that's done, it's time to open ''/etc/opendkim.conf'' and setup the configuration for the server's keypair: |
| |
| <code bash> | <code> |
| Canonicalization relaxed/simple | Canonicalization relaxed/simple |
| Mode sv | Mode sv |
| It's now time to create the keys: | It's now time to create the keys: |
| |
| <code bash> | <code> |
| sudo mkdir -p /etc/opendkim/keys | sudo mkdir -p /etc/opendkim/keys |
| sudo chown -R opendkim:opendkim /etc/opendkim | sudo chown -R opendkim:opendkim /etc/opendkim |
| sudo chmod 711 /etc/opendkim/keys | sudo chmod 711 /etc/opendkim/keys |
| sudo mkdir /etc/opendkim/keys/yourdomain.com | |
| sudo opendkim-genkey -b 2048 -d yourdomain.com -D /etc/opendkim/keys/yourdomain.com -s default -v | |
| sudo chown opendkim:opendkim /etc/opendkim/keys/yourdomain.com/default.private | |
| sudo chmod 600 /etc/opendkim/keys/yourdomain.com/default.private | |
| </code> | </code> |
| |
| After creating the keys, edit the signing table so that outgoing emails can verify against the keypair. Open up ''/etc/opendkim/signing.table'' and add: | After creating the keys, edit the signing table so that outgoing emails can verify against the keypair. Open up ''/etc/opendkim/signing.table'' and add: |
| |
| <code bash> | <code> |
| '*@yourdomain.com default._domainkey.yourdomain.com' | *@yourdomain.com default._domainkey.yourdomain.com |
| '*@*.yourdomain.com default._domainkey.yourdomain.com' | *@*.yourdomain.com default._domainkey.yourdomain.com |
| </code> | </code> |
| |
| default._domainkey.yourdomain.com yourdomain.com:default:/etc/opendkim/keys/yourdomain.com/default.private | default._domainkey.yourdomain.com yourdomain.com:default:/etc/opendkim/keys/yourdomain.com/default.private |
| |
| Lastly, instruct you server to trust only localhost and your domain. Open up /etc/opendkim/trusted.hosts and enter: | Lastly, instruct you server to trust only localhost and your domain. Open up ''/etc/opendkim/trusted.hosts'' and enter: |
| |
| <code bash> | <code> |
| 127.0.0.1\ | 127.0.0.1\ |
| localhost\ | localhost\ |
| </code> | </code> |
| |
| Use the built-in tool to test your DKIM keypair: | Now, cut the keys: |
| |
| sudo opendkim-testkey -d yourdomain.com -s default -vvv | sudo mkdir /etc/opendkim/keys/example.com |
| | sudo opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s default -v |
| | sudo chown opendkim:opendkim /etc/opendkim/keys/example.com/default.private |
| | sudo chmod 600 /etc/opendkim/keys/example.com/default.private |
| |
| It's now time to build your last DNS record. To do that, you need your public key you created above. To view that on the CLI, run the cat command below. You then take that output to your DNS host of choice, and create a TXT record containing this value. In the subdomain field, you enter your selector, i.e., default._domainkey and in the target, you enter the output of the command below. | It's now time to build your last DNS record. To do that, you need your public key you created above. To view that on the CLI, run the cat command below. You then take that output to your DNS host of choice, and create a TXT record containing this value. In the subdomain field, you enter your selector, i.e., default._domainkey and in the target, you enter the output of the command below. |
| |
| sudo cat /etc/opendkim/keys/yourdomain.com/default.txt | sudo cat /etc/opendkim/keys/yourdomain.com/default.txt |
| | |
| | Once your DNS record for DKIM is created, use the built-in tool to test the record: |
| | |
| | sudo opendkim-testkey -d yourdomain.com -s default -vvv |
| |
| Okay, now that your keypair for smtp has been created and DNS updated, it's a good time to create the server's DKIM policy. First, you need to give postfix access to opendkim's tooling: | Okay, now that your keypair for smtp has been created and DNS updated, it's a good time to create the server's DKIM policy. First, you need to give postfix access to opendkim's tooling: |
| Now that the server's DKIM policy is configured, it's required to let postfix know of the policy, where the socket is, and how to leverage the policy (reject/accept). Please note that the other policies will add to and/or expand this block, notably the smtpd_milters stanza, which will expand with each additional policy and associated socket that gets added. Open up the main postfix configuration in ''/etc/postfix/main.cf'' and enter the following: | Now that the server's DKIM policy is configured, it's required to let postfix know of the policy, where the socket is, and how to leverage the policy (reject/accept). Please note that the other policies will add to and/or expand this block, notably the smtpd_milters stanza, which will expand with each additional policy and associated socket that gets added. Open up the main postfix configuration in ''/etc/postfix/main.cf'' and enter the following: |
| |
| <code bash> | <code> |
| milter_default_action = accept | milter_default_action = accept |
| milter_protocol = 6 | milter_protocol = 6 |
| </code> | </code> |
| |
| The most important line is the uppermost line, which specifies that email should not be rejected as a result of leveraging the policy. Again, this ensures that spam assassin has what it needs to help users sort and organize email, without the possibility of phantom rejections, prohibiting email from having ever arrived in your inbox. After your DKIM keypair and DKIM policy are setup, you can setup a DMARC policy as well. Install the policy with sudo apt install opendmarc. After it installs, open up ''/etc/opendmarc.conf'' and enter the following: | The most important line is the uppermost line, which specifies that email should not be rejected as a result of leveraging the policy. Again, this ensures that spam assassin has what it needs to help users sort and organize email, without the possibility of phantom rejections, prohibiting email from having ever arrived in your inbox. After your DKIM keypair and DKIM policy are setup, you can setup a DMARC policy as well. Install the policy with ''sudo apt install opendmarc''. After it installs, open up ''/etc/opendmarc.conf'' and enter the following: |
| |
| <code bash> | <code> |
| AuthservID OpenDMARC | AuthservID OpenDMARC |
| TrustedAuthservIDs mail.yourdomain.com | TrustedAuthservIDs mail.yourdomain.com |
| Similarly to the DKIM policy, we need to give postfix permissions to the DMARC tooling and socket. It's also required to add opendmarc to the postfix group: | Similarly to the DKIM policy, we need to give postfix permissions to the DMARC tooling and socket. It's also required to add opendmarc to the postfix group: |
| |
| <code bash> | <code> |
| sudo mkdir -p /var/spool/postfix/opendmarc | sudo mkdir -p /var/spool/postfix/opendmarc |
| sudo chown opendmarc:opendmarc /var/spool/postfix/opendmarc -R | sudo chown opendmarc:opendmarc /var/spool/postfix/opendmarc -R |
| Similarly to SPF and DKIM, it's essential to define the policy in the main postfix configuration file. Open up ''/etc/postfix/main.cf'' and add a block for dmarc. Expand the ''smtpd_milters'' section to include DMARC's socket. Note again how I specify accept so as to ensure proper logging for spam assassin without any possibility of phantom rejections, or email lost at the gates. | Similarly to SPF and DKIM, it's essential to define the policy in the main postfix configuration file. Open up ''/etc/postfix/main.cf'' and add a block for dmarc. Expand the ''smtpd_milters'' section to include DMARC's socket. Note again how I specify accept so as to ensure proper logging for spam assassin without any possibility of phantom rejections, or email lost at the gates. |
| |
| <code bash> | <code> |
| #dmarc policy | #dmarc policy |
| milter_default_action = accept | milter_default_action = accept |
| First, let's add sieve to the local delivery agent over in /etc/dovecot/conf.d/15-lda.conf. This is essential for any time you are manually testing and/or processing emails on the shell, which will utilize the LDA instead of LMTP. This ensures that the local delivery agent is sieve-ready. Head over to ''/etc/dovecot/conf.d/15-lda.conf'' and edit the block: | First, let's add sieve to the local delivery agent over in /etc/dovecot/conf.d/15-lda.conf. This is essential for any time you are manually testing and/or processing emails on the shell, which will utilize the LDA instead of LMTP. This ensures that the local delivery agent is sieve-ready. Head over to ''/etc/dovecot/conf.d/15-lda.conf'' and edit the block: |
| |
| <code bash> | <code> |
| protocol lda { | protocol lda { |
| mail_plugins = $mail_plugins sieve | mail_plugins = $mail_plugins sieve |
| It is also imperative that LMTP be configured for using sieve as well. Head over to ''/etc/dovecot/conf.d/20-lmtp.conf'' and edit the block: | It is also imperative that LMTP be configured for using sieve as well. Head over to ''/etc/dovecot/conf.d/20-lmtp.conf'' and edit the block: |
| |
| <code bash> | <code> |
| protocol lmtp { | protocol lmtp { |
| mail_plugins = quota sieve | mail_plugins = quota sieve |
| As with any service, it's essential to configure postfix to use it. So, head to ''/etc/postfix/main.cf'' and add the spam assassin socket to the primary milter. Just as with DKIM and DMARC, you are expanding the existing smtpd_milters clause, not making redundant and/or duplicate stanzas. Head over to ''/etc/postfix/main.cf'' and edit the policy block we created above for DKIM, DMARC, and now spam assassin. | As with any service, it's essential to configure postfix to use it. So, head to ''/etc/postfix/main.cf'' and add the spam assassin socket to the primary milter. Just as with DKIM and DMARC, you are expanding the existing smtpd_milters clause, not making redundant and/or duplicate stanzas. Head over to ''/etc/postfix/main.cf'' and edit the policy block we created above for DKIM, DMARC, and now spam assassin. |
| |
| <code bash> | <code> |
| milter_default_action = accept | milter_default_action = accept |
| milter_protocol = 6 | milter_protocol = 6 |
| Sieve is already enabled in dovecot and postfix. That was done up above. Now, the tutorial is discussing how to leverage the sieve syntax or language to get desired user results for incoming email. The most basic way to do this is by setting up a global, or server-wide rule, that filters emails before dovecot, via lmtp, delivers the emails to their final destination. This is done by spam assassin adding custom fields and scoring to incoming email headers. The sieve plugin assesses these headers and then makes the correct determination for the final destination, which dovecot, via lmtp, carries out. In summary, spam assassin does the ranking and header-adding, sieve assesses the spam assassin scores and uses the global sieve rule to make determinations for all users, and finally dovecot+limtp handles the mail delivery. To create a global config for all users in this way, head over to ''/etc/dovecot/conf.d/90-sieve.conf'' and then add/uncomment this line: | Sieve is already enabled in dovecot and postfix. That was done up above. Now, the tutorial is discussing how to leverage the sieve syntax or language to get desired user results for incoming email. The most basic way to do this is by setting up a global, or server-wide rule, that filters emails before dovecot, via lmtp, delivers the emails to their final destination. This is done by spam assassin adding custom fields and scoring to incoming email headers. The sieve plugin assesses these headers and then makes the correct determination for the final destination, which dovecot, via lmtp, carries out. In summary, spam assassin does the ranking and header-adding, sieve assesses the spam assassin scores and uses the global sieve rule to make determinations for all users, and finally dovecot+limtp handles the mail delivery. To create a global config for all users in this way, head over to ''/etc/dovecot/conf.d/90-sieve.conf'' and then add/uncomment this line: |
| |
| sieve_before = /var/mail/SpamToJunk.sieve | <code> |
| | #sieve_before = /var/mail/SpamToJunk.sieve #old bookworm way and early Trixie |
| |
| Now, create the file that you just referenced above in ''/var/mail/SpamToJunk.sieve'' and enter the following: | #modern trixie way |
| | sieve_script SpamToJunk { |
| | type = before |
| | path = /var/mail/SpamToJunk.sieve |
| | } |
| | </code> |
| | |
| | Now, create the file that you just referenced above in ''/var/mail/SpamToJunk.sieve'' and enter the following and/or any sieve rules you would like. |
| |
| <code bash> | <code> |
| require "fileinto"; | require "fileinto"; |
| if header :contains "X-Spam-Flag" "YES" { | if header :contains "X-Spam-Flag" "YES" { |
| Load the rule by issuing the ''sudo sievec /var/mail/SpamToJunk.sieve'' command. I would also recommend restarting postfix, dovecot, and spam assassin. Now, the header called X-Spam-Flag is populated with either a Yes or No response by the spam assassin service. Spam assassin determines the yes/no score based on the scoring rules specified in the configuration file. There are many other options and far more complex rules one can establish than this basic example. This is just to get folks started. Here's an example set of rules from the ''/etc/spamassassin/local.cf'' main configuration file: | Load the rule by issuing the ''sudo sievec /var/mail/SpamToJunk.sieve'' command. I would also recommend restarting postfix, dovecot, and spam assassin. Now, the header called X-Spam-Flag is populated with either a Yes or No response by the spam assassin service. Spam assassin determines the yes/no score based on the scoring rules specified in the configuration file. There are many other options and far more complex rules one can establish than this basic example. This is just to get folks started. Here's an example set of rules from the ''/etc/spamassassin/local.cf'' main configuration file: |
| |
| <code bash> | <code> |
| report_contact webmaster@yourdomain.com | report_contact webmaster@yourdomain.com |
| required_score 5.0 | required_score 5.0 |
| report_safe 0 | report_safe 0 |
| ifplugin Mail::SpamAssassin::Plugin::Shortcircuitendif # Mail::SpamAssassin::Plugin::Shortcircuit | ifplugin Mail::SpamAssassin::Plugin::Shortcircuit |
| | endif Mail::SpamAssassin::Plugin::Shortcircuit |
| # uncomment the line below once unbound is working (optional) | # uncomment the line below once unbound is working (optional) |
| # dns_server 127.0.0.1score MISSING_FROM 5.0 | # dns_server 127.0.0.1score MISSING_FROM 5.0 |
| Download [[https://repo.haacksnetworking.org/haacknet/haackingclub/-/blob/master/configs/mailservers/spamassassin/local.cf?ref_type=heads|local.cf]]: | Download [[https://repo.haacksnetworking.org/haacknet/haackingclub/-/blob/master/configs/mailservers/spamassassin/local.cf?ref_type=heads|local.cf]]: |
| |
| This basic configuration shows you where to place the rules/scoring, etc. The names above are referred to technically as symbolic headers and I found the examples above by searching documentation and/or forum hunting. I also spent time reviewing the rules for accuracy and testing with ''spamassassin -t -D < example.eml'' regularly until assassing scored emails properly. It takes time to perfec this, so keep it permissive for starters. Make sure to check logs regularly for errors/clues using ''journalctl -u spamass-milter -u postfix -u dovecot -u opendkim -u opendmarc''. This will help you track what is and is not working for spam assassin and for you, and to thereby adjust/alter/remove scores or change points to fit your use-case and preferences. The whitelist and blacklist options can be scaled as needed and are self-explanatory. This setup is very elegant and helpful for single user email servers and/or tight-knit and close groups of family/people. As more users are needed, the ability of a one-sizefits-all rule to meet everyone's individual needs becomes more and more difficult. For this reason, I chose to install Roundcube in order to leverage the filters feature in the webgui to more easily manage spam rules. Here's how to install Roundcube and use it to manage sieve. | This basic configuration shows you where to place the rules/scoring, etc. The names above are referred to technically as symbolic headers and I found the examples above by searching documentation and/or forum hunting. I also spent time reviewing the rules for accuracy and testing with ''sudo -u username sieve-test -C -D -t - /home/username/sieve/roundcube.sieve ./spam-message.eml'' regularly until assassing scored emails properly. It takes time to perfec this, so keep it permissive for starters. Make sure to check logs regularly for errors/clues using ''journalctl -u spamass-milter -u postfix -u dovecot -u opendkim -u opendmarc''. This will help you track what is and is not working for spam assassin and for you, and to thereby adjust/alter/remove scores or change points to fit your use-case and preferences. The whitelist and blacklist options can be scaled as needed and are self-explanatory. This setup is very elegant and helpful for single user email servers and/or tight-knit and close groups of family/people. As more users are needed, the ability of a one-sizefits-all rule to meet everyone's individual needs becomes more and more difficult. For this reason, I chose to install Roundcube in order to leverage the filters feature in the webgui to more easily manage spam rules. Here's how to install Roundcube and use it to manage sieve. |
| |
| <code bash> | <code> |
| cd /var/www | cd /var/www |
| wget https://github.com/roundcube/roundcubemail/releases/download/1.6.1/roundcubemail-1.6.1-complete.tar.gz | wget https://github.com/roundcube/roundcubemail/releases/download/1.6.1/roundcubemail-1.6.1-complete.tar.gz |
| Obviously, go check the git repo and make sure to download the latest stable version. Roundcube requires a database, so let's set that up: | Obviously, go check the git repo and make sure to download the latest stable version. Roundcube requires a database, so let's set that up: |
| |
| <code bash> | <code> |
| sudo mysql -u root | sudo mysql -u root |
| CREATE DATABASE roundcube DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; | CREATE DATABASE roundcube DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; |
| Earlier in the tutorial, you were instructed to setup a vhost for ''mail.haacksnetworking.org'' / ''mail.domain.com''. You already created vhost-mail.conf for the purposes of having an associated A record and website destination, which is helpful for complete validation of email's record health. Now, you are going leverage this certificate for the purpose of encrypting the apache2 front-end, or reverse proxy. Apache receives the public requests, and then the new vhost config, which enables the reverse proxy, forwards those requests upstream to the roundcube service. First, replace ''vhost-mail.conf'' with something like the following: | Earlier in the tutorial, you were instructed to setup a vhost for ''mail.haacksnetworking.org'' / ''mail.domain.com''. You already created vhost-mail.conf for the purposes of having an associated A record and website destination, which is helpful for complete validation of email's record health. Now, you are going leverage this certificate for the purpose of encrypting the apache2 front-end, or reverse proxy. Apache receives the public requests, and then the new vhost config, which enables the reverse proxy, forwards those requests upstream to the roundcube service. First, replace ''vhost-mail.conf'' with something like the following: |
| |
| <code bash> | <code> |
| <VirtualHost *:80> | <VirtualHost *:80> |
| ServerName mail.domain.com | ServerName mail.domain.com |
| ServerAdmin email@email.com | ServerAdmin email@email.com |
| DocumentRoot /var/www/roundcube/ | DocumentRoot /var/www/roundcube/ #add public_html for latest RC |
| ErrorLog ${APACHE_LOG_DIR}/roundcube_error.log | ErrorLog ${APACHE_LOG_DIR}/roundcube_error.log |
| CustomLog ${APACHE_LOG_DIR}/roundcube_access.log combined | CustomLog ${APACHE_LOG_DIR}/roundcube_access.log combined |
| After that's done, edit the vhost-mail-le.conf file with something like the following: | After that's done, edit the vhost-mail-le.conf file with something like the following: |
| |
| <code bash> | <code> |
| <IfModule mod_ssl.c> | <IfModule mod_ssl.c> |
| <VirtualHost *:443> | <VirtualHost *:443> |
| ServerName mail.domain.com | ServerName mail.domain.com |
| ServerAdmin email@email.com | ServerAdmin email@email.com |
| DocumentRoot /var/www/roundcube/ | DocumentRoot /var/www/roundcube/ #add public_html for latest RC |
| ErrorLog ${APACHE_LOG_DIR}/roundcube_error.log | ErrorLog ${APACHE_LOG_DIR}/roundcube_error.log |
| CustomLog ${APACHE_LOG_DIR}/roundcube_access.log combined | CustomLog ${APACHE_LOG_DIR}/roundcube_access.log combined |
| Since the certificate was already created, these configs can just be dropped in to the http vhost and https vhost, respectively. These two vhosts were created earlier by you (http) and subsequently by Let's Encrypt (https). It's now time to connect Roundcube to the database that was created earlier: | Since the certificate was already created, these configs can just be dropped in to the http vhost and https vhost, respectively. These two vhosts were created earlier by you (http) and subsequently by Let's Encrypt (https). It's now time to connect Roundcube to the database that was created earlier: |
| |
| <code bash> | <code> |
| cd /var/www/roundcube/config/ | cd /var/www/roundcube/config/ |
| sudo cp config.inc.php.sample config.inc.php | sudo cp config.inc.php.sample config.inc.php |
| <$config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcube';> | <$config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcube';> |
| <$config['des_key'] = 'rcmail-!24ByteDESkey*Str';? | <$config['des_key'] = 'rcmail-!24ByteDESkey*Str';? |
| <$config['imap_host'] = 'startls://domain.com:143';> | <$config['imap_host'] = 'ssl://mail.domain.com:993';> |
| <$config['smtp_host'] = 'tls://mail.domain.com:587';> | <$config['smtp_host'] = 'tls://mail.domain.com:587';> |
| <$config['enable_spellcheck'] = true;> | <$config['enable_spellcheck'] = true;> |
| In addition to configuring the database and renaming the sample config, it's imperative to edit the plugins block at the end of the primary configuration as well. Add and/or replace the bottom plugin block as follows: | In addition to configuring the database and renaming the sample config, it's imperative to edit the plugins block at the end of the primary configuration as well. Add and/or replace the bottom plugin block as follows: |
| |
| <code bash> | <code> |
| $config['plugins'] = [ | $config['plugins'] = [ |
| 'archive', | 'archive', |
| 'debug_logger', | 'debug_logger', |
| 'emoticons', | 'emoticons', |
| 'enigma', | // 'enigma', // |
| 'filesystem_attachments', | 'filesystem_attachments', |
| 'help', | 'help', |
| At this point, you should be able to log in to mail.haacksnetworking.org / mail.domain.com using a common web browser and your credentials. Your user name is the UNIX user name, i.e., only the handle without the domain. So, my user name is jonathan, for example ... and without haacksnetworking.org at the end. Your password is whatever you adduser yourusername specified when creating your simple UNIX user. At this point, you want to open your web browser and login. Head to the Filters section under Settings. Click the cog at the top and select Edit Filter Set. In the block to the right, replace the contents with the following: | At this point, you should be able to log in to mail.haacksnetworking.org / mail.domain.com using a common web browser and your credentials. Your user name is the UNIX user name, i.e., only the handle without the domain. So, my user name is jonathan, for example ... and without haacksnetworking.org at the end. Your password is whatever you adduser yourusername specified when creating your simple UNIX user. At this point, you want to open your web browser and login. Head to the Filters section under Settings. Click the cog at the top and select Edit Filter Set. In the block to the right, replace the contents with the following: |
| |
| <code bash> | <code> |
| require ["fileinto"]; | require ["fileinto"]; |
| # rule:[whitelist] | # rule:[whitelist] |
| } | } |
| # rule:[spamcheck] | # rule:[spamcheck] |
| if anyof (header :contains "x-spam-status" "Yes", header :contains "x-spam-flag" "YES", header :contains "x-spam-level" "*****") | if anyof (header :matches "x-spam-status" "Yes", header :contains "x-spam-flag" "YES", header :contains "x-spam-level" "*****") |
| { | { |
| fileinto "Junk"; | fileinto "Junk"; |
| As you can see in the asset above, you now have access to refined sieve rules for each user. Once I decided that I needed or wanted user-level spam controls, it no longer made sense for me to manage this exclusively on the CLI. For this reason, I added Roundcube and began managing spam for email accounts individually. When Trixie came out, Dovecot's sieve implementation needed a bit of fine-tuning. First, head to ''/etc/dovecot/conf.d/90-sieve.conf'' and change the block as follows: | As you can see in the asset above, you now have access to refined sieve rules for each user. Once I decided that I needed or wanted user-level spam controls, it no longer made sense for me to manage this exclusively on the CLI. For this reason, I added Roundcube and began managing spam for email accounts individually. When Trixie came out, Dovecot's sieve implementation needed a bit of fine-tuning. First, head to ''/etc/dovecot/conf.d/90-sieve.conf'' and change the block as follows: |
| |
| <code bash> | <code> |
| # Comment out the old block: | # Comment out the old block: |
| #plugin { | #plugin { |
| active_path = ~/.dovecot.sieve | active_path = ~/.dovecot.sieve |
| } | } |
| | </code> |
| | |
| | Lastly, Roundcube is a web gui. So, it is also important that your php handler is optimized and configured with more memory and extra fpm servers. I make the following php adjustments for snappier response time. In ''/etc/php/8.4/fpm/php.ini'', input: |
| | |
| | <code> |
| | memory_limit = 1G |
| | max_execution_time = 300 |
| | max_input_time = 300 |
| | </code> |
| | |
| | And, finally, in ''/etc/php/8.4/fpm/pool.d/www.conf'', I adjust the servers to help account for simultaneous connections: |
| | |
| | <code> |
| | pm = dynamic |
| | pm.max_children = 200 |
| | pm.start_servers = 20 |
| | pm.min_spare_servers = 10 |
| | pm.max_spare_servers = 20 |
| | pm.max_requests = 500 |
| | request_terminate_timeout = 0 |
| </code> | </code> |
| |
| ====== Part V - Setting up pflogsumm ====== | ====== Part V - Setting up pflogsumm ====== |
| |
| It's important to be able to monitor how your setup is performing and what is or is not working correctly. No better way to do that than to get some analytics emailed to you each day. To do that, let's install pflogsumm and use rsyslog for logging. Install the packages sudo apt install pflogsumm rsyslog and then create the log rotation rule over in ''/etc/logrotate.d/postfix-log'' and enter the following: | It's important to be able to monitor how your setup is performing and what is or is not working correctly. No better way to do that than to get some analytics emailed to you each day. To do that, let's install pflogsumm and use rsyslog for logging. Install the packages ''sudo apt install pflogsumm'' rsyslog and then create the log rotation rule over in ''/etc/logrotate.d/postfix-log'' and enter the following: |
| |
| <code bash> | <code> |
| /var/log/mail.log { | /var/log/mail.log { |
| missingok | missingok |
| You need to make sure to comment out or remove #mail.log from ''/etc/logrotate.d/rsyslog''. This is done so as to replace the default rotation. Now, I will create a script to run pflogsumm on the archives. Create ''/usr/local/bin/pflog-run.sh'' or something similar and enter the following: | You need to make sure to comment out or remove #mail.log from ''/etc/logrotate.d/rsyslog''. This is done so as to replace the default rotation. Now, I will create a script to run pflogsumm on the archives. Create ''/usr/local/bin/pflog-run.sh'' or something similar and enter the following: |
| |
| <code bash> | <code> |
| #!/bin/sh | #!/bin/sh |
| #/usr/sbin/logrotate -f /etc/logrotate.d/postfix-log | #/usr/sbin/logrotate -f /etc/logrotate.d/postfix-log |
| Some of the spam assassin tooling that uses RBL will not work unless you use your own recursive DNS instead of, for example, ''8.8.8.8''. It should also be noted that using your own DNS is better from both a speed and privacy perspective, so setting this up kills two birds with one proverbial stone. I have a separate tutorial about this on my blog and it has an associated wiki entry as well. As you review these tutorials, please remember that unbound can be used for both LAN and WAN settings. For this tutorial, an email server, one would use the WAN configuration. Here's a concise recap of how to set that up, which you will do directly on the email server. First, let's install unbound sudo apt install unbound, by far the most hassle free way to do recursive DNS. Once it is up and running, it's time to put a WAN-based config in place in ''/etc/unbound/unbound.conf''. In that file, add the following: | Some of the spam assassin tooling that uses RBL will not work unless you use your own recursive DNS instead of, for example, ''8.8.8.8''. It should also be noted that using your own DNS is better from both a speed and privacy perspective, so setting this up kills two birds with one proverbial stone. I have a separate tutorial about this on my blog and it has an associated wiki entry as well. As you review these tutorials, please remember that unbound can be used for both LAN and WAN settings. For this tutorial, an email server, one would use the WAN configuration. Here's a concise recap of how to set that up, which you will do directly on the email server. First, let's install unbound sudo apt install unbound, by far the most hassle free way to do recursive DNS. Once it is up and running, it's time to put a WAN-based config in place in ''/etc/unbound/unbound.conf''. In that file, add the following: |
| |
| <code bash> | <code> |
| server: | server: |
| # Bind to localhost only | # Bind to localhost only |
| Next, open up ''/etc/logrotate.d/unbound'' and enter the rotation logic: | Next, open up ''/etc/logrotate.d/unbound'' and enter the rotation logic: |
| |
| <code bash> | <code> |
| /var/log/unbound/unbound.log { | /var/log/unbound/unbound.log { |
| daily | daily |
| Next, disable systemd's resolver: | Next, disable systemd's resolver: |
| |
| <code bash> | <code> |
| systemctl disable --now unbound-resolvconf.service | systemctl disable --now unbound-resolvconf.service |
| sed -Ei 's/^unbound_conf=/#unbound_conf=/' /etc/resolvconf.conf | sed -Ei 's/^unbound_conf=/#unbound_conf=/' /etc/resolvconf.conf |
| In the .xml file, put the following, adjusting for ''domain.com'': | In the .xml file, put the following, adjusting for ''domain.com'': |
| |
| <code bash> | <code> |
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> |
| <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006"> | <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006"> |
| The options below are results of small things that came up while using my own server over the last 5 years or so. First, I noticed that clients would not set up the standard directories and it turns out you need to tell dovevot to do that over in ''/etc/dovecot/conf.d/15-mailboxes.conf'' by enabling the ''auto = create'' in the folder blocks for which you desire auto-population. | The options below are results of small things that came up while using my own server over the last 5 years or so. First, I noticed that clients would not set up the standard directories and it turns out you need to tell dovevot to do that over in ''/etc/dovecot/conf.d/15-mailboxes.conf'' by enabling the ''auto = create'' in the folder blocks for which you desire auto-population. |
| |
| <code bash> | <code> |
| mailbox Drafts { | mailbox Drafts { |
| auto = create | auto = create |
| |
| Once you edited the file, load the changes with sudo postmap /etc/postfix/virtual_alias. | Once you edited the file, load the changes with sudo postmap /etc/postfix/virtual_alias. |
| Another thing I researched when reviewing Linux Babe's tutorial, but ultimately rejected doing was body and header inspection. To do that, install postfix's regular expression tooling with sudo apt install postfix-pcre and then edit the main configuration ''/etc/postfix/main.cf'' and enter the following stanzas: | Another thing I researched when reviewing Linux Babe's tutorial, but ultimately rejected doing was body and header inspection. To do that, install postfix's regular expression tooling with ''sudo apt install postfix-pcre'' and then edit the main configuration ''/etc/postfix/main.cf'' and enter the following stanzas: |
| |
| header_checks = pcre:/etc/postfix/header_checks | header_checks = pcre:/etc/postfix/header_checks |
| sudo postmap /etc/postfix/body_checks | sudo postmap /etc/postfix/body_checks |
| |
| Before, during, and after the creation of this email server tutorial, I've had a need to use messaging/chat apps. I've used them all, whether Signal, Telegram, Nextcloud Talk, and loads of more boutique and experimental platforms. After years of debate with friends and colleagues, a friend suggested [[https://delta.chat/en/download|Delta Chat]], a chat app that - wait for it - uses email servers for chatting. Given my email server was already set up and purring, I gave it a try and I've used it since for family and business conversations, that is, small and trusted audiences. If you get through this tutorial, it's worth giving it a try! Just edit ''/etc/dovecot/conf.d/20-imap.conf'' and ensure the ''imap_idle_notify_interval = 1min'' idle notify interval is 1 or 2 mins. For small use cases, increasing this frequency will harm nothing and improve the snappiness of the Delta Chat experience. Everything else is already perfectly compatible with Delta Chat. Just export and save your keys! | Before, during, and after the creation of this email server tutorial, I've had a need to use messaging/chat apps. I've used them all, whether Signal, Telegram, Nextcloud Talk, and loads of more boutique and experimental platforms. After years of debate with friends and colleagues, a friend suggested [[https://delta.chat/en/download|Delta Chat]], a chat app that - wait for it - uses email servers for chatting. Given my email server was already set up and purring, I gave it a try and I've used it since for family and business conversations, that is, small and trusted audiences. All of the settings in this tutorial work perfectly for Delta Chat's classic email server option. The only setting you might want to tinker with if you use the server for Delta Chat is the ''imap_idle_notify_interval'' in ''/etc/dovecot/conf.d/20-imap.conf'' which I covered above. |
| | |
| | -- -- -- -- Miscellaneous Issues -- -- -- -- |
| | |
| | To test sieve's logic and/or to test spamassassin's scoring, download email (.eml) from Roundcube that did not behave as planned. Once you have the email, ''ssh'' into your mail server's simple unix user's mail directory and do the following: |
| | |
| | su - username |
| | cd ~/sieve |
| | spamassassin -t -D < Amazon1.eml |
| | # this ^^ gives you the headers / spamassassin scores in stdout |
| | sieve-test -D -e roundcube.sieve Amazon3.eml |
| | # this ^^ tests your sieve logic to see if the email goes in ham or spam |
| | |
| | In the output that follows, look for something like the below. I put example output for each command above, respectively: |
| | |
| | <code> |
| | Content analysis details: (16.9 points, 5.0 required) |
| | |
| | pts rule name description |
| | ---- ---------------------- -------------------------------------------------- |
| | 0.8 DKIM_ADSP_ALL No valid author signature, domain signs all mail |
| | 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record |
| | -0.0 SPF_PASS SPF: sender matches SPF record |
| | 0.2 BAYES_999 BODY: Bayes spam probability is 99.9 to 100% |
| | [score: 1.0000] |
| | 3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100% |
| | [score: 1.0000] |
| | 2.0 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail |
| | domains are different |
| | 2.1 HTML_IMAGE_ONLY_12 BODY: HTML: images with 800-1200 bytes of words |
| | 0.0 HTML_EXTRA_CLOSE BODY: HTML contains far too many close tags |
| | 0.1 MIME_HTML_ONLY BODY: Message only has text/html MIME parts |
| | 0.0 HTML_MESSAGE BODY: HTML included in message |
| | 2.0 RDNS_DYNAMIC Delivered to internal network by host with |
| | dynamic-looking rDNS |
| | 0.0 HTML_SHORT_LINK_IMG_2 HTML is very short with a linked image |
| | 3.6 HTML_TAG_BALANCE_CENTER Malformatted HTML |
| | 0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag |
| | 1.0 GOOG_STO_IMG_HTML Apparently using google content hosting to avoid |
| | URIBL |
| | 1.2 DMARC_QUAR DMARC quarantine policy |
| | 0.0 T_REMOTE_IMAGE Message contains an external image |
| | </code> |
| |
| If other quirky issues come up, I'll besure to add them right here! | <code> |
| | sieve-test: Debug: sieve: Commit fileinto action |
| | sieve-test: Debug: sieve: fileinto action: Commit storing into mailbox 'Junk' |
| | sieve-test: Info: sieve: msgid=<20250@psyb.info>: fileinto action: stored mail into mailbox 'Junk' |
| | info: msgid=<20250@psyb.info>: fileinto action: stored mail into mailbox 'Junk'. |
| | sieve-test: Debug: sieve: Finished finalizing actions (status=ok, keep=none, committed=yes) |
| | </code> |
| |
| ====== Part IX - What's next? ====== | ====== Part IX - What's next? ====== |
| I rewrote the mail server tutorial for the presentation [[https://tech.haacksnetworking.org/2025/06/10/your-email-your-rules-self-hosting-simplified/|Your Email, Your Rules: Self-Hosting Simplified]]. The SeaGL presentation can be found [[https://pretalx.seagl.org/2025/talk/VLM7AS/|on their calendar]]. | I rewrote the mail server tutorial for the presentation [[https://tech.haacksnetworking.org/2025/06/10/your-email-your-rules-self-hosting-simplified/|Your Email, Your Rules: Self-Hosting Simplified]]. The SeaGL presentation can be found [[https://pretalx.seagl.org/2025/talk/VLM7AS/|on their calendar]]. |
| |
| --- //[[alerts@haacksnetworking.org|oemb1905]] 2025/11/05 03:41// | --- //[[alerts@haacksnetworking.org|oemb1905]] 2026/08/31 00:07// |