Compare commits
6 Commits
eb7a22729a
...
df24c92e54
Author | SHA1 | Date |
---|---|---|
Egor Savkin | df24c92e54 | |
Egor Savkin | 4bbf162a87 | |
Egor Savkin | 164c0f021c | |
Egor Savkin | e60896c288 | |
Egor Savkin | f9985dc2c1 | |
Egor Savkin | 8355672611 |
|
@ -1,43 +0,0 @@
|
||||||
logoutput: syslog
|
|
||||||
user.privileged: root
|
|
||||||
user.unprivileged: nobody
|
|
||||||
|
|
||||||
# The listening network interface or address.
|
|
||||||
internal: 5.78.86.156 port=2025
|
|
||||||
internal: 2a01:4ff:1f0:83de::1 port = 2025
|
|
||||||
|
|
||||||
# The proxying network interface or address.
|
|
||||||
external: eth0
|
|
||||||
|
|
||||||
# socks-rules determine what is proxied through the external interface.
|
|
||||||
socksmethod: none
|
|
||||||
|
|
||||||
# client-rules determine who can connect to the internal interface.
|
|
||||||
clientmethod: none
|
|
||||||
|
|
||||||
client pass {
|
|
||||||
from: 94.190.212.123/32 to: 0.0.0.0/0
|
|
||||||
}
|
|
||||||
socks pass {
|
|
||||||
from: 94.190.212.123/32 to: 0.0.0.0/0
|
|
||||||
}
|
|
||||||
|
|
||||||
client pass {
|
|
||||||
from: 202.77.7.238/32 to: 0.0.0.0/0
|
|
||||||
}
|
|
||||||
socks pass {
|
|
||||||
from: 202.77.7.238/32 to: 0.0.0.0/0
|
|
||||||
}
|
|
||||||
|
|
||||||
client pass {
|
|
||||||
from: 2001:470:18:390::2/128 to: 0.0.0.0/0
|
|
||||||
}
|
|
||||||
socks pass {
|
|
||||||
from: 2001:470:18:390::2/128 to: 0.0.0.0/0
|
|
||||||
}
|
|
||||||
client pass {
|
|
||||||
from: 2001:470:f891:1:5999:5529:5d:f71d/128 to: 0.0.0.0/0
|
|
||||||
}
|
|
||||||
socks pass {
|
|
||||||
from: 2001:470:f891:1:5999:5529:5d:f71d/128 to: 0.0.0.0/0
|
|
||||||
}
|
|
|
@ -77,14 +77,12 @@ stream {
|
||||||
# SMTP
|
# SMTP
|
||||||
server {
|
server {
|
||||||
listen 25;
|
listen 25;
|
||||||
proxy_protocol on;
|
|
||||||
proxy_pass smtp_backend;
|
proxy_pass smtp_backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Submission (Authenticated SMTP)
|
# Submission (Authenticated SMTP)
|
||||||
server {
|
server {
|
||||||
listen 587;
|
listen 587;
|
||||||
proxy_protocol on;
|
|
||||||
proxy_pass submission_backend;
|
proxy_pass submission_backend;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
# Setup m-labs-intl.com server
|
# Setup m-labs-intl.com server
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
apt install git nginx-full python3 python3.12-venv python3-pip dante-server
|
apt install git nginx-full python3 python3.12-venv python3-pip
|
||||||
snap install --classic certbot
|
snap install --classic certbot
|
||||||
ln -s /snap/bin/certbot /usr/bin/certbot
|
ln -s /snap/bin/certbot /usr/bin/certbot
|
||||||
useradd -m rfqserver
|
useradd -m rfqserver
|
||||||
|
@ -11,8 +11,6 @@ cp m-labs-intl.com /etc/nginx/sites-available/
|
||||||
cp nginx.conf /etc/nginx/
|
cp nginx.conf /etc/nginx/
|
||||||
ln -s /etc/nginx/sites-available/m-labs-intl.com /etc/nginx/sites-enabled/
|
ln -s /etc/nginx/sites-available/m-labs-intl.com /etc/nginx/sites-enabled/
|
||||||
|
|
||||||
cp danted.conf /etc/
|
|
||||||
|
|
||||||
mkdir -p /var/www/m-labs-intl.com/html
|
mkdir -p /var/www/m-labs-intl.com/html
|
||||||
chown -R zolaupd /var/www/m-labs-intl.com/
|
chown -R zolaupd /var/www/m-labs-intl.com/
|
||||||
|
|
||||||
|
@ -46,10 +44,8 @@ cp rfq.service /etc/systemd/system/
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable rfq.service
|
systemctl enable rfq.service
|
||||||
systemctl start rfq.service
|
systemctl start rfq.service
|
||||||
systemctl enable danted.service
|
|
||||||
|
|
||||||
service nginx restart
|
service nginx restart
|
||||||
service danted restart
|
|
||||||
|
|
||||||
certbot --nginx
|
certbot --nginx
|
||||||
|
|
||||||
|
|
|
@ -1,278 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import email
|
|
||||||
import logging
|
|
||||||
from smtplib import SMTP as SMTPClient
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
from aiosmtpd.proxy_protocol import ProxyData
|
|
||||||
from python_socks.sync import Proxy
|
|
||||||
import os
|
|
||||||
import ssl
|
|
||||||
from aiosmtpd.controller import Controller
|
|
||||||
from aiosmtpd.smtp import Envelope, Session, SMTP
|
|
||||||
from email.message import Message
|
|
||||||
import dkim
|
|
||||||
import spf
|
|
||||||
from functools import lru_cache
|
|
||||||
import dns.resolver
|
|
||||||
|
|
||||||
PROXY = Proxy.from_url('socks5://5.78.86.156:2025')
|
|
||||||
|
|
||||||
END_HOST = "localhost"
|
|
||||||
END_PORT = 25
|
|
||||||
|
|
||||||
PROXY_SMTP_HOST = "*"
|
|
||||||
PROXY_SMTP_PORT = 2025
|
|
||||||
END_SMTP_HOST = "*"
|
|
||||||
END_SMTP_PORT = 3025
|
|
||||||
GENERIC_SMTP_HOST = "*"
|
|
||||||
GENERIC_SMTP_PORT = 25
|
|
||||||
|
|
||||||
log = logging.getLogger("smtphandler")
|
|
||||||
VALID_PROXY_ADDRS = {"5.78.86.156", "2a01:4ff:1f0:83de::1", "m-labs-intl.com", "mail.m-labs-intl.com"}
|
|
||||||
VALID_END_ADDRS = {"localhost", "127.0.0.1"}
|
|
||||||
VALID_RECEPIENTS = {"m-labs.hk", "m-labs.ph", "m-labs-intl.com", "193thz.com", "malloctech.fr"}
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=256)
|
|
||||||
def get_mx(domain):
|
|
||||||
records = dns.resolver.resolve(domain, "MX")
|
|
||||||
if not records:
|
|
||||||
return None
|
|
||||||
result = max(records, key=lambda r: r.preference)
|
|
||||||
return str(result.exchange)
|
|
||||||
|
|
||||||
|
|
||||||
class ProxiedSMTP(SMTPClient):
|
|
||||||
def _get_socket(self, host, port, timeout):
|
|
||||||
return PROXY.connect(dest_host=host, dest_port=port, timeout=timeout)
|
|
||||||
|
|
||||||
|
|
||||||
class GenericMailServer:
|
|
||||||
"""
|
|
||||||
Accepts mail from everywhere, and passes it to the real SMTP server, after proper SPF and DKIM checks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def handle_MAIL(self,
|
|
||||||
server: SMTP,
|
|
||||||
session: Session,
|
|
||||||
envelope: Envelope,
|
|
||||||
address: str,
|
|
||||||
mail_options: list):
|
|
||||||
ip = session.peer[0]
|
|
||||||
result, description = spf.check2(ip, address, session.host_name)
|
|
||||||
valid_spf = result == 'pass'
|
|
||||||
envelope.spf = valid_spf
|
|
||||||
|
|
||||||
log.info("SPF: %s, %s", result, description)
|
|
||||||
|
|
||||||
if not valid_spf:
|
|
||||||
return '550 SPF validation failed'
|
|
||||||
|
|
||||||
envelope.mail_from = address
|
|
||||||
envelope.mail_options.extend(mail_options)
|
|
||||||
|
|
||||||
return '250 OK'
|
|
||||||
|
|
||||||
async def handle_RCPT(self,
|
|
||||||
server: SMTP,
|
|
||||||
session: Session,
|
|
||||||
envelope: Envelope,
|
|
||||||
address: str,
|
|
||||||
rcpt_options: list):
|
|
||||||
if address.split("@")[1] not in VALID_RECEPIENTS:
|
|
||||||
return '550 not relaying to that domain'
|
|
||||||
|
|
||||||
log.debug("Handle RCPT for %s", address)
|
|
||||||
envelope.rcpt_tos.append(address)
|
|
||||||
return '250 OK'
|
|
||||||
|
|
||||||
async def handle_DATA(self,
|
|
||||||
server: SMTP,
|
|
||||||
session: Session,
|
|
||||||
envelope: Envelope):
|
|
||||||
valid_dkim = dkim.verify(envelope.content)
|
|
||||||
envelope.dkim = valid_dkim
|
|
||||||
log.info("DKIM: %s", valid_dkim)
|
|
||||||
|
|
||||||
message: Message = email.message_from_bytes(envelope.content)
|
|
||||||
|
|
||||||
if not valid_dkim:
|
|
||||||
return '550 DKIM validation failed'
|
|
||||||
|
|
||||||
log.info('Message: %s', message)
|
|
||||||
try:
|
|
||||||
with SMTPClient(END_HOST, END_PORT) as client:
|
|
||||||
client.sendmail(
|
|
||||||
from_addr=envelope.mail_from,
|
|
||||||
to_addrs=envelope.rcpt_tos,
|
|
||||||
msg=envelope.original_content
|
|
||||||
)
|
|
||||||
except BaseException as e:
|
|
||||||
print(e)
|
|
||||||
return '500 Could not process your message'
|
|
||||||
|
|
||||||
return '250 Message accepted for delivery'
|
|
||||||
|
|
||||||
|
|
||||||
class EndMailServer:
|
|
||||||
"""
|
|
||||||
Accepts mail only from end server. Checks if mail is signed to be from proxy, and sends to proxy if needed.
|
|
||||||
SPF and DKIM needs to be already included.
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def handle_DATA(self,
|
|
||||||
server: SMTP,
|
|
||||||
session: Session,
|
|
||||||
envelope: Envelope):
|
|
||||||
ip = session.peer[0]
|
|
||||||
if ip not in VALID_END_ADDRS:
|
|
||||||
return "521 Server doesn't accept mail"
|
|
||||||
message: Message = email.message_from_bytes(envelope.content)
|
|
||||||
log.info('Message: %s', message)
|
|
||||||
mx_rcpt: Dict[str, list[str]] = {}
|
|
||||||
for rcpt in envelope.rcpt_tos:
|
|
||||||
_, _, domain = rcpt.partition("@")
|
|
||||||
mx = get_mx(domain)
|
|
||||||
if mx is None:
|
|
||||||
continue
|
|
||||||
mx_rcpt.setdefault(mx, []).append(rcpt)
|
|
||||||
|
|
||||||
try:
|
|
||||||
for mx, rcpts in mx_rcpt.items():
|
|
||||||
if envelope.mail_from in VALID_PROXY_ADDRS:
|
|
||||||
with ProxiedSMTP(mx, 25) as client:
|
|
||||||
client.sendmail(
|
|
||||||
from_addr=envelope.mail_from,
|
|
||||||
to_addrs=rcpts,
|
|
||||||
msg=envelope.original_content
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
with SMTPClient(mx, 25) as client:
|
|
||||||
client.sendmail(
|
|
||||||
from_addr=envelope.mail_from,
|
|
||||||
to_addrs=rcpts,
|
|
||||||
msg=envelope.original_content
|
|
||||||
)
|
|
||||||
except BaseException as e:
|
|
||||||
print(e)
|
|
||||||
return '500 Could not process your message'
|
|
||||||
|
|
||||||
return '250 Message accepted for delivery'
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyMailServer:
|
|
||||||
"""
|
|
||||||
Accepts mail only from proxy server, and passes it to the real SMTP server, after proper SPF and DKIM checks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def handle_PROXY(self, server: SMTP, session: Session, envelope: Envelope, proxy_data: ProxyData):
|
|
||||||
ip = session.peer[0]
|
|
||||||
envelope.proxy_data = proxy_data
|
|
||||||
return ip in VALID_PROXY_ADDRS
|
|
||||||
|
|
||||||
async def handle_MAIL(self,
|
|
||||||
server: SMTP,
|
|
||||||
session: Session,
|
|
||||||
envelope: Envelope,
|
|
||||||
address: str,
|
|
||||||
mail_options: list):
|
|
||||||
ip = envelope.proxy_data.src_addr
|
|
||||||
result, description = spf.check2(ip, address, session.host_name)
|
|
||||||
valid_spf = result == 'pass'
|
|
||||||
envelope.spf = valid_spf
|
|
||||||
|
|
||||||
log.info("SPF: %s, %s", result, description)
|
|
||||||
|
|
||||||
if not valid_spf:
|
|
||||||
return '550 SPF validation failed'
|
|
||||||
|
|
||||||
envelope.mail_from = address
|
|
||||||
envelope.mail_options.extend(mail_options)
|
|
||||||
|
|
||||||
return '250 OK'
|
|
||||||
|
|
||||||
async def handle_RCPT(self,
|
|
||||||
server: SMTP,
|
|
||||||
session: Session,
|
|
||||||
envelope: Envelope,
|
|
||||||
address: str,
|
|
||||||
rcpt_options: list):
|
|
||||||
if address.split("@")[1] not in VALID_RECEPIENTS:
|
|
||||||
return '550 not relaying to that domain'
|
|
||||||
|
|
||||||
log.debug("Handle RCPT for %s", address)
|
|
||||||
envelope.rcpt_tos.append(address)
|
|
||||||
return '250 OK'
|
|
||||||
|
|
||||||
async def handle_DATA(self,
|
|
||||||
server: SMTP,
|
|
||||||
session: Session,
|
|
||||||
envelope: Envelope):
|
|
||||||
valid_dkim = dkim.verify(envelope.content)
|
|
||||||
envelope.dkim = valid_dkim
|
|
||||||
log.info("DKIM: %s", valid_dkim)
|
|
||||||
|
|
||||||
message: Message = email.message_from_bytes(envelope.content)
|
|
||||||
|
|
||||||
if not valid_dkim:
|
|
||||||
return '550 DKIM validation failed'
|
|
||||||
|
|
||||||
log.info('Message: %s', message)
|
|
||||||
try:
|
|
||||||
with SMTPClient(END_HOST, END_PORT) as client:
|
|
||||||
client.sendmail(
|
|
||||||
from_addr=envelope.mail_from,
|
|
||||||
to_addrs=envelope.rcpt_tos,
|
|
||||||
msg=envelope.original_content
|
|
||||||
)
|
|
||||||
except BaseException as e:
|
|
||||||
print(e)
|
|
||||||
return '500 Could not process your message'
|
|
||||||
|
|
||||||
return '250 Message accepted for delivery'
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
host = os.getenv('SMTP_HOST', '*')
|
|
||||||
port = int(os.getenv('SMTP_PORT', '25'))
|
|
||||||
accept_host = os.getenv('ACCEPT_HOST')
|
|
||||||
ssl_keys = os.getenv('SSL_KEYS')
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
|
|
||||||
end_handler = EndMailServer()
|
|
||||||
proxy_handler = ProxyMailServer()
|
|
||||||
generic_handler = GenericMailServer()
|
|
||||||
|
|
||||||
ssl_context = None
|
|
||||||
|
|
||||||
if ssl_keys:
|
|
||||||
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
|
||||||
ssl_context.load_cert_chain(ssl_keys + '.crt', ssl_keys + '.key')
|
|
||||||
|
|
||||||
generic_controller = Controller(GenericMailServer, hostname=GENERIC_SMTP_HOST, port=GENERIC_SMTP_PORT)
|
|
||||||
generic_controller.factory = lambda: SMTP(GenericMailServer, enable_SMTPUTF8=True, tls_context=ssl_context)
|
|
||||||
generic_controller.start()
|
|
||||||
log.info("Generic SMTP server started on %s:%s", GENERIC_SMTP_HOST, GENERIC_SMTP_PORT)
|
|
||||||
|
|
||||||
end_controller = Controller(EndMailServer, hostname=END_SMTP_HOST, port=END_SMTP_PORT)
|
|
||||||
end_controller.factory = lambda: SMTP(EndMailServer, enable_SMTPUTF8=True)
|
|
||||||
end_controller.start()
|
|
||||||
log.info("End SMTP server started on %s:%s", END_SMTP_HOST, END_SMTP_PORT)
|
|
||||||
|
|
||||||
proxy_controller = Controller(ProxyMailServer, hostname=PROXY_SMTP_HOST, port=PROXY_SMTP_PORT)
|
|
||||||
proxy_controller.factory = lambda: SMTP(ProxyMailServer, enable_SMTPUTF8=True, tls_context=ssl_context)
|
|
||||||
proxy_controller.start()
|
|
||||||
log.info("Proxy SMTP server started on %s:%s", PROXY_SMTP_HOST, PROXY_SMTP_PORT)
|
|
||||||
|
|
||||||
try:
|
|
||||||
loop.run_forever()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Shutting down")
|
|
||||||
finally:
|
|
||||||
generic_controller.stop()
|
|
||||||
end_controller.stop()
|
|
||||||
proxy_controller.stop()
|
|
||||||
loop.stop()
|
|
||||||
loop.close()
|
|
|
@ -26,10 +26,9 @@ let
|
||||||
${config.services.mysql.package}/bin/mysqldump --user=root --single-transaction flarum > flarum.sql
|
${config.services.mysql.package}/bin/mysqldump --user=root --single-transaction flarum > flarum.sql
|
||||||
${config.services.postgresql.package}/bin/pg_dump mattermost > mattermost.sql
|
${config.services.postgresql.package}/bin/pg_dump mattermost > mattermost.sql
|
||||||
${config.services.postgresql.package}/bin/pg_dump rt5 > rt.sql
|
${config.services.postgresql.package}/bin/pg_dump rt5 > rt.sql
|
||||||
${config.services.postgresql.package}/bin/pg_dump gitea > gitea.sql
|
|
||||||
|
|
||||||
exec 6< /etc/nixos/secret/backup-passphrase
|
exec 6< /etc/nixos/secret/backup-passphrase
|
||||||
${pkgs.gnutar}/bin/tar cf - ${lib.concatMapStringsSep " " (p: "--exclude \"${p}\"") excludePaths} /etc/nixos /var/vmail /var/lib/hedgedoc /var/lib/gitea /var/lib/afws /var/lib/mattermost/data /var/www/193thz flarum.sql mattermost.sql rt.sql gitea.sql | \
|
${pkgs.gnutar}/bin/tar cf - ${lib.concatMapStringsSep " " (p: "--exclude \"${p}\"") excludePaths} /etc/nixos /var/vmail /var/lib/hedgedoc /var/lib/gitea /var/lib/afws /var/lib/mattermost/data /var/www/193thz flarum.sql mattermost.sql rt.sql | \
|
||||||
${pkgs.bzip2}/bin/bzip2 | \
|
${pkgs.bzip2}/bin/bzip2 | \
|
||||||
${pkgs.gnupg}/bin/gpg --symmetric --batch --passphrase-fd 6
|
${pkgs.gnupg}/bin/gpg --symmetric --batch --passphrase-fd 6
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -375,14 +375,10 @@ in
|
||||||
notify explicit;
|
notify explicit;
|
||||||
also-notify {
|
also-notify {
|
||||||
216.218.130.2; # ns1.he.net
|
216.218.130.2; # ns1.he.net
|
||||||
213.239.220.50; # ns1.qnetp.net
|
|
||||||
88.198.32.245; # new qnetp
|
|
||||||
};
|
};
|
||||||
'';
|
'';
|
||||||
slaves = [
|
slaves = [
|
||||||
"216.218.133.2" "2001:470:600::2" # slave.dns.he.net
|
"216.218.133.2" "2001:470:600::2" # slave.dns.he.net
|
||||||
"213.239.220.50" "2a01:4f8:a0:7041::1" # ns1.qnetp.net
|
|
||||||
"88.198.32.245" # new qnetp
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
"200-29.98.206.103.in-addr.arpa" = {
|
"200-29.98.206.103.in-addr.arpa" = {
|
||||||
|
@ -777,10 +773,6 @@ in
|
||||||
services.gitea = {
|
services.gitea = {
|
||||||
enable = true;
|
enable = true;
|
||||||
appName = "M-Labs Git";
|
appName = "M-Labs Git";
|
||||||
database = {
|
|
||||||
type = "postgres";
|
|
||||||
socket = "/run/postgresql";
|
|
||||||
};
|
|
||||||
mailerPasswordFile = "/etc/nixos/secret/mailerpassword";
|
mailerPasswordFile = "/etc/nixos/secret/mailerpassword";
|
||||||
settings = {
|
settings = {
|
||||||
server = {
|
server = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
$TTL 7200
|
$TTL 7200
|
||||||
|
|
||||||
@ SOA ns.m-labs-intl.com. sb.m-labs.hk. (
|
@ SOA ns.m-labs-intl.com. sb.m-labs.hk. (
|
||||||
2024081503
|
2024081402
|
||||||
7200
|
7200
|
||||||
3600
|
3600
|
||||||
86400
|
86400
|
||||||
|
@ -10,14 +10,12 @@ $TTL 7200
|
||||||
|
|
||||||
NS ns.m-labs-intl.com.
|
NS ns.m-labs-intl.com.
|
||||||
NS ns1.he.net.
|
NS ns1.he.net.
|
||||||
NS ns1.qnetp.net.
|
|
||||||
|
|
||||||
A 5.78.86.156
|
A 5.78.86.156
|
||||||
AAAA 2a01:4ff:1f0:83de::1
|
AAAA 2a01:4ff:1f0:83de::1
|
||||||
MX 10 mail.m-labs-intl.com.
|
MX 10 mail.m-labs-intl.com.
|
||||||
TXT "v=spf1 mx -all"
|
TXT "v=spf1 mx -all"
|
||||||
TXT "google-site-verification=BlQd5_5wWW7calKC7bZA0GdoxR8-zj4gwJEg9sGJ3l8"
|
TXT "google-site-verification=BlQd5_5wWW7calKC7bZA0GdoxR8-zj4gwJEg9sGJ3l8"
|
||||||
CAA 0 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1768317117"
|
|
||||||
|
|
||||||
ns A 94.190.212.123
|
ns A 94.190.212.123
|
||||||
ns AAAA 2001:470:18:390::2
|
ns AAAA 2001:470:18:390::2
|
||||||
|
|
Loading…
Reference in New Issue