diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2023-10-04 01:35:06 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2023-10-04 02:11:48 +0200 |
commit | 1a64deeb894dc95e2645a75771732c6cc53a79ad (patch) | |
tree | 1b9df4838f894577a09b9b260151756272efeb53 /systems/eldiron/ejabberd | |
parent | fa25ffd4583cc362075cd5e1b4130f33306103f0 (diff) | |
download | Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.tar.gz Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.tar.zst Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.zip |
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
Diffstat (limited to 'systems/eldiron/ejabberd')
-rw-r--r-- | systems/eldiron/ejabberd/default.nix | 141 | ||||
-rw-r--r-- | systems/eldiron/ejabberd/ejabberd.yml | 231 | ||||
-rwxr-xr-x | systems/eldiron/ejabberd/warn_xmpp_email.py | 112 |
3 files changed, 484 insertions, 0 deletions
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 @@ | |||
1 | { lib, pkgs, config, mypackages-lib, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.ejabberd; | ||
4 | in | ||
5 | { | ||
6 | options.myServices = { | ||
7 | ejabberd.enable = lib.mkOption { | ||
8 | type = lib.types.bool; | ||
9 | default = false; | ||
10 | description = '' | ||
11 | Whether to enable ejabberd service. | ||
12 | ''; | ||
13 | }; | ||
14 | }; | ||
15 | |||
16 | config = lib.mkIf cfg.enable { | ||
17 | myServices.dns.zones."immae.fr" = with config.myServices.dns.helpers; | ||
18 | lib.mkMerge [ | ||
19 | { | ||
20 | extraConfig = '' | ||
21 | notify yes; | ||
22 | ''; | ||
23 | slaves = [ "raito" ]; | ||
24 | emailPolicies."".receive = true; | ||
25 | } | ||
26 | zoneHeader | ||
27 | mailMX | ||
28 | (mailCommon "immae.fr") | ||
29 | (ips servers.eldiron.ips.main) | ||
30 | { | ||
31 | ns = [ "immae" "raito" ]; | ||
32 | CAA = letsencrypt; | ||
33 | subdomains.www = ips servers.eldiron.ips.production; | ||
34 | subdomains.im = ips servers.eldiron.ips.main; | ||
35 | subdomains.conference = ips servers.eldiron.ips.main; | ||
36 | subdomains.pubsub = ips servers.eldiron.ips.main; | ||
37 | subdomains.proxy = ips servers.eldiron.ips.main; | ||
38 | subdomains.upload = ips servers.eldiron.ips.main; | ||
39 | subdomains._xmppconnect.TXT = [ | ||
40 | "_xmpp-client-xbosh=https://im.immae.fr/bosh" | ||
41 | "_xmpp-client-websocket=wss://im.immae.fr/ws" | ||
42 | ]; | ||
43 | } | ||
44 | ]; | ||
45 | |||
46 | security.acme.certs = { | ||
47 | "ejabberd" = { | ||
48 | group = "ejabberd"; | ||
49 | domain = "eldiron.immae.eu"; | ||
50 | keyType = "rsa4096"; | ||
51 | postRun = '' | ||
52 | systemctl restart ejabberd.service | ||
53 | ''; | ||
54 | extraDomainNames = [ "immae.fr" "conference.immae.fr" "proxy.immae.fr" "pubsub.immae.fr" "upload.immae.fr" ]; | ||
55 | }; | ||
56 | }; | ||
57 | networking.firewall.allowedTCPPorts = [ 5222 5269 ]; | ||
58 | myServices.websites.tools.im.enable = true; | ||
59 | systemd.services.ejabberd.postStop = '' | ||
60 | rm /var/log/ejabberd/erl_crash*.dump | ||
61 | ''; | ||
62 | secrets.keys = { | ||
63 | "ejabberd/psql.yml" = { | ||
64 | permissions = "0400"; | ||
65 | user = "ejabberd"; | ||
66 | group = "ejabberd"; | ||
67 | text = '' | ||
68 | sql_type: pgsql | ||
69 | sql_server: "localhost" | ||
70 | sql_database: "${config.myEnv.jabber.postgresql.database}" | ||
71 | sql_username: "${config.myEnv.jabber.postgresql.user}" | ||
72 | sql_password: "${config.myEnv.jabber.postgresql.password}" | ||
73 | ''; | ||
74 | }; | ||
75 | "ejabberd/host.yml" = { | ||
76 | permissions = "0400"; | ||
77 | user = "ejabberd"; | ||
78 | group = "ejabberd"; | ||
79 | text = '' | ||
80 | host_config: | ||
81 | "immae.fr": | ||
82 | domain_certfile: "${config.security.acme.certs.ejabberd.directory}/full.pem" | ||
83 | auth_method: [ldap] | ||
84 | ldap_servers: ["${config.myEnv.jabber.ldap.host}"] | ||
85 | ldap_encrypt: tls | ||
86 | ldap_rootdn: "${config.myEnv.jabber.ldap.dn}" | ||
87 | ldap_password: "${config.myEnv.jabber.ldap.password}" | ||
88 | ldap_base: "${config.myEnv.jabber.ldap.base}" | ||
89 | ldap_uids: | ||
90 | uid: "%u" | ||
91 | immaeXmppUid: "%u" | ||
92 | ldap_filter: "${config.myEnv.jabber.ldap.filter}" | ||
93 | ''; | ||
94 | }; | ||
95 | }; | ||
96 | users.users.ejabberd.extraGroups = [ "keys" ]; | ||
97 | services.ejabberd = { | ||
98 | package = pkgs.ejabberd.override { withPgsql = true; }; | ||
99 | imagemagick = true; | ||
100 | enable = true; | ||
101 | ctlConfig = '' | ||
102 | ERLANG_NODE=ejabberd@localhost | ||
103 | ''; | ||
104 | configFile = pkgs.runCommand "ejabberd.yml" { | ||
105 | certificatePrivateKeyAndFullChain = "${config.security.acme.certs.ejabberd.directory}/full.pem"; | ||
106 | certificateCA = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; | ||
107 | sql_config_file = config.secrets.fullPaths."ejabberd/psql.yml"; | ||
108 | host_config_file = config.secrets.fullPaths."ejabberd/host.yml"; | ||
109 | } '' | ||
110 | substituteAll ${./ejabberd.yml} $out | ||
111 | ''; | ||
112 | }; | ||
113 | secrets.keys."postfix/scripts/ejabberd-env" = { | ||
114 | user = "postfixscripts"; | ||
115 | group = "root"; | ||
116 | permissions = "0400"; | ||
117 | text = builtins.toJSON { | ||
118 | jid = "notify_bot@immae.fr"; | ||
119 | password = "{{ .xmpp.notify_bot }}"; | ||
120 | }; | ||
121 | }; | ||
122 | services.postfix.extraAliases = let | ||
123 | nixpkgs = builtins.fetchTarball { | ||
124 | url = "https://github.com/NixOS/nixpkgs/archive/840c782d507d60aaa49aa9e3f6d0b0e780912742.tar.gz"; | ||
125 | sha256 = "14q3kvnmgz19pgwyq52gxx0cs90ddf24pnplmq33pdddbb6c51zn"; | ||
126 | }; | ||
127 | pkgs' = import nixpkgs { inherit (pkgs) system; overlays = []; }; | ||
128 | warn_xmpp_email = scriptEnv: pkgs'.runCommand "warn_xmpp_email" { | ||
129 | inherit scriptEnv; | ||
130 | pythonEnv = pkgs'.python3.withPackages (ps: [ | ||
131 | ps.unidecode ps.slixmpp | ||
132 | ]); | ||
133 | } '' | ||
134 | substituteAll ${./warn_xmpp_email.py} $out | ||
135 | chmod a+x $out | ||
136 | ''; | ||
137 | in '' | ||
138 | ejabberd: "|${mypackages-lib.postfixScript pkgs "ejabberd" (warn_xmpp_email config.secrets.fullPaths."postfix/scripts/ejabberd-env")}" | ||
139 | ''; | ||
140 | }; | ||
141 | } | ||
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 @@ | |||
1 | ### | ||
2 | ### ejabberd configuration file | ||
3 | ### | ||
4 | ### The parameters used in this configuration file are explained at | ||
5 | ### | ||
6 | ### https://docs.ejabberd.im/admin/configuration | ||
7 | ### | ||
8 | ### The configuration file is written in YAML. | ||
9 | ### ******************************************************* | ||
10 | ### ******* !!! WARNING !!! ******* | ||
11 | ### ******* YAML IS INDENTATION SENSITIVE ******* | ||
12 | ### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY ******* | ||
13 | ### ******************************************************* | ||
14 | ### Refer to http://en.wikipedia.org/wiki/YAML for the brief description. | ||
15 | ### However, ejabberd treats different literals as different types: | ||
16 | ### | ||
17 | ### - unquoted or single-quoted strings. They are called "atoms". | ||
18 | ### Example: dog, 'Jupiter', '3.14159', YELLOW | ||
19 | ### | ||
20 | ### - numeric literals. Example: 3, -45.0, .0 | ||
21 | ### | ||
22 | ### - quoted or folded strings. | ||
23 | ### Examples of quoted string: "Lizzard", "orange". | ||
24 | ### Example of folded string: | ||
25 | ### > Art thou not Romeo, | ||
26 | ### and a Montague? | ||
27 | ### | ||
28 | |||
29 | hosts: | ||
30 | - "immae.fr" | ||
31 | |||
32 | loglevel: 4 | ||
33 | log_rotate_size: 10485760 | ||
34 | log_rotate_date: "" | ||
35 | log_rotate_count: 1 | ||
36 | log_rate_limit: 100 | ||
37 | |||
38 | certfiles: | ||
39 | - "@certificatePrivateKeyAndFullChain@" | ||
40 | |||
41 | listen: | ||
42 | - | ||
43 | port: 5222 | ||
44 | ip: "::" | ||
45 | module: ejabberd_c2s | ||
46 | max_stanza_size: 262144 | ||
47 | shaper: c2s_shaper | ||
48 | access: c2s | ||
49 | starttls_required: true | ||
50 | - | ||
51 | port: 5269 | ||
52 | ip: "::" | ||
53 | module: ejabberd_s2s_in | ||
54 | max_stanza_size: 524288 | ||
55 | - | ||
56 | port: 5280 | ||
57 | ip: "127.0.0.1" | ||
58 | module: ejabberd_http | ||
59 | request_handlers: | ||
60 | "/admin": ejabberd_web_admin | ||
61 | "/api": mod_http_api | ||
62 | "/bosh": mod_bosh | ||
63 | "/captcha": ejabberd_captcha | ||
64 | "/upload": mod_http_upload | ||
65 | "/ws": ejabberd_http_ws | ||
66 | tls: false | ||
67 | |||
68 | s2s_use_starttls: optional | ||
69 | s2s_cafile: "@certificateCA@" | ||
70 | |||
71 | default_db: sql | ||
72 | include_config_file: @sql_config_file@ | ||
73 | include_config_file: @host_config_file@ | ||
74 | new_sql_schema: true | ||
75 | |||
76 | acl: | ||
77 | admin: | ||
78 | - user: "ismael@immae.fr" | ||
79 | local: | ||
80 | user_regexp: "" | ||
81 | loopback: | ||
82 | ip: | ||
83 | - "127.0.0.0/8" | ||
84 | - "::1/128" | ||
85 | - "::FFFF:127.0.0.1/128" | ||
86 | |||
87 | access_rules: | ||
88 | local: | ||
89 | - allow: local | ||
90 | c2s: | ||
91 | - deny: blocked | ||
92 | - allow | ||
93 | announce: | ||
94 | - allow: admin | ||
95 | configure: | ||
96 | - allow: admin | ||
97 | muc_admin: | ||
98 | - allow: admin | ||
99 | muc_create: | ||
100 | - allow: local | ||
101 | muc: | ||
102 | - allow | ||
103 | pubsub_createnode: | ||
104 | - allow: local | ||
105 | register: | ||
106 | - deny | ||
107 | trusted_network: | ||
108 | - allow: loopback | ||
109 | |||
110 | api_permissions: | ||
111 | "console commands": | ||
112 | from: | ||
113 | - ejabberd_ctl | ||
114 | who: all | ||
115 | what: "*" | ||
116 | "admin access": | ||
117 | who: | ||
118 | - acl: admin | ||
119 | - oauth: | ||
120 | - scope: "ejabberd:admin" | ||
121 | - acl: admin | ||
122 | what: | ||
123 | - "*" | ||
124 | - "!stop" | ||
125 | - "!start" | ||
126 | "public commands": | ||
127 | who: | ||
128 | - ip: | ||
129 | - "0.0.0.0" | ||
130 | - "::" | ||
131 | what: | ||
132 | - "status" | ||
133 | - "connected_users_number" | ||
134 | |||
135 | shaper: | ||
136 | normal: 1000 | ||
137 | fast: 50000 | ||
138 | |||
139 | shaper_rules: | ||
140 | max_user_sessions: 10 | ||
141 | max_user_offline_messages: | ||
142 | - 5000: admin | ||
143 | - 100 | ||
144 | c2s_shaper: | ||
145 | - none: admin | ||
146 | - normal | ||
147 | s2s_shaper: fast | ||
148 | |||
149 | modules: | ||
150 | mod_adhoc: {} | ||
151 | mod_admin_extra: {} | ||
152 | mod_announce: | ||
153 | access: announce | ||
154 | mod_avatar: {} | ||
155 | mod_blocking: {} | ||
156 | mod_bosh: {} | ||
157 | mod_caps: {} | ||
158 | mod_carboncopy: {} | ||
159 | mod_client_state: {} | ||
160 | mod_configure: {} | ||
161 | mod_disco: {} | ||
162 | mod_fail2ban: {} | ||
163 | mod_http_api: {} | ||
164 | mod_http_upload: | ||
165 | put_url: "https://im.immae.fr/upload" | ||
166 | custom_headers: | ||
167 | "Access-Control-Allow-Origin": "*" | ||
168 | "Access-Control-Allow-Methods": "OPTIONS, HEAD, GET, PUT, POST" | ||
169 | "Access-Control-Allow-Headers": "Content-Type" | ||
170 | mod_last: {} | ||
171 | mod_mam: | ||
172 | default: always | ||
173 | mod_muc: | ||
174 | access: | ||
175 | - allow | ||
176 | access_admin: | ||
177 | - allow: admin | ||
178 | access_create: muc_create | ||
179 | access_persistent: muc_create | ||
180 | default_room_options: | ||
181 | mam: true | ||
182 | mod_muc_admin: {} | ||
183 | mod_offline: | ||
184 | access_max_user_messages: max_user_offline_messages | ||
185 | mod_ping: {} | ||
186 | mod_privacy: {} | ||
187 | mod_private: {} | ||
188 | mod_proxy65: | ||
189 | access: local | ||
190 | max_connections: 5 | ||
191 | mod_pubsub: | ||
192 | access_createnode: pubsub_createnode | ||
193 | plugins: | ||
194 | - "flat" | ||
195 | - "pep" | ||
196 | force_node_config: | ||
197 | ## Change from "whitelist" to "open" to enable OMEMO support | ||
198 | ## See https://github.com/processone/ejabberd/issues/2425 | ||
199 | "eu.siacs.conversations.axolotl.*": | ||
200 | access_model: open | ||
201 | ## Avoid buggy clients to make their bookmarks public | ||
202 | "storage:bookmarks": | ||
203 | access_model: whitelist | ||
204 | mod_push: {} | ||
205 | mod_push_keepalive: {} | ||
206 | mod_register: | ||
207 | ## Only accept registration requests from the "trusted" | ||
208 | ## network (see access_rules section above). | ||
209 | ## Think twice before enabling registration from any | ||
210 | ## address. See the Jabber SPAM Manifesto for details: | ||
211 | ## https://github.com/ge0rg/jabber-spam-fighting-manifesto | ||
212 | ip_access: trusted_network | ||
213 | access: register | ||
214 | mod_roster: | ||
215 | versioning: true | ||
216 | mod_s2s_dialback: {} | ||
217 | mod_shared_roster: {} | ||
218 | mod_stats: {} | ||
219 | mod_stream_mgmt: | ||
220 | resend_on_timeout: if_offline | ||
221 | mod_time: {} | ||
222 | mod_vcard: {} | ||
223 | mod_vcard_xupdate: {} | ||
224 | mod_version: | ||
225 | show_os: false | ||
226 | |||
227 | ### Local Variables: | ||
228 | ### mode: yaml | ||
229 | ### End: | ||
230 | ### vim: set filetype=yaml tabstop=8 | ||
231 | |||
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 @@ | |||
1 | #!@pythonEnv@/bin/python3 | ||
2 | |||
3 | import sys | ||
4 | import json | ||
5 | import slixmpp | ||
6 | import asyncio | ||
7 | import logging | ||
8 | import io | ||
9 | |||
10 | CONFIG = json.load(open("@scriptEnv@", "r")) | ||
11 | |||
12 | def sanitize(string): | ||
13 | import re | ||
14 | from unidecode import unidecode | ||
15 | return re.compile(r"[^-.A-Za-z0-9_]").sub("_", unidecode(string)) | ||
16 | |||
17 | def parse_email(): | ||
18 | import email | ||
19 | from email.header import decode_header | ||
20 | |||
21 | mail = email.message_from_file(sys.stdin) | ||
22 | try: | ||
23 | d = decode_header(mail["subject"])[0] | ||
24 | if d[1] is not None: | ||
25 | subject = d[0].decode(d[1]) | ||
26 | else: | ||
27 | subject = d[0] | ||
28 | except Exception as e: | ||
29 | subject = mail["subject"] | ||
30 | sender = mail["from"] | ||
31 | recipient = mail["X-Original-To"] | ||
32 | |||
33 | body = "" | ||
34 | html = None | ||
35 | files = {} | ||
36 | for part in mail.walk(): | ||
37 | if part.get_content_type() == "text/plain": | ||
38 | body += "\n-------------------\n" | ||
39 | try: | ||
40 | body += part.get_payload(decode=True).decode(encoding=part.get_content_charset() or "utf-8") | ||
41 | except Exception as e: | ||
42 | body += part.get_payload(decode=False) | ||
43 | elif part.get_content_type() == "text/html": | ||
44 | html = part.get_payload(decode=True) | ||
45 | elif part.get_content_type() != "text/html" and\ | ||
46 | part.get_content_maintype() != "multipart": | ||
47 | |||
48 | filename = part.get_filename() or "{}.dat".format(part["Content-ID"]) | ||
49 | files[sanitize(filename)] = (part.get_content_type(), part.get_payload(decode=True)) | ||
50 | |||
51 | return [body, html, subject, sender, recipient, files] | ||
52 | |||
53 | [body, html, subject, sender, recipient, files] = parse_email() | ||
54 | |||
55 | class Bot(slixmpp.ClientXMPP): | ||
56 | def __init__(self, jid, password, body, html, subject, sender, recipient, files): | ||
57 | super().__init__(jid, password) | ||
58 | |||
59 | self.got_error = False | ||
60 | self.body = body | ||
61 | self.html = html | ||
62 | self.subject = subject | ||
63 | self.sender = sender | ||
64 | self.recipient = recipient | ||
65 | self.files = files | ||
66 | self.register_plugin('xep_0363') | ||
67 | self.add_event_handler("session_start", self.session_start) | ||
68 | self.add_event_handler("message", self.message) | ||
69 | |||
70 | @asyncio.coroutine | ||
71 | def session_start(self, event): | ||
72 | files = [] | ||
73 | if self.html is not None: | ||
74 | url = yield from self['xep_0363'].upload_file( | ||
75 | "mail.html", | ||
76 | content_type="text/html", | ||
77 | input_file=io.BytesIO(self.html)) | ||
78 | files.append(("HTML version", url)) | ||
79 | for f in self.files: | ||
80 | url = yield from self['xep_0363'].upload_file( | ||
81 | f, | ||
82 | content_type=self.files[f][0], | ||
83 | input_file=io.BytesIO(self.files[f][1]) | ||
84 | ) | ||
85 | files.append((f, url)) | ||
86 | |||
87 | text = """ | ||
88 | New e-mail message from {sender} | ||
89 | Subject: {subject} | ||
90 | {body} | ||
91 | """.format(sender=self.sender, subject=self.subject, body=self.body) | ||
92 | if len(files) > 0: | ||
93 | text += "\n\nAttachments:" | ||
94 | for f in files: | ||
95 | text += "\n{}: {}".format(f[0], f[1]) | ||
96 | self.send_message(mto=self.recipient, mbody=text, msubject=self.subject, mtype='message') | ||
97 | yield from asyncio.sleep(5) | ||
98 | self.disconnect() | ||
99 | |||
100 | @asyncio.coroutine | ||
101 | def message(self, msg): | ||
102 | if msg["type"] == "error": | ||
103 | self.got_error = True | ||
104 | |||
105 | logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s') | ||
106 | xmpp = Bot(CONFIG["jid"], CONFIG["password"], body, html, subject, sender, recipient, files) | ||
107 | xmpp.connect() | ||
108 | xmpp.process(forever=False) | ||
109 | if xmpp.got_error: | ||
110 | sys.exit(1) | ||
111 | else: | ||
112 | sys.exit(0) | ||