From 1a64deeb894dc95e2645a75771732c6cc53a79ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Wed, 4 Oct 2023 01:35:06 +0200 Subject: Squash changes containing private information There were a lot of changes since the previous commit, but a lot of them contained personnal information about users. All thos changes got stashed into a single commit (history is kept in a different place) and private information was moved in a separate private repository --- systems/eldiron/ejabberd/default.nix | 141 +++++++++++++++++ systems/eldiron/ejabberd/ejabberd.yml | 231 ++++++++++++++++++++++++++++ systems/eldiron/ejabberd/warn_xmpp_email.py | 112 ++++++++++++++ 3 files changed, 484 insertions(+) create mode 100644 systems/eldiron/ejabberd/default.nix create mode 100644 systems/eldiron/ejabberd/ejabberd.yml create mode 100755 systems/eldiron/ejabberd/warn_xmpp_email.py (limited to 'systems/eldiron/ejabberd') diff --git a/systems/eldiron/ejabberd/default.nix b/systems/eldiron/ejabberd/default.nix new file mode 100644 index 0000000..5268516 --- /dev/null +++ b/systems/eldiron/ejabberd/default.nix @@ -0,0 +1,141 @@ +{ lib, pkgs, config, mypackages-lib, ... }: +let + cfg = config.myServices.ejabberd; +in +{ + options.myServices = { + ejabberd.enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable ejabberd service. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + myServices.dns.zones."immae.fr" = with config.myServices.dns.helpers; + lib.mkMerge [ + { + extraConfig = '' + notify yes; + ''; + slaves = [ "raito" ]; + emailPolicies."".receive = true; + } + zoneHeader + mailMX + (mailCommon "immae.fr") + (ips servers.eldiron.ips.main) + { + ns = [ "immae" "raito" ]; + CAA = letsencrypt; + subdomains.www = ips servers.eldiron.ips.production; + subdomains.im = ips servers.eldiron.ips.main; + subdomains.conference = ips servers.eldiron.ips.main; + subdomains.pubsub = ips servers.eldiron.ips.main; + subdomains.proxy = ips servers.eldiron.ips.main; + subdomains.upload = ips servers.eldiron.ips.main; + subdomains._xmppconnect.TXT = [ + "_xmpp-client-xbosh=https://im.immae.fr/bosh" + "_xmpp-client-websocket=wss://im.immae.fr/ws" + ]; + } + ]; + + security.acme.certs = { + "ejabberd" = { + group = "ejabberd"; + domain = "eldiron.immae.eu"; + keyType = "rsa4096"; + postRun = '' + systemctl restart ejabberd.service + ''; + extraDomainNames = [ "immae.fr" "conference.immae.fr" "proxy.immae.fr" "pubsub.immae.fr" "upload.immae.fr" ]; + }; + }; + networking.firewall.allowedTCPPorts = [ 5222 5269 ]; + myServices.websites.tools.im.enable = true; + systemd.services.ejabberd.postStop = '' + rm /var/log/ejabberd/erl_crash*.dump + ''; + secrets.keys = { + "ejabberd/psql.yml" = { + permissions = "0400"; + user = "ejabberd"; + group = "ejabberd"; + text = '' + sql_type: pgsql + sql_server: "localhost" + sql_database: "${config.myEnv.jabber.postgresql.database}" + sql_username: "${config.myEnv.jabber.postgresql.user}" + sql_password: "${config.myEnv.jabber.postgresql.password}" + ''; + }; + "ejabberd/host.yml" = { + permissions = "0400"; + user = "ejabberd"; + group = "ejabberd"; + text = '' + host_config: + "immae.fr": + domain_certfile: "${config.security.acme.certs.ejabberd.directory}/full.pem" + auth_method: [ldap] + ldap_servers: ["${config.myEnv.jabber.ldap.host}"] + ldap_encrypt: tls + ldap_rootdn: "${config.myEnv.jabber.ldap.dn}" + ldap_password: "${config.myEnv.jabber.ldap.password}" + ldap_base: "${config.myEnv.jabber.ldap.base}" + ldap_uids: + uid: "%u" + immaeXmppUid: "%u" + ldap_filter: "${config.myEnv.jabber.ldap.filter}" + ''; + }; + }; + users.users.ejabberd.extraGroups = [ "keys" ]; + services.ejabberd = { + package = pkgs.ejabberd.override { withPgsql = true; }; + imagemagick = true; + enable = true; + ctlConfig = '' + ERLANG_NODE=ejabberd@localhost + ''; + configFile = pkgs.runCommand "ejabberd.yml" { + certificatePrivateKeyAndFullChain = "${config.security.acme.certs.ejabberd.directory}/full.pem"; + certificateCA = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; + sql_config_file = config.secrets.fullPaths."ejabberd/psql.yml"; + host_config_file = config.secrets.fullPaths."ejabberd/host.yml"; + } '' + substituteAll ${./ejabberd.yml} $out + ''; + }; + secrets.keys."postfix/scripts/ejabberd-env" = { + user = "postfixscripts"; + group = "root"; + permissions = "0400"; + text = builtins.toJSON { + jid = "notify_bot@immae.fr"; + password = "{{ .xmpp.notify_bot }}"; + }; + }; + services.postfix.extraAliases = let + nixpkgs = builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/840c782d507d60aaa49aa9e3f6d0b0e780912742.tar.gz"; + sha256 = "14q3kvnmgz19pgwyq52gxx0cs90ddf24pnplmq33pdddbb6c51zn"; + }; + pkgs' = import nixpkgs { inherit (pkgs) system; overlays = []; }; + warn_xmpp_email = scriptEnv: pkgs'.runCommand "warn_xmpp_email" { + inherit scriptEnv; + pythonEnv = pkgs'.python3.withPackages (ps: [ + ps.unidecode ps.slixmpp + ]); + } '' + substituteAll ${./warn_xmpp_email.py} $out + chmod a+x $out + ''; + in '' + ejabberd: "|${mypackages-lib.postfixScript pkgs "ejabberd" (warn_xmpp_email config.secrets.fullPaths."postfix/scripts/ejabberd-env")}" + ''; + }; +} diff --git a/systems/eldiron/ejabberd/ejabberd.yml b/systems/eldiron/ejabberd/ejabberd.yml new file mode 100644 index 0000000..82ac35b --- /dev/null +++ b/systems/eldiron/ejabberd/ejabberd.yml @@ -0,0 +1,231 @@ +### +### ejabberd configuration file +### +### The parameters used in this configuration file are explained at +### +### https://docs.ejabberd.im/admin/configuration +### +### The configuration file is written in YAML. +### ******************************************************* +### ******* !!! WARNING !!! ******* +### ******* YAML IS INDENTATION SENSITIVE ******* +### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY ******* +### ******************************************************* +### Refer to http://en.wikipedia.org/wiki/YAML for the brief description. +### However, ejabberd treats different literals as different types: +### +### - unquoted or single-quoted strings. They are called "atoms". +### Example: dog, 'Jupiter', '3.14159', YELLOW +### +### - numeric literals. Example: 3, -45.0, .0 +### +### - quoted or folded strings. +### Examples of quoted string: "Lizzard", "orange". +### Example of folded string: +### > Art thou not Romeo, +### and a Montague? +### + +hosts: + - "immae.fr" + +loglevel: 4 +log_rotate_size: 10485760 +log_rotate_date: "" +log_rotate_count: 1 +log_rate_limit: 100 + +certfiles: + - "@certificatePrivateKeyAndFullChain@" + +listen: + - + port: 5222 + ip: "::" + module: ejabberd_c2s + max_stanza_size: 262144 + shaper: c2s_shaper + access: c2s + starttls_required: true + - + port: 5269 + ip: "::" + module: ejabberd_s2s_in + max_stanza_size: 524288 + - + port: 5280 + ip: "127.0.0.1" + module: ejabberd_http + request_handlers: + "/admin": ejabberd_web_admin + "/api": mod_http_api + "/bosh": mod_bosh + "/captcha": ejabberd_captcha + "/upload": mod_http_upload + "/ws": ejabberd_http_ws + tls: false + +s2s_use_starttls: optional +s2s_cafile: "@certificateCA@" + +default_db: sql +include_config_file: @sql_config_file@ +include_config_file: @host_config_file@ +new_sql_schema: true + +acl: + admin: + - user: "ismael@immae.fr" + local: + user_regexp: "" + loopback: + ip: + - "127.0.0.0/8" + - "::1/128" + - "::FFFF:127.0.0.1/128" + +access_rules: + local: + - allow: local + c2s: + - deny: blocked + - allow + announce: + - allow: admin + configure: + - allow: admin + muc_admin: + - allow: admin + muc_create: + - allow: local + muc: + - allow + pubsub_createnode: + - allow: local + register: + - deny + trusted_network: + - allow: loopback + +api_permissions: + "console commands": + from: + - ejabberd_ctl + who: all + what: "*" + "admin access": + who: + - acl: admin + - oauth: + - scope: "ejabberd:admin" + - acl: admin + what: + - "*" + - "!stop" + - "!start" + "public commands": + who: + - ip: + - "0.0.0.0" + - "::" + what: + - "status" + - "connected_users_number" + +shaper: + normal: 1000 + fast: 50000 + +shaper_rules: + max_user_sessions: 10 + max_user_offline_messages: + - 5000: admin + - 100 + c2s_shaper: + - none: admin + - normal + s2s_shaper: fast + +modules: + mod_adhoc: {} + mod_admin_extra: {} + mod_announce: + access: announce + mod_avatar: {} + mod_blocking: {} + mod_bosh: {} + mod_caps: {} + mod_carboncopy: {} + mod_client_state: {} + mod_configure: {} + mod_disco: {} + mod_fail2ban: {} + mod_http_api: {} + mod_http_upload: + put_url: "https://im.immae.fr/upload" + custom_headers: + "Access-Control-Allow-Origin": "*" + "Access-Control-Allow-Methods": "OPTIONS, HEAD, GET, PUT, POST" + "Access-Control-Allow-Headers": "Content-Type" + mod_last: {} + mod_mam: + default: always + mod_muc: + access: + - allow + access_admin: + - allow: admin + access_create: muc_create + access_persistent: muc_create + default_room_options: + mam: true + mod_muc_admin: {} + mod_offline: + access_max_user_messages: max_user_offline_messages + mod_ping: {} + mod_privacy: {} + mod_private: {} + mod_proxy65: + access: local + max_connections: 5 + mod_pubsub: + access_createnode: pubsub_createnode + plugins: + - "flat" + - "pep" + force_node_config: + ## Change from "whitelist" to "open" to enable OMEMO support + ## See https://github.com/processone/ejabberd/issues/2425 + "eu.siacs.conversations.axolotl.*": + access_model: open + ## Avoid buggy clients to make their bookmarks public + "storage:bookmarks": + access_model: whitelist + mod_push: {} + mod_push_keepalive: {} + mod_register: + ## Only accept registration requests from the "trusted" + ## network (see access_rules section above). + ## Think twice before enabling registration from any + ## address. See the Jabber SPAM Manifesto for details: + ## https://github.com/ge0rg/jabber-spam-fighting-manifesto + ip_access: trusted_network + access: register + mod_roster: + versioning: true + mod_s2s_dialback: {} + mod_shared_roster: {} + mod_stats: {} + mod_stream_mgmt: + resend_on_timeout: if_offline + mod_time: {} + mod_vcard: {} + mod_vcard_xupdate: {} + mod_version: + show_os: false + +### Local Variables: +### mode: yaml +### End: +### vim: set filetype=yaml tabstop=8 + diff --git a/systems/eldiron/ejabberd/warn_xmpp_email.py b/systems/eldiron/ejabberd/warn_xmpp_email.py new file mode 100755 index 0000000..d482b43 --- /dev/null +++ b/systems/eldiron/ejabberd/warn_xmpp_email.py @@ -0,0 +1,112 @@ +#!@pythonEnv@/bin/python3 + +import sys +import json +import slixmpp +import asyncio +import logging +import io + +CONFIG = json.load(open("@scriptEnv@", "r")) + +def sanitize(string): + import re + from unidecode import unidecode + return re.compile(r"[^-.A-Za-z0-9_]").sub("_", unidecode(string)) + +def parse_email(): + import email + from email.header import decode_header + + mail = email.message_from_file(sys.stdin) + try: + d = decode_header(mail["subject"])[0] + if d[1] is not None: + subject = d[0].decode(d[1]) + else: + subject = d[0] + except Exception as e: + subject = mail["subject"] + sender = mail["from"] + recipient = mail["X-Original-To"] + + body = "" + html = None + files = {} + for part in mail.walk(): + if part.get_content_type() == "text/plain": + body += "\n-------------------\n" + try: + body += part.get_payload(decode=True).decode(encoding=part.get_content_charset() or "utf-8") + except Exception as e: + body += part.get_payload(decode=False) + elif part.get_content_type() == "text/html": + html = part.get_payload(decode=True) + elif part.get_content_type() != "text/html" and\ + part.get_content_maintype() != "multipart": + + filename = part.get_filename() or "{}.dat".format(part["Content-ID"]) + files[sanitize(filename)] = (part.get_content_type(), part.get_payload(decode=True)) + + return [body, html, subject, sender, recipient, files] + +[body, html, subject, sender, recipient, files] = parse_email() + +class Bot(slixmpp.ClientXMPP): + def __init__(self, jid, password, body, html, subject, sender, recipient, files): + super().__init__(jid, password) + + self.got_error = False + self.body = body + self.html = html + self.subject = subject + self.sender = sender + self.recipient = recipient + self.files = files + self.register_plugin('xep_0363') + self.add_event_handler("session_start", self.session_start) + self.add_event_handler("message", self.message) + + @asyncio.coroutine + def session_start(self, event): + files = [] + if self.html is not None: + url = yield from self['xep_0363'].upload_file( + "mail.html", + content_type="text/html", + input_file=io.BytesIO(self.html)) + files.append(("HTML version", url)) + for f in self.files: + url = yield from self['xep_0363'].upload_file( + f, + content_type=self.files[f][0], + input_file=io.BytesIO(self.files[f][1]) + ) + files.append((f, url)) + + text = """ +New e-mail message from {sender} +Subject: {subject} +{body} +""".format(sender=self.sender, subject=self.subject, body=self.body) + if len(files) > 0: + text += "\n\nAttachments:" + for f in files: + text += "\n{}: {}".format(f[0], f[1]) + self.send_message(mto=self.recipient, mbody=text, msubject=self.subject, mtype='message') + yield from asyncio.sleep(5) + self.disconnect() + + @asyncio.coroutine + def message(self, msg): + if msg["type"] == "error": + self.got_error = True + +logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s') +xmpp = Bot(CONFIG["jid"], CONFIG["password"], body, html, subject, sender, recipient, files) +xmpp.connect() +xmpp.process(forever=False) +if xmpp.got_error: + sys.exit(1) +else: + sys.exit(0) -- cgit v1.2.3