§2024-09-23

試作機器: haraka@hc4Noble.yushei.net

  1. git clone
$ mkdir build && cd $_
$ git clone https://github.com/haraka/Haraka.git
$ cd Haraka/
$ git checkout v3.0.5 -b v3.0.5
Switched to a new branch 'v3.0.5'

$ node --version
v18.19.1
$ npm  --version
9.2.0
$ ls
bin         connection.js    Dockerfile   haraka.js     http            logger.js     plugins     README.md   server.js       tls_socket.js
Changes.md  contrib          docs         haraka.sh     LICENSE         outbound      plugins.js  rfc1869.js  smtp_client.js  TODO
config      CONTRIBUTORS.md  endpoint.js  host_pool.js  line_socket.js  package.json  Plugins.md  run_tests   test            transaction.js

$ npm install
npm WARN deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm WARN deprecated npmlog@6.0.2: This package is no longer supported.
npm WARN deprecated @npmcli/move-file@1.1.2: This functionality has been moved to @npmcli/fs
npm WARN deprecated are-we-there-yet@3.0.1: This package is no longer supported.
npm WARN deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
npm WARN deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
npm WARN deprecated gauge@4.0.4: This package is no longer supported.
npm WARN deprecated eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.

added 415 packages, and audited 416 packages in 3m

56 packages are looking for funding
  run `npm fund` for details

13 high severity vulnerabilities

To address all issues possible (including breaking changes), run:
  npm audit fix --force

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.
  1. try to run
$ sudo node haraka.js  # !!! has to use sudo to accesse interface
WARNING: Not running installed Haraka - command line arguments ignored
loaded TLD files:
  1=1445
  2=8416
  3=3642
loaded 9773 Public Suffixes
loglevel: INFO
log format: DEFAULT
[WARN] [-] [server] smtp.ini.nodes unset, using 1, see https://github.com/haraka/Haraka/wiki/Performance-Tuning
Starting up Haraka version 3.0.5/bfd73e7a
[INFO] [-] [plugins] loading mail_from.is_resolvable
[INFO] [-] [plugins] loading rcpt_to.in_host_list
[INFO] [-] [plugins] loading queue/smtp_forward
[NOTICE] [-] [server] worker started worker=1 pid=6789
loaded TLD files:
  1=1445
  2=8416
  3=3642
loaded 9773 Public Suffixes
loglevel: INFO
log format: DEFAULT
[WARN] [-] [server] smtp.ini.nodes unset, using 1, see https://github.com/haraka/Haraka/wiki/Performance-Tuning
Starting up Haraka version 3.0.5/bfd73e7a
[INFO] [-] [plugins] loading mail_from.is_resolvable
[INFO] [-] [plugins] loading rcpt_to.in_host_list
[INFO] [-] [plugins] loading queue/smtp_forward
[INFO] [-] [core] loading tls.ini
[ERROR] [-] [core] tls key /home/alexlai/build/Haraka/config/tls_key.pem could not be loaded.
[ERROR] [-] [core] tls cert /home/alexlai/build/Haraka/config/tls_cert.pem could not be loaded.
[NOTICE] [-] [server] Listening on [::0]:25
[NOTICE] [-] [server] worker 1 listening on [::0]:25
[NOTICE] [F3C94E90-006A-4744-A8F1-F96A968DFB7D] [core] connect ip=::1 port=33038 local_ip=::1 local_port=25 #!!! connect msgs
[NOTICE] [8F89F248-E084-4813-BB36-AF307AAFBE16] [core] connect ip=192.168.16.249 port=44844 local_ip=192.168.48.239 local_port=25

  1. Edit config/plugins and config/smtp.ini to specify the plugins and config you want.

  2. Setting Up Send-Only Mail Server From Scratch (With Haraka)

haraka@hc4Noble:~/build$ pwd
/home/haraka/build
$ haraka -i hc4NobleMailServer
create: /home/haraka/build/hc4NobleMailServer
create: /home/haraka/build/hc4NobleMailServer/plugins
create: /home/haraka/build/hc4NobleMailServer/docs
create: /home/haraka/build/hc4NobleMailServer/config
create: /home/haraka/build/hc4NobleMailServer/config/smtp.ini
create: /home/haraka/build/hc4NobleMailServer/config/log.ini
create: /home/haraka/build/hc4NobleMailServer/config/plugins

