One host
Every process on the host reads the same file. The login proxies take the mail ports and terminate TLS, and a session backend per protocol serves the mail behind them on loopback. yarilo-auth, yarilo-warden, yarilo-locks and yarilo-dict are reached over Unix sockets.
Each process runs as an account that holds only what it needs. The units, users and directories for systemd are in the repository's deploy/; FreeBSD runs the same set from a port, see Running on FreeBSD.
WARNING
Single-host, not highly available. For HA or multi-node, use Kubernetes.
Processes
| Process | Account | Listens on |
|---|---|---|
yarilo-locks | yarilo | / |
yarilo-auth | yarilo-auth | /, / |
yarilo-warden | yarilo-auth | / |
yarilo-dict | yarilo | / |
yarilo-imap, -pop3, -submission, -lmtp, -managesieve | yarilo | 127., 10110, 10587, 10024, 14190 |
yarilo-imap-login | yarilo-login | 993, 143 |
yarilo-pop3-login | yarilo-login | 995, 110 |
yarilo-submission-login | yarilo-login | 465, 587 |
yarilo-managesieve-login | yarilo-login | 4190 |
yarilo-lmtp-login | yarilo-delivery | 127., for the local MTA |
Start yarilo-locks first, then yarilo-auth, yarilo-warden and yarilo-dict, then the backends, then the login proxies. Every process reads the file named by CONFIG, / when it is unset.
Each process also serves health and metrics on telemetry., :8080 by default, so on one host every process gets its own port from TELEMETRY_. The units give yarilo-locks 127., yarilo-auth 127., and so on. The service manager sets it per process; it is not a key in the file.
Accounts
| Account | Groups | Holds |
|---|---|---|
yarilo | yarilo-auth, yarilo-master | the mail in /, the state in /, the lock and dict sockets |
yarilo-auth | yarilo-master | the user database in /; owns the auth and warden sockets |
yarilo-login | yarilo-auth | the TLS key; reaches the auth client and warden sockets only |
yarilo-delivery | yarilo-auth, yarilo-master | nothing on disk; reaches the master socket to deliver |
The auth master socket answers user lookups and issues sessions without a password, which is how yarilo-lmtp-login delivers. It belongs to the yarilo-master group, which the client login proxies are not in. On a socket, yarilo-auth also checks the peer's uid and issues a session only to the users in auth_. A compromised login proxy reaches neither the mail nor a session for another user.
The login proxies bind ports below 1024 without root: the systemd units give them CAP_ and nothing else, and every other unit has no capabilities at all.
Databases
Two databases sit behind the mail, each opened by one process:
| Database | Opened by | Default | Keys |
|---|---|---|---|
| Users and passwords | yarilo-auth | SQLite, / | auth. |
| IMAP METADATA and the Sieve metadata extensions | yarilo-dict | SQLite, / | dicts., driver sql |
Either can be MySQL or PostgreSQL instead, on this host or another; the drivers are built in, so this is a change to the file only. The sessions never open the metadata database: they reach yarilo-dict on its socket and name the dict.
The file is readable by every yarilo process, so a database password does not go in it. The DSN names a variable, and the service manager sets it for that one process from a file only root reads:
auth:
passdb:
- driver: postgres
dsn: "postgres://yarilo:${YARILO_AUTH_DB_PASSWORD}@127.0.0.1:5432/yarilo?sslmode=disable"
dicts:
metadata:
driver: sql
settings:
driver: mysql
dsn: "yarilo:${YARILO_DICT_DB_PASSWORD}@tcp(127.0.0.1:3306)/yarilo"
namespace: metadata| Password | File | Seen by |
|---|---|---|
YARILO_ | / | yarilo-auth |
YARILO_ | / | yarilo-dict |
Each file holds NAME=value lines, owned by root with mode 0600:
install -o root -m 0600 /dev/null /etc/yarilo/auth.env
echo 'YARILO_AUTH_DB_PASSWORD=...' >> /etc/yarilo/auth.envsystemd loads it with EnvironmentFile in that unit alone. On FreeBSD rc.d reads / and dict. as root for that component alone, and a file not owned by root with mode 0600 stops that component's start. With SQLite there is no password and no file.
For the SQLite user database, see Creating the first user for the table, and keep the file 0600 and owned by yarilo-auth.
Running with systemd
deploy/ holds:
- one unit per component and
yarilo., which wants the processes above;target sysusers., the four accounts and their groups;d/ yarilo. conf tmpfiles., the socket, state and mail directories with their owners;d/ yarilo. conf yarilo., the configuration on this page.yaml
Build a . from a checkout:
./hack/package/deb.shThis builds the binaries into dist/ and writes dist/, which installs the binaries in /, the units and the sample configuration. Installing it creates the accounts and directories and starts nothing.
Install the package, put the certificate and key in /, and start the target:
apt install ./dist/yarilo_*_amd64.deb
install -o root -g yarilo-login -m 0640 key.pem /etc/yarilo/tls/key.pem
install -o root -g root -m 0644 cert.pem /etc/yarilo/tls/cert.pem
systemctl enable --now yarilo.targetThis starts the processes in order: each of yarilo-locks, yarilo-auth, yarilo-warden and yarilo-dict counts as started once its socket exists, so the units after it do not race it.
systemctl restart yarilo-imaprestarts one component.systemctl stop yarilo.stops them in reverse order.target yarilo-jmap,yarilo-jmap-login,yarilo-quota-status,yarilo-sasl-loginandyarilo-backend-apijoin the target withsystemctl enable.- Logs go to the journal:
journalctl -u 'yarilo-*'.
Removing the package stops and disables the units and keeps the configuration, the data and the accounts. Purging it removes the configuration and the state in /, but never the mail or the certificates: while / or / holds anything, it stays, and so do the accounts that own it.
Running on FreeBSD
The FreeBSD port mail/ runs the same processes under the same accounts, started by an rc.d script instead of units.
Platforms
FreeBSD is fully supported. yarilo-auth reads the peer's uid on the master socket, so the check against auth_ holds as it does on Linux.
NetBSD and OpenBSD build, and the gate runs go vet for both, but the service has not been run end to end on either. Neither gives the peer's credentials on a Unix socket, so there the master socket rests on its group alone.
Installing from the port
The port is developed in yarilomail/freebsd-ports before it goes into the ports tree. It builds the 2.4.6 release, the first that runs on FreeBSD with these accounts and the peer check. Once the port is accepted into the tree, this step becomes pkg install yarilo.
Add the port's accounts to the ports tree, copy the port in and build it:
git clone https://github.com/yarilomail/freebsd-ports.git
cat freebsd-ports/UIDs.add >> /usr/ports/UIDs
cat freebsd-ports/GIDs.add >> /usr/ports/GIDs
cp -R freebsd-ports/mail/yarilo /usr/ports/mail/
cd /usr/ports/mail/yarilo && make install cleanFTS, on by default, buildsyarilo-ftson Xapian.REDISpulls in Redis for state shared across hosts.make configchanges either before the build.
Installing creates the accounts and the directories, and starts nothing.
Starting and stopping
Start from the sample configuration, put the certificate and key in place and enable the service:
cp /usr/local/etc/yarilo/yarilo.yaml.sample /usr/local/etc/yarilo/yarilo.yaml
install -o root -g yarilo-login -m 0640 key.pem /usr/local/etc/yarilo/tls/key.pem
install -o root -g wheel -m 0644 cert.pem /usr/local/etc/yarilo/tls/cert.pem
sysrc yarilo_enable=YES
service yarilo startThe script starts the processes in the order of yarilo_ and stops them in reverse. It waits for the lock, auth, warden and dict sockets before it starts what follows them.
| rc.conf variable | Default |
|---|---|
yarilo_ | NO |
yarilo_ | / |
yarilo_ | locks auth warden dict imap pop3 lmtp submission managesieve imap-login pop3-login lmtp-login submission-login managesieve-login |
yarilo_ | info |
yarilo_ | 60 seconds |
service yarilo restart imaprestarts one component, andservice yarilo statuslists each.fts,jmap,jmap-login,quota-status,sasl-loginandbackend-apijoin the set when added toyarilo_.components - Each component gets a fixed telemetry port on loopback, from
127.for0. 0. 1:18080 locksto18099forbackend-api, whatever the list holds.
Accounts on FreeBSD
The accounts are the ones in Accounts, with fixed ids from the port:
| Account | UID | GID |
|---|---|---|
yarilo | 803 | 803 |
yarilo-auth | 804 | 804 |
yarilo-login | 805 | 805 |
yarilo-delivery | 809 | 809 |
yarilo-master is a group alone, GID 810, with yarilo, yarilo-delivery and yarilo-auth as members. yarilo, yarilo-login and yarilo-delivery are members of the yarilo-auth group.
Paths
| Linux | FreeBSD | |
|---|---|---|
| Binaries | / | / |
| Configuration | / | / |
| Certificate and key | / | / |
| Sockets | / | / |
| User database | / | / |
| State, metadata database | / | / |
| Database passwords | /, dict. | /, dict. |
/ | / | |
| Logs | the journal | / |
Ports below 1024
FreeBSD has no per-process capability for low ports. The login proxies bind them as yarilo-login and yarilo-delivery through mac_portacl(4), and nothing runs as root.
Load the module and grant each account its ports:
sysrc -f /boot/loader.conf mac_portacl_load=YES
kldload mac_portacl
cat >> /etc/sysctl.conf <<'CONF'
net.inet.ip.portrange.reservedhigh=0
security.mac.portacl.suser_exempt=1
security.mac.portacl.rules=uid:809:tcp:24,uid:805:tcp:110,uid:805:tcp:143,uid:805:tcp:465,uid:805:tcp:587,uid:805:tcp:993,uid:805:tcp:995
CONF
service sysctl restartThis sequence:
- hands the decision on ports below 1024 to mac_portacl, with root still allowed every port;
- lets
yarilo-delivery(809) bind 24, andyarilo-login(805) bind 110, 143, 465, 587, 993 and 995.
Without the rules, service yarilo start names the missing piece and does not start the login proxy.
Logs
Each process writes /. newsyslog rotates them daily and keeps seven, from /, and service yarilo reopen makes the processes write to the new files.
Delivery from Postfix
yarilo-lmtp-login takes local delivery on 127.. Point Postfix at it:
postconf -e virtual_transport=lmtp:inet:127.0.0.1:24The file
The configuration the package installs as /, without its Sieve extension list:
## /etc/yarilo/yarilo.yaml for one host; every process reads this file.
general:
ssl:
ssl_server_cert_file: /etc/yarilo/tls/cert.pem
ssl_server_key_file: /etc/yarilo/tls/key.pem
ssl_min_protocol: TLS1.2
xclient:
trusted_nets: ["127.0.0.1/32"]
## The sockets carry no TLS: their permissions and the peer check are the boundary.
internal_tls:
enabled: false
services:
## The login proxies, on the mail ports.
imap_login:
enabled: true
port: 143
ssl_mode: starttls
imaps_login:
enabled: true
port: 993
ssl_mode: ssl
pop3_login:
enabled: true
port: 110
ssl_mode: starttls
pop3s_login:
enabled: true
port: 995
ssl_mode: ssl
submission_login:
enabled: true
port: 587
ssl_mode: starttls
auth_allow_cleartext: false
submissions_login:
enabled: true
port: 465
ssl_mode: ssl
auth_allow_cleartext: false
## yarilo-lmtp-login takes mail from the local MTA without a login.
lmtp_login:
enabled: true
port: 24
address: 127.0.0.1
ssl_mode: "no"
managesieve_login:
enabled: true
port: 4190
ssl_mode: starttls
jmap_login:
enabled: true
port: 8443
ssl_mode: ssl
auth_allow_cleartext: false
## The session backends, behind the proxies; an empty address is 127.0.0.1.
imap:
enabled: true
port: 10143
ssl_mode: "no"
xclient_protocol: true
pop3:
enabled: true
port: 10110
ssl_mode: "no"
xclient_protocol: true
submission:
enabled: true
port: 10587
ssl_mode: "no"
xclient_protocol: true
lmtp:
enabled: true
port: 10024
ssl_mode: "no"
xclient_protocol: true
managesieve:
enabled: true
port: 14190
ssl_mode: "no"
xclient_protocol: true
jmap:
enabled: true
port: 10443
ssl_mode: "no"
xclient_protocol: true
imap_login_service:
backend_addr: 127.0.0.1:10143
pop3_login_service:
backend_addr: 127.0.0.1:10110
submission_login_service:
backend_addr: 127.0.0.1:10587
lmtp_login_service:
backend_addr: 127.0.0.1:10024
managesieve_login_service:
backend_addr: 127.0.0.1:14190
## WebSocket push needs websocket_ticket_key, a secret of your own.
jmap_login_service:
backend_addr: 127.0.0.1:10443
jmap_service:
auth_master_addr: unix:/run/yarilo/auth/auth-master.sock
auth_service:
listen: unix:/run/yarilo/auth/auth-client.sock
addr: unix:/run/yarilo/auth/auth-client.sock
master_listen: unix:/run/yarilo/auth/auth-master.sock
master_addr: unix:/run/yarilo/auth/auth-master.sock
socket_group: yarilo-auth
## The backends and yarilo-delivery reach it; only yarilo-delivery issues sessions.
master_socket_group: yarilo-master
master_session_users: [yarilo-delivery]
warden_service:
listen: unix:/run/yarilo/auth/warden.sock
addr: unix:/run/yarilo/auth/warden.sock
socket_group: yarilo-auth
## One host: lock state in memory, on a socket only yarilo reaches.
locks_service:
mode: embedded
socket: /run/yarilo/locks/locks.sock
socket_group: yarilo
locks_client:
mode: embedded
socket: /run/yarilo/locks/locks.sock
## yarilo-dict opens the dict databases; the sessions reach it over this socket.
dict_service:
dict_listen: unix:/run/yarilo/dict/dict.sock
dict_addr: unix:/run/yarilo/dict/dict.sock
socket_group: yarilo
## SQLite, or MySQL or PostgreSQL with the password in /etc/yarilo/auth.env.
auth:
passdb:
- driver: sqlite
dsn: /var/lib/yarilo/auth/users.db
# - driver: mysql
# dsn: "yarilo:${YARILO_AUTH_DB_PASSWORD}@tcp(127.0.0.1:3306)/yarilo"
# - driver: postgres
# dsn: "postgres://yarilo:${YARILO_AUTH_DB_PASSWORD}@127.0.0.1:5432/yarilo?sslmode=disable"
storage:
mail_driver: maildir
maildir_root: /var/mail/vhosts
mail_home: "%d/%u"
index: fileindex
## Submission and Sieve send through the local MTA.
protocol:
jmap:
jmap_base_url: "https://mail.example.org:8443"
submission:
relay:
submission_relay_host: 127.0.0.1
submission_relay_port: 25
sieve:
sieve_enabled: true
sieve_scripts_driver: fs
sieve_submission_host: 127.0.0.1:25
## SQLite, or MySQL or PostgreSQL with the password in /etc/yarilo/dict.env.
dicts:
metadata:
driver: sql
settings:
driver: sqlite
dsn: /var/lib/yarilo/state/metadata.db
namespace: metadataThe shipped file lists every Sieve extension except vnd., vnd. and vnd., which run programs as the yarilo account; add them only with program directories you trust.
The MTA delivers to 127. (lmtp_). For Postfix:
postconf -e virtual_transport=lmtp:inet:127.0.0.1:24It authenticates its users, if it does, through yarilo-sasl-login; see Docker Compose for the MTA side.
See Services for the listener keys and the Unix socket rules.