aboutsummaryrefslogtreecommitdiff
path: root/systems/eldiron/ejabberd
diff options
context:
space:
mode:
Diffstat (limited to 'systems/eldiron/ejabberd')
-rw-r--r--systems/eldiron/ejabberd/default.nix141
-rw-r--r--systems/eldiron/ejabberd/ejabberd.yml231
-rwxr-xr-xsystems/eldiron/ejabberd/warn_xmpp_email.py112
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, ... }:
2let
3 cfg = config.myServices.ejabberd;
4in
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
29hosts:
30 - "immae.fr"
31
32loglevel: 4
33log_rotate_size: 10485760
34log_rotate_date: ""
35log_rotate_count: 1
36log_rate_limit: 100
37
38certfiles:
39 - "@certificatePrivateKeyAndFullChain@"
40
41listen:
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
68s2s_use_starttls: optional
69s2s_cafile: "@certificateCA@"
70
71default_db: sql
72include_config_file: @sql_config_file@
73include_config_file: @host_config_file@
74new_sql_schema: true
75
76acl:
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
87access_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
110api_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
135shaper:
136 normal: 1000
137 fast: 50000
138
139shaper_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
149modules:
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
3import sys
4import json
5import slixmpp
6import asyncio
7import logging
8import io
9
10CONFIG = json.load(open("@scriptEnv@", "r"))
11
12def sanitize(string):
13 import re
14 from unidecode import unidecode
15 return re.compile(r"[^-.A-Za-z0-9_]").sub("_", unidecode(string))
16
17def 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
55class 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 = """
88New e-mail message from {sender}
89Subject: {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
105logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s')
106xmpp = Bot(CONFIG["jid"], CONFIG["password"], body, html, subject, sender, recipient, files)
107xmpp.connect()
108xmpp.process(forever=False)
109if xmpp.got_error:
110 sys.exit(1)
111else:
112 sys.exit(0)