mirror of
https://github.com/chatmail/relay.git
synced 2026-05-10 16:04:37 +00:00
Compare commits
32 Commits
greeterbot
...
link2xt/cg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83d3418dc8 | ||
|
|
700256c273 | ||
|
|
d575d62b18 | ||
|
|
8cdf8ce376 | ||
|
|
7c9abfbde3 | ||
|
|
95de87a325 | ||
|
|
5366df8dc6 | ||
|
|
0a6db5161d | ||
|
|
62e25e44fd | ||
|
|
ce9fe920dc | ||
|
|
c171866faf | ||
|
|
7758c94e31 | ||
|
|
66debb9245 | ||
|
|
3542232393 | ||
|
|
536c12d989 | ||
|
|
265403e110 | ||
|
|
fd679af577 | ||
|
|
ecbf135549 | ||
|
|
7b90b936dd | ||
|
|
17a919ee53 | ||
|
|
1b15ec0eae | ||
|
|
bf863f05b6 | ||
|
|
a2316beab1 | ||
|
|
28fc91f5f3 | ||
|
|
67062677b0 | ||
|
|
faf8ffe678 | ||
|
|
5821098699 | ||
|
|
542d63888a | ||
|
|
449f8a014c | ||
|
|
57764d0cf5 | ||
|
|
c39a79e26a | ||
|
|
b6622fc68e |
@@ -10,10 +10,6 @@ dependencies = [
|
||||
"iniconfig",
|
||||
"deltachat-rpc-server",
|
||||
"deltachat-rpc-client",
|
||||
"ConfigArgParse",
|
||||
"deltachat",
|
||||
"setuptools>=60",
|
||||
"setuptools-scm>=8",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
@@ -26,7 +22,6 @@ where = ['src']
|
||||
doveauth = "chatmaild.doveauth:main"
|
||||
filtermail = "chatmaild.filtermail:main"
|
||||
echobot = "chatmaild.echo:main"
|
||||
greeterbot = "chatmaild.greeterbot:main"
|
||||
chatmail-metrics = "chatmaild.metrics:main"
|
||||
|
||||
[project.entry-points.pytest11]
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
@@ -46,17 +46,11 @@ class Connection:
|
||||
)
|
||||
return result
|
||||
|
||||
def get_user_list(self) -> set[str]:
|
||||
"""Get a set of all users."""
|
||||
q = "SELECT addr from users"
|
||||
return set([tup[0] for tup in self._sqlconn.execute(q).fetchall()])
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self, path: str, read_only=False):
|
||||
def __init__(self, path: str):
|
||||
self.path = Path(path)
|
||||
if not read_only:
|
||||
self.ensure_tables()
|
||||
self.ensure_tables()
|
||||
|
||||
def _get_connection(
|
||||
self, write=False, transaction=False, closing=False
|
||||
|
||||
@@ -46,7 +46,7 @@ def is_allowed_to_create(config: Config, user, cleartext_password) -> bool:
|
||||
len(localpart) > config.username_max_length
|
||||
or len(localpart) < config.username_min_length
|
||||
):
|
||||
if localpart not in ("echo", "hello"):
|
||||
if localpart != "echo":
|
||||
logging.warning(
|
||||
"localpart %s has to be between %s and %s chars long",
|
||||
localpart,
|
||||
|
||||
Binary file not shown.
@@ -1,132 +0,0 @@
|
||||
import time
|
||||
|
||||
import deltachat
|
||||
from deltachat.tracker import ConfigureFailed
|
||||
from time import sleep
|
||||
import tempfile
|
||||
import os
|
||||
import configargparse
|
||||
import pkg_resources
|
||||
import secrets
|
||||
|
||||
from chatmaild.database import Database
|
||||
from chatmaild.config import read_config
|
||||
from chatmaild.newemail import ALPHANUMERIC_PUNCT, CONFIG_PATH
|
||||
|
||||
PASSDB_PATH = "/home/vmail/passdb.sqlite"
|
||||
|
||||
|
||||
def setup_account(data_dir: str, debug: bool) -> deltachat.Account:
|
||||
"""Create a deltachat account with a given addr/password combination.
|
||||
|
||||
:param data_dir: the directory where the data(base) is stored.
|
||||
:param debug: whether to show log messages for the account.
|
||||
:return: the deltachat account object.
|
||||
"""
|
||||
chatmail_config = read_config(CONFIG_PATH)
|
||||
addr = "hello@" + chatmail_config.mail_domain
|
||||
|
||||
try:
|
||||
os.mkdir(os.path.join(data_dir, addr))
|
||||
except FileExistsError:
|
||||
pass
|
||||
db_path = os.path.join(data_dir, addr, "db.sqlite")
|
||||
|
||||
ac = deltachat.Account(db_path)
|
||||
if debug:
|
||||
ac.add_account_plugin(deltachat.events.FFIEventLogger(ac))
|
||||
|
||||
ac.set_config("mvbox_move", "0")
|
||||
ac.set_config("sentbox_watch", "0")
|
||||
ac.set_config("bot", "1")
|
||||
ac.set_config("mdns_enabled", "0")
|
||||
|
||||
if not ac.is_configured():
|
||||
cleartext_password = "".join(
|
||||
secrets.choice(ALPHANUMERIC_PUNCT)
|
||||
for _ in range(chatmail_config.password_min_length + 3)
|
||||
)
|
||||
ac.set_config("mail_pw", cleartext_password)
|
||||
ac.set_config("addr", addr)
|
||||
|
||||
configtracker = ac.configure()
|
||||
try:
|
||||
configtracker.wait_finish()
|
||||
except ConfigureFailed:
|
||||
print(
|
||||
"configuration setup failed for %s with password:\n%s"
|
||||
% (ac.get_config("addr"), ac.get_config("mail_pw"))
|
||||
)
|
||||
raise
|
||||
|
||||
ac.start_io()
|
||||
avatar = pkg_resources.resource_filename(__name__, "avatar.jpg")
|
||||
ac.set_avatar(avatar)
|
||||
ac.set_config("displayname", f"Hello at {chatmail_config.mail_domain}!")
|
||||
return ac
|
||||
|
||||
|
||||
class GreetBot:
|
||||
def __init__(self, passdb, account):
|
||||
self.db = Database(passdb, read_only=True)
|
||||
self.account = account
|
||||
self.domain = account.get_config("addr").split("@")[1]
|
||||
with self.db.read_connection() as conn:
|
||||
self.existing_users = conn.get_user_list()
|
||||
|
||||
def greet_users(self):
|
||||
with self.db.read_connection() as conn:
|
||||
users = conn.get_user_list()
|
||||
new_users = users.difference(self.existing_users)
|
||||
self.existing_users = users
|
||||
time.sleep(20) # wait until Delta is configured on the user side
|
||||
for user in new_users:
|
||||
for ci_prefix in ["ac1_", "ac2_", "ac3_", "ac4_", "ac5_", "ci-"]:
|
||||
if user.startswith(ci_prefix):
|
||||
continue
|
||||
if user not in [c.addr for c in self.account.get_contacts()]:
|
||||
print("Inviting", user)
|
||||
contact = self.account.create_contact(user)
|
||||
chat = contact.create_chat()
|
||||
chat.send_text(
|
||||
"Welcome to %s! Here you can try out Delta Chat." % (self.domain,)
|
||||
)
|
||||
chat.send_text(
|
||||
"I prepared some webxdc apps for you, if you are interested:"
|
||||
)
|
||||
chat.send_file(pkg_resources.resource_filename(__name__, "editor.xdc"))
|
||||
chat.send_file(
|
||||
pkg_resources.resource_filename(__name__, "tower-builder.xdc")
|
||||
)
|
||||
chat.send_text(
|
||||
"You can visit https://webxdc.org/apps to discover more apps! "
|
||||
"Some of these games you can also play with friends, directly in the chat."
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
args = configargparse.ArgumentParser()
|
||||
args.add_argument("--db_path", help="location of the Delta Chat database")
|
||||
args.add_argument(
|
||||
"--passdb", default=PASSDB_PATH, help="location of the chatmail passdb"
|
||||
)
|
||||
args.add_argument("--show-ffi", action="store_true", help="print Delta Chat log")
|
||||
ops = args.parse_args()
|
||||
|
||||
# ensuring account data directory
|
||||
if ops.db_path is None:
|
||||
tempdir = tempfile.TemporaryDirectory(prefix="hellobot")
|
||||
ops.db_path = tempdir.name
|
||||
elif not os.path.exists(ops.db_path):
|
||||
os.mkdir(ops.db_path)
|
||||
|
||||
ac = setup_account(ops.db_path, ops.show_ffi)
|
||||
greeter = GreetBot(ops.passdb, ac)
|
||||
print("waiting for new chatmail users...")
|
||||
while 1:
|
||||
greeter.greet_users()
|
||||
sleep(5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,11 +0,0 @@
|
||||
[Unit]
|
||||
Description=Chatmail greeterbot, a Delta Chat bot to greet new users
|
||||
|
||||
[Service]
|
||||
ExecStart={execpath} --passdb {passdb_path} --db_path /home/vmail/greeterbot/ --show-ffi
|
||||
User=vmail
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Binary file not shown.
@@ -101,13 +101,11 @@ def _install_remote_venv_with_chatmaild(config) -> None:
|
||||
"doveauth",
|
||||
"filtermail",
|
||||
"echobot",
|
||||
"greeterbot",
|
||||
):
|
||||
params = dict(
|
||||
execpath=f"{remote_venv_dir}/bin/{fn}",
|
||||
config_path=remote_chatmail_inipath,
|
||||
remote_venv_dir=remote_venv_dir,
|
||||
passdb_path="/home/vmail/passdb.sqlite",
|
||||
)
|
||||
source_path = importlib.resources.files("chatmaild").joinpath(f"{fn}.service.f")
|
||||
content = source_path.read_text().format(**params).encode()
|
||||
|
||||
@@ -6,7 +6,7 @@ import io
|
||||
|
||||
|
||||
def gen_qr_png_data(maildomain):
|
||||
url = f"DCACCOUNT:https://{maildomain}/cgi-bin/newemail.py"
|
||||
url = f"DCACCOUNT:https://{maildomain}/new"
|
||||
image = gen_qr(maildomain, url)
|
||||
temp = io.BytesIO()
|
||||
image.save(temp, format="png")
|
||||
|
||||
@@ -45,8 +45,22 @@ http {
|
||||
default_type text/plain;
|
||||
}
|
||||
|
||||
# add cgi-bin support
|
||||
include /usr/share/doc/fcgiwrap/examples/nginx.conf;
|
||||
location /new {
|
||||
if ($request_method = GET) {
|
||||
# Redirect to Delta Chat,
|
||||
# which will in turn do a POST request.
|
||||
return 301 dcaccount:https://{{ config.domain_name }}/new;
|
||||
}
|
||||
|
||||
fastcgi_pass unix:/run/fcgiwrap.socket;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/newemail.py;
|
||||
}
|
||||
|
||||
# Old URL for compatibility with e.g. printed QR codes.
|
||||
location /cgi-bin/newemail.py {
|
||||
return 301 /new;
|
||||
}
|
||||
}
|
||||
|
||||
# Redirect www. to non-www
|
||||
|
||||
@@ -1,30 +1,60 @@
|
||||
rules {
|
||||
REJECT_DKIM_SPF {
|
||||
## Reject on missing or invalid DKIM signatures.
|
||||
##
|
||||
## We require DKIM signature on incoming mails regardless of DMARC policy.
|
||||
|
||||
# R_DKIM_REJECT: DKIM reject inserted by `dkim` module.
|
||||
REJECT_INVALID_DKIM {
|
||||
action = "reject";
|
||||
# Reject if
|
||||
# - R_DKIM_RJECT: DKIM reject inserted by `dkim` module.
|
||||
# - R_DKIM_PERMFAIL: permanent failure inserted by `dkim` module e.g. no DKIM DNS record found.
|
||||
# - No DKIM signing (R_DKIM_NA symbol inserted by `dkim` module)
|
||||
#
|
||||
# - SPF failure (R_SPF_FAIL)
|
||||
# - SPF permanent failure, e.g. failed to resolve DNS record referenced from SPF (R_SPF_PERMFAIL)
|
||||
#
|
||||
# - DMARC policy failure (DMARC_POLICY_REJECT)
|
||||
#
|
||||
# Do not reject if:
|
||||
# - R_DKIM_TEMPFAIL, it is a DNS resolution failure
|
||||
# and we do not want to lose messages because of faulty network.
|
||||
#
|
||||
# - R_SPF_SOFTFAIL
|
||||
# - R_SPF_NEUTRAL
|
||||
# - R_SPF_DNSFAIL
|
||||
# - R_SPF_NA
|
||||
#
|
||||
# - DMARC_DNSFAIL
|
||||
# - DMARC_NA
|
||||
# - DMARC_POLICY_SOFTFAIL
|
||||
# - DMARC_POLICY_QUARANTINE
|
||||
# - DMARC_BAD_POLICY
|
||||
expression = "R_DKIM_REJECT | R_DKIM_PERMFAIL | R_DKIM_NA | R_SPF_FAIL | R_SPF_PERMFAIL | DMARC_POLICY_REJECT";
|
||||
expression = "R_DKIM_REJECT";
|
||||
message = "Rejected due to invalid DKIM signature";
|
||||
}
|
||||
|
||||
# R_DKIM_PERMFAIL: permanent failure inserted by `dkim` module e.g. no DKIM DNS record found.
|
||||
REJECT_PERMFAIL_DKIM {
|
||||
action = "reject";
|
||||
expression = "R_DKIM_PERMFAIL";
|
||||
message = "Rejected due to missing DKIM DNS entry";
|
||||
}
|
||||
|
||||
# No DKIM signature (R_DKIM_NA symbol inserted by `dkim` module).
|
||||
REJECT_MISSING_DKIM {
|
||||
action = "reject";
|
||||
expression = "R_DKIM_NA";
|
||||
message = "Rejected due to missing DKIM signature";
|
||||
}
|
||||
|
||||
|
||||
## Reject on SPF failure.
|
||||
|
||||
# - SPF failure (R_SPF_FAIL)
|
||||
# - SPF permanent failure, e.g. failed to resolve DNS record referenced from SPF (R_SPF_PERMFAIL)
|
||||
REJECT_SPF {
|
||||
action = "reject";
|
||||
expression = "R_SPF_FAIL | R_SPF_PERMFAIL";
|
||||
message = "Rejected due to failed SPF check";
|
||||
}
|
||||
|
||||
# Reject on DMARC policy check failure.
|
||||
REJECT_DMARC {
|
||||
action = "reject";
|
||||
expression = "DMARC_POLICY_REJECT";
|
||||
message = "Rejected due to DMARC policy";
|
||||
}
|
||||
|
||||
|
||||
# Do not reject if:
|
||||
# - R_DKIM_TEMPFAIL, it is a DNS resolution failure
|
||||
# and we do not want to lose messages because of faulty network.
|
||||
#
|
||||
# - R_SPF_SOFTFAIL
|
||||
# - R_SPF_NEUTRAL
|
||||
# - R_SPF_DNSFAIL
|
||||
# - R_SPF_NA
|
||||
#
|
||||
# - DMARC_DNSFAIL
|
||||
# - DMARC_NA
|
||||
# - DMARC_POLICY_SOFTFAIL
|
||||
# - DMARC_POLICY_QUARANTINE
|
||||
# - DMARC_BAD_POLICY
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ Welcome to instant, interoperable and [privacy-preserving](privacy.html) messagi
|
||||
|
||||
👉 **Tap** or scan this QR code to get a random `@{{config.mail_domain}}` e-mail address
|
||||
|
||||
<a href="DCACCOUNT:https://{{ config.mail_domain }}/cgi-bin/newemail.py">
|
||||
<a href="DCACCOUNT:https://{{ config.mail_domain }}/new">
|
||||
<img width=300 style="float: none;" src="qr-chatmail-invite-{{config.mail_domain}}.png" /></a>
|
||||
|
||||
🐣 **Choose** your Avatar and Name
|
||||
|
||||
Reference in New Issue
Block a user