haraka@hc4Noble:~/build$ ls -al hc4NobleMailServer/
total 28
drwxrwxr-x 5 haraka haraka 4096 Sep 23 18:31 .
drwxrwxr-x 4 haraka haraka 4096 Sep 23 18:31 ..
drwxrwxr-x 2 haraka haraka 4096 Sep 23 18:31 config
drwxrwxr-x 2 haraka haraka 4096 Sep 23 18:31 docs
-rw-rw-r-- 1 haraka haraka  147 Sep 23 18:31 package.json
drwxrwxr-x 2 haraka haraka 4096 Sep 23 18:31 plugins
-rw-rw-r-- 1 haraka haraka  888 Sep 23 18:31 README

$ cat hc4NobleMailServer/package.json 
{
  "name": "haraka_local",
  "description": "An SMTP Server",
  "version": "0.0.1",
  "dependencies": {},
  "repository": "",
  "license": "MIT"

haraka@hc4Noble:~/build$ ls -al hc4NobleMailServer/plugins/
total 8
drwxrwxr-x 2 haraka haraka 4096 Sep 23 18:31 .
drwxrwxr-x 5 haraka haraka 4096 Sep 23 18:31 ..
haraka@hc4Noble:~/build$ ls -al hc4NobleMailServer/config/
total 32
drwxrwxr-x 2 haraka haraka 4096 Sep 23 18:31 .
drwxrwxr-x 5 haraka haraka 4096 Sep 23 18:31 ..
-rw-rw-r-- 1 haraka haraka    9 Sep 23 18:31 host_list
-rw-rw-r-- 1 haraka haraka   64 Sep 23 18:31 internalcmd_key
-rw-rw-r-- 1 haraka haraka  277 Sep 23 18:31 log.ini
-rw-rw-r-- 1 haraka haraka    9 Sep 23 18:31 me
-rw-rw-r-- 1 haraka haraka 1309 Sep 23 18:31 plugins
-rw-rw-r-- 1 haraka haraka 1744 Sep 23 18:31 smtp.ini
haraka@hc4Noble:~/build$ ls -al hc4NobleMailServer/docs
total 8
drwxrwxr-x 2 haraka haraka 4096 Sep 23 18:31 .
drwxrwxr-x 5 haraka haraka 4096 Sep 23 18:31 ..
  1. DNS Configuration
$ dig hc4noble.yushei.net mx

; <<>> DiG 9.18.28-1~deb12u2-Debian <<>> hc4noble.yushei.net mx
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64767
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;hc4noble.yushei.net.		IN	MX

;; ANSWER SECTION:
hc4noble.yushei.net.	7200	IN	MX	1 mail.hc4noble.yushei.net.

;; Query time: 243 msec
;; SERVER: 168.95.192.1#53(168.95.192.1) (UDP)
;; WHEN: Tue Sep 24 07:18:35 CST 2024
;; MSG SIZE  rcvd: 69

$ dig mail.hc4Noble.yushei.net --> not yet

  1. Generate a Letencrypt Certificate Install certbot:
$ sudo apt-get -y install certbot

then generate cert-only this way:

` certbot certonly --standalone -d "mail.website.com" -m "mail@website.com" --agree-tos --hsts --staple-ocsp --non-interactive `

&para;Command Breakdown:

- certbot certonly: This tells Certbot to only obtain a certificate without attempting to install it. It’s useful when you want to manually manage your web server’s configuration.
- --standalone: This option tells Certbot to run its own temporary web server to respond to the challenge from Let’s Encrypt. This is useful if you don’t have an existing web server running or prefer not to stop it.
- -d "mail.website.com": The -d flag specifies the domain for which you're requesting the certificate. In this case, it’s for mail.website.com.
- -m "mail@website.com": This flag specifies the email address associated with the certificate. It's important for recovery and notifications about the certificate.
- --agree-tos: This indicates that you agree to Let’s Encrypt's terms of service, which is a requirement for obtaining a certificate.
- --hsts: This option enables HTTP Strict Transport Security (HSTS), which helps protect your site against man-in-the-middle attacks by enforcing the use of HTTPS.
- --staple-ocsp: This enables OCSP stapling, which helps improve the performance of SSL connections and enhances privacy by providing a way to check the revocation status of a certificate.
- --non-interactive: This tells Certbot to run without prompting for user input. It’s useful for automated scripts or cron jobs.

- the followings will fail since hc4Noble.yushei.net is a private IP

$ sudo certbot certonly --standalone -d "hc4Noble.yushei.net" -m "rai.sousuke@mac.com" --agree-tos --hsts --staple-ocsp --non-interactive Saving debug log to /var/log/letsencrypt/letsencrypt.log Account registered. Requesting a certificate for hc4noble.yushei.net, 192.168.48.239

Certbot failed to authenticate some domains (authenticator: standalone). The Certificate Authority reported these problems: Domain: hc4noble.yushei.net Type: dns Detail: no valid A records found for hc4noble.yushei.net; no valid AAAA records found for hc4noble.yushei.net

Hint: The Certificate Authority failed to download the challenge files from the temporary standalone webserver started by Certbot on port 80. Ensure that the listed domains point to this machine and that it can accept inbound connections from the internet.

Some challenges have failed. Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.


6. Setting Up Haraka

6.1  tls

open the config/tls.ini

nano /path/to/haraka/config/tls.ini
and refer to the new certificate:

key=/etc/letsencrypt/live/website.com/privkey.pem
cert=/etc/letsencrypt/live/website.com/fullchain.pem

6.2 me

Here you can set the name to use of the server, it would be used in received lines and elsewhere, by default it is set to your server hostname, 

open config/me, and add mail.website.com on the first line.

$ cat config/me hc4Noble


6.3 host_list

Here, you add the host you want to accept mail for, this is not important for a send-only mail server as it is used when you want to receive an email, so you can skip this.

To accept emails for your domain,  you need to add them to the config/host_list file. for example, to accept emails for website.com (i.e., mail@website.com, user@website.com) you need to add website.com to the host_list file as a single line.

In my case, I added the following:

localhost
mail.webiste.com
webiste.com

Set to accept mail for localhost, mail.website.com, and website.com, again, this option can be skipped since we are only using haraka as a send-only, I just wanted to point it out

$ cat config/host_list hc4Noble



6.4 SMTP

Open config/smtp.ini, uncomment listen, and use it as follows:

listen=111.11.1.111:587

Meaning we are listening on port 587, you can as well change it to 465, but I would stick with 587

uncomment spool_dir, it is like this initially:

;spool_dir=/var/spool/haraka
change it to

spool_dir=/var/spool/haraka
close the file


haraka@hc4Noble:~/build/hc4NobleMailServer$ cat config/smtp.ini ; address to listen on (default: all IPv6 and IPv4 addresses, port 25) ; use "[::0]:25" to listen on IPv6 and IPv4 (not all OSes) listen=[::0]:25

; Note you can listen on multiple IPs/ports using commas: ;listen=127.0.0.1:2529,127.0.0.2:2529,127.0.0.3:2530

; public IP address (default: none) ; If your machine is behind a NAT, some plugins (SPF, GeoIP) gain features ; if they know the servers public IP. If 'stun' is installed, Haraka will ; try to figure it out. If that doesn't work, set it here. ;public_ip=N.N.N.N

; Time in seconds to let sockets be idle with no activity ;inactivity_timeout=300

; Drop privileges to this user/group ;user=smtp ;group=smtp

; Don't stop Haraka if plugins fail to compile ;ignore_bad_plugins=0

; Run using cluster to fork multiple backend processes ;nodes=cpus

; Daemonize ;daemonize=true ;daemon_log_file=/var/log/haraka.log ;daemon_pid_file=/var/run/haraka.pid

; Spooling ; Save memory by spooling large messages to disk spool_dir=/var/spool/haraka ; Specify -1 to never spool to disk ; Specify 0 to always spool to disk ; Otherwise specify a size in bytes, once reached the ; message will be spooled to disk to save memory. ;spool_after=

; Force Shutdown Timeout ; - Haraka tries to close down gracefully, but if everything is shut down ; after this time it will hard close. 30s is usually long enough to ; wait for outbound connections to finish. ;force_shutdown_timeout=30

; SMTP service extensions: https://tools.ietf.org/html/rfc1869 ; strict_rfc1869 = false

; Advertise support for SMTPUTF8 (RFC-6531) ;smtputf8=true

[headers] ;add_received=true ;clean_auth_results=true

;show_version=true

; replace max_header_lines max_lines=1000

; replace max_received_count max_received=100 haraka@hc4Noble:/build/hc4NobleMailServer$ nano config/smtp.ini haraka@hc4Noble:/build/hc4NobleMailServer$ cat config/smtp.ini ; address to listen on (default: all IPv6 and IPv4 addresses, port 25) ; use "[::0]:25" to listen on IPv6 and IPv4 (not all OSes) listen=[::0]:25

; Note you can listen on multiple IPs/ports using commas: ;listen=127.0.0.1:2529,127.0.0.2:2529,127.0.0.3:2530

; public IP address (default: none) ; If your machine is behind a NAT, some plugins (SPF, GeoIP) gain features ; if they know the servers public IP. If 'stun' is installed, Haraka will ; try to figure it out. If that doesn't work, set it here. ;public_ip=N.N.N.N

; Time in seconds to let sockets be idle with no activity ;inactivity_timeout=300

; Drop privileges to this user/group ;user=smtp ;group=smtp

; Don't stop Haraka if plugins fail to compile ;ignore_bad_plugins=0

; Run using cluster to fork multiple backend processes ;nodes=cpus

; Daemonize ;daemonize=true ;daemon_log_file=/var/log/haraka.log ;daemon_pid_file=/var/run/haraka.pid

; Spooling ; Save memory by spooling large messages to disk spool_dir=/var/spool/haraka ; Specify -1 to never spool to disk ; Specify 0 to always spool to disk ; Otherwise specify a size in bytes, once reached the ; message will be spooled to disk to save memory. ;spool_after=

; Force Shutdown Timeout ; - Haraka tries to close down gracefully, but if everything is shut down ; after this time it will hard close. 30s is usually long enough to ; wait for outbound connections to finish. ;force_shutdown_timeout=30

; SMTP service extensions: https://tools.ietf.org/html/rfc1869 ; strict_rfc1869 = false

; Advertise support for SMTPUTF8 (RFC-6531) ;smtputf8=true

[headers] ;add_received=true ;clean_auth_results=true

;show_version=true

; replace max_header_lines max_lines=1000

; replace max_received_count max_received=100


6.5 auth_enc_file

When connecting to the SMTP server, we need a way to authenticate with our user and pass, by default, haraka has a flat_file which stores password in clear text, so we would not use it, instead, install auth_enc_file using the following:

` npm install haraka-plugin-auth-enc-file `

$ cat package.json { "name": "haraka_local", "description": "An SMTP Server", "version": "0.0.1", "dependencies": {}, "repository": "", "license": "MIT" }haraka@hc4Noble:~/build/hc4NobleMailServer$ npm install haraka-plugin-auth-enc-file

added 2 packages, and audited 3 packages in 3s

found 0 vulnerabilities haraka@hc4Noble:~/build/hc4NobleMailServer$ cat package.json { "name": "haraka_local", "description": "An SMTP Server", "version": "0.0.1", "dependencies": { "haraka-plugin-auth-enc-file": "^1.0.3" }, "repository": "", "license": "MIT" }


once that is installed, open the config/auth_enc_file.ini file using:

nano config/auth_enc_file.ini
and use the following format, you can have multiple user:pass, just make sure they are on a separate line:

mail={SHA512-CRYPT}xxxxxx
otherUser={SHA512-CRYPT}xxxxxx
You would replace xxxxxx with a SHA512-CRYPT hash, let's say you want your password to be, my_mum_is_lovely, you would generate it this way:

mkpasswd -m sha-512 -s my_mum_is_lovely
which gives the output

$6$LP5cw5oGVvl4xFPI$ZOC4hSFVpCT/r5hwACfyeh5ENU53zVGoht6cx/4f5Pzr.NABk1jipJRsDan5hd8NtwBJf38vVpt49ZhHJrQP71
Now, you can just change the xxxxxx to the above, and repeat the step for another user in the auth_enc_file.ini.

With that, it means you would be able to connect your SMTP server with the following settings:

For the mail user:

Host: mail.website.com
User: mail
Pass: my_mum_is_lovely
Port: 587
Encryption: tls
For the other user:

Host: mail.website.com
User: otherUser
Pass: your_choosen_pass
Port: 587
Encryption: tls
You won't be able to send anything yet as we are still setting up haraka

dkim
DomainKeys Identified Mail allows the sending server to use a cryptographic signature, storing the public decryption key in a DNS record. The receiver can then verify the signing server has a key for that domain.

cd into the dkim directory:

cd /path/to/haraka/dkim
chmod 744 dkim_key_gen.sh
generate a key for website.com

./dkim_key_gen.sh website.com
When that is done, haraka would have created 4 files in config/dkim/website.com:

dns private public selector
The selector file contains the DNS label where the DKIM public key is published. The private and public files contain the DKIM keys.

The DNS file contains a formatted record of the public key suitable for copying/pasting into your domain's zone file. It also has suggestions for DKIM, SPF, and DMARC policy records.

use cat to view the DNS (I am only highlighting the important bit):

Add this TXT record to the website.com DNS zone.

....
....

BIND zone file formatted:

feb2023._domainkey    IN   TXT (
        "v=DKIM1;p=MIIBIjANBgkxxxxxxxxxxxxxxxxxxxxxxxxxxxuHDDuMexuecP6JiHVzdZppTmANfB1Ak91s0Y1Ev+OW"
        "k/QMsrJ+bCeTOIocXXX6+WqZedddddddddddddddddddSk/e5zjR7ikXRbnsdx2sf5YmtJjv6yDpe1PUCnHR4zjryr2U"
        "FVp69FbiZMDM9PT0lWedfAM3wV+t3Udddddddddddd9ihpMnSLXX6vnCwe349VKQqrt+BS/hwZZhyLWfYdx"
        "0mtOQdDAQAB")
The instruction on how to add it to the txt record would be there. Once you are done with that, you can also add a dmarc:

_dmarc  IN      TXT "v=DMARC1; p=reject; adkim=s; aspf=r; rua=mailto:dmarc-feedback@website.com; ruf=mailto:dmarc-feedback@website.com; pct=100"
Now, open /path/to/haraka_instance/config/dkim_sign.ini and add the following:

headers_to_sign=Subject,From,To
Enable plugins
Open /path/to/haraka_instance/config/plugins and uncomment (by uncomment I mean, remove the pre-pended # symbol) the following plugin:

tls
spf
dkim_sign
haraka-plugin-auth-enc-file
If haraka-plugin-auth-enc-file is not in that file, just add it at the very last line, then close.

Manage Haraka With SystemD
This way you would be able to start haraka automatically on boot, disable, and easily manage haraka instances

Create a new service file for Haraka. You can create it in the /etc/systemd/system directory with the following command:

sudo nano /etc/systemd/system/haraka_instance_1.service
add the below:

[Unit]
Description=Haraka Mail Server
After=network.target

[Service]
ExecStart=/usr/bin/haraka -c .
WorkingDirectory=/path/to/haraka_instance_1
Restart=always
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
Save the File

Reload the Systemd daemon to make the changes effective:

sudo systemctl daemon-reload
Start the Haraka service using the following command:

sudo systemctl start haraka_instance_1
Enable the new haraka service using the following command:

sudo systemctl enable haraka_instance_1
To check the status of the Haraka service, run:

sudo systemctl status haraka_instance_1
Haraka full logs would be logged at Syslog

Testing Smtp Server
If everything went well, you can test your SMTP server, for example, for the mail user, you can use the following details:

For the mail user:

Host: mail.website.com
User: mail
Pass: my_mum_is_lovely
Port: 587
Encryption: tls
Good luck.

If you get stuck or you want to hire me for consultation, then reach out to me: faruq [at] devsrealm.com

Troubleshooting
Here are some common issue and their fixes

Couldn't reach server. Please double-check the server and port number
The "Couldn't reach server. Please double-check the server and port number" error is issued by Gmail when using the "Send mail as" feature. The error is because Google could not reach the server, and the likely culprit is a malformed or incorrect DNS setting, check the following:

Your mail hostname is pointing to the IP of the server hosting the domain
If you are using AAAA records, cross-check that you are using the correct IPV6, Gmail would try to connect to IPV6 and if it fails, it halts, you'll think it would try to also reach the IP of the A record, but that isn't the case, so ensure the IPV6 is correct, or just delete it altogether, this way, Gmail would use the A record IP

- /etc/systemd/system/Haraka.service

[Unit] Description=Haraka Mail Server After=network.target

[Service] ExecStart=/usr/local/bin/haraka -c . WorkingDirectory=/home/haraka/build/hc4NobleMailServer Restart=always RestartSec=10 User=root

[Install] WantedBy=multi-user.target