Mercurial > libervia-backend
annotate libervia/backend/plugins/plugin_comp_email_gateway/__init__.py @ 4350:6baea959dc33
component email gateway: convert `autocrypt` header:
Autocrypt header must be transmitted in both directions to allow opportunistic end-to-end
encryption with this protocol.
Moved email validation regex to `tools/common/regex.py`, as it can be used in other
locations.
rel 456
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 28 Feb 2025 09:23:35 +0100 |
parents | 54df67d5646c |
children | f43cbceba2a0 |
rev | line source |
---|---|
4303 | 1 #!/usr/bin/env python3 |
2 | |
3 # Libervia Email Gateway Component | |
4 # Copyright (C) 2009-2024 Jérôme Poisson (goffi@goffi.org) | |
5 | |
6 # This program is free software: you can redistribute it and/or modify | |
7 # it under the terms of the GNU Affero General Public License as published by | |
8 # the Free Software Foundation, either version 3 of the License, or | |
9 # (at your option) any later version. | |
10 | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU Affero General Public License for more details. | |
15 | |
16 # You should have received a copy of the GNU Affero General Public License | |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
19 import base64 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
20 from email import encoders |
4303 | 21 from email.header import decode_header |
22 from email.message import EmailMessage | |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
23 from email.mime.application import MIMEApplication |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
24 from email.mime.multipart import MIMEMultipart |
4303 | 25 from email.mime.text import MIMEText |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
26 from email.utils import formataddr, getaddresses, parseaddr |
4303 | 27 from functools import partial |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
28 import hashlib |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
29 from pathlib import Path |
4303 | 30 import re |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
31 import shutil |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
32 import tempfile |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
33 from typing import NamedTuple, TYPE_CHECKING, cast |
4303 | 34 |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
35 from pydantic import BaseModel |
4303 | 36 from twisted.internet import defer, reactor |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
37 from twisted.internet.threads import deferToThread |
4303 | 38 from twisted.mail import smtp |
39 from twisted.words.protocols.jabber import jid | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
40 from twisted.words.protocols.jabber import error as jabber_error |
4303 | 41 from twisted.words.protocols.jabber.error import StanzaError |
42 from twisted.words.protocols.jabber.xmlstream import XMPPHandler | |
43 from twisted.words.xish import domish | |
44 from wokkel import data_form, disco, iwokkel | |
45 from zope.interface import implementer | |
46 | |
47 from libervia.backend.core import exceptions | |
48 from libervia.backend.core.constants import Const as C | |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
49 from libervia.backend.core.core_types import SatXMPPComponent, SatXMPPEntity |
4303 | 50 from libervia.backend.core.i18n import D_, _ |
51 from libervia.backend.core.log import getLogger | |
52 from libervia.backend.memory.persistent import LazyPersistentBinaryDict | |
53 from libervia.backend.memory.sqla import select | |
54 from libervia.backend.memory.sqla_mapping import PrivateIndBin | |
55 from libervia.backend.models.core import MessageData | |
4338
7c0b7ecb816f
component email gateway: Add a pubsub service:
Goffi <goffi@goffi.org>
parents:
4337
diff
changeset
|
56 from libervia.backend.plugins.plugin_comp_email_gateway.pubsub_service import ( |
7c0b7ecb816f
component email gateway: Add a pubsub service:
Goffi <goffi@goffi.org>
parents:
4337
diff
changeset
|
57 EmailGWPubsubService, |
7c0b7ecb816f
component email gateway: Add a pubsub service:
Goffi <goffi@goffi.org>
parents:
4337
diff
changeset
|
58 ) |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
59 from libervia.backend.plugins.plugin_exp_gre import GRE, GetDataHandler |
4350
6baea959dc33
component email gateway: convert `autocrypt` header:
Goffi <goffi@goffi.org>
parents:
4347
diff
changeset
|
60 from libervia.backend.plugins.plugin_sec_gre_encrypter_openpgp import NS_GRE_OPENPGP |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
61 from libervia.backend.plugins.plugin_sec_gre_formatter_mime import NS_GRE_MIME |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
62 from libervia.backend.plugins.plugin_xep_0033 import ( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
63 AddressType, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
64 AddressesData, |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
65 RECIPIENT_FIELDS, |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
66 ) |
4303 | 67 from libervia.backend.plugins.plugin_xep_0077 import XEP_0077 |
68 from libervia.backend.plugins.plugin_xep_0106 import XEP_0106 | |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
69 from libervia.backend.plugins.plugin_xep_0131 import HeadersData, Urgency, XEP_0131 |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
70 from libervia.backend.plugins.plugin_xep_0373 import binary_to_ascii_armor |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
71 from libervia.backend.plugins.plugin_xep_0498 import XEP_0498 |
4350
6baea959dc33
component email gateway: convert `autocrypt` header:
Goffi <goffi@goffi.org>
parents:
4347
diff
changeset
|
72 from libervia.backend.tools.common import regex |
4303 | 73 from libervia.backend.tools.utils import aio |
74 | |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
75 from .imap import IMAPClientFactory |
4303 | 76 from .models import Credentials, UserData |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
77 |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
78 if TYPE_CHECKING: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
79 from libervia.backend.core.main import LiberviaBackend |
4303 | 80 |
81 | |
82 log = getLogger(__name__) | |
83 | |
84 IMPORT_NAME = "email-gateway" | |
85 NAME = "Libervia Email Gateway" | |
86 | |
87 PLUGIN_INFO = { | |
88 C.PI_NAME: "Email Gateway Component", | |
89 C.PI_IMPORT_NAME: IMPORT_NAME, | |
90 C.PI_MODES: [C.PLUG_MODE_COMPONENT], | |
91 C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT, | |
92 C.PI_PROTOCOLS: [], | |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
93 C.PI_DEPENDENCIES: [ |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
94 "XEP-0033", "XEP-0077", "XEP-0106", "XEP-0498", "GRE", "GRE-MIME", "GRE-OpenPGP" |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
95 ], |
4303 | 96 C.PI_RECOMMENDATIONS: [], |
97 C.PI_MAIN: "EmailGatewayComponent", | |
98 C.PI_HANDLER: C.BOOL_TRUE, | |
99 C.PI_DESCRIPTION: D_( | |
100 "Gateway to handle email. Usual emails are handled as message, while mailing " | |
101 "lists are converted to pubsub blogs." | |
102 ), | |
103 } | |
104 | |
105 CONF_SECTION = f"component {IMPORT_NAME}" | |
106 PREFIX_KEY_CREDENTIALS = "CREDENTIALS_" | |
107 KEY_CREDENTIALS = f"{PREFIX_KEY_CREDENTIALS}{{from_jid}}" | |
108 | |
109 | |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
110 class FileMetadata(NamedTuple): |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
111 path: Path |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
112 hash: str |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
113 size: int |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
114 |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
115 |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
116 class SendMailExtra(BaseModel): |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
117 addresses: AddressesData | None = None |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
118 headers: HeadersData | None = None |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
119 |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
120 |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
121 class EmailGatewayComponent(GetDataHandler): |
4303 | 122 IMPORT_NAME = IMPORT_NAME |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
123 gre_formatters = [NS_GRE_MIME] |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
124 gre_encrypters = [NS_GRE_OPENPGP] |
4303 | 125 verbose = 0 |
126 | |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
127 def __init__(self, host: "LiberviaBackend") -> None: |
4303 | 128 self.host = host |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
129 self.client: SatXMPPComponent | None = None |
4303 | 130 self.initalized = False |
131 self.storage: LazyPersistentBinaryDict | None = None | |
132 self._iq_register = cast(XEP_0077, host.plugins["XEP-0077"]) | |
133 self._iq_register.register_handler( | |
134 self._on_registration_form, self._on_registration_submit | |
135 ) | |
136 self._e = cast(XEP_0106, host.plugins["XEP-0106"]) | |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
137 self._shim = cast(XEP_0131, host.plugins["XEP-0131"]) |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
138 self._pfs = cast(XEP_0498, host.plugins["XEP-0498"]) |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
139 self._gre = cast(GRE, host.plugins["GRE"]) |
4303 | 140 # TODO: For the moment, all credentials are kept in cache; we should only keep the |
141 # X latest. | |
142 self.users_data: dict[jid.JID, UserData] = {} | |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
143 self.files_path = self.host.get_local_path(None, C.FILES_DIR) |
4303 | 144 host.trigger.add_with_check( |
145 "message_received", self, self._message_received_trigger, priority=-1000 | |
146 ) | |
147 | |
148 async def _init(self) -> None: | |
149 """Initialisation done after profile is connected""" | |
150 assert self.client is not None | |
151 self.client.identities.append(disco.DiscoIdentity("gateway", "smtp", NAME)) | |
152 self.storage = LazyPersistentBinaryDict(IMPORT_NAME, self.client.profile) | |
153 await self.connect_registered_users() | |
154 | |
155 @aio | |
156 async def get_registered_users(self) -> dict[jid.JID, Credentials]: | |
157 """Retrieve credentials for all registered users | |
158 | |
159 @return: a mapping from user JID to credentials data. | |
160 """ | |
161 assert self.client is not None | |
162 profile_id = self.host.memory.storage.profiles[self.client.profile] | |
163 async with self.host.memory.storage.session() as session: | |
164 query = select(PrivateIndBin).where( | |
165 PrivateIndBin.profile_id == profile_id, | |
166 PrivateIndBin.namespace == IMPORT_NAME, | |
167 PrivateIndBin.key.startswith(PREFIX_KEY_CREDENTIALS), | |
168 ) | |
169 result = await session.execute(query) | |
170 return { | |
171 jid.JID(p.key[len(PREFIX_KEY_CREDENTIALS) :]): p.value | |
172 for p in result.scalars() | |
173 } | |
174 | |
175 async def connect_registered_users(self) -> None: | |
176 """Connected users already registered to the gateway.""" | |
177 registered_data = await self.get_registered_users() | |
178 for user_jid, credentials in registered_data.items(): | |
179 user_data = self.users_data[user_jid] = UserData(credentials=credentials) | |
180 if not credentials["imap_success"]: | |
181 log.warning( | |
182 f"Ignoring unsuccessful IMAP credentials of {user_jid}. This user " | |
183 "won't receive message from this gateway." | |
184 ) | |
185 else: | |
186 try: | |
187 await self.connect_imap(user_jid, user_data) | |
188 except Exception as e: | |
189 log.warning(f"Can't connect {user_jid} to IMAP: {e}.") | |
190 else: | |
191 log.debug(f"Connection to IMAP server successful for {user_jid}.") | |
192 | |
4338
7c0b7ecb816f
component email gateway: Add a pubsub service:
Goffi <goffi@goffi.org>
parents:
4337
diff
changeset
|
193 def get_handler(self, __) -> tuple[XMPPHandler, XMPPHandler]: |
7c0b7ecb816f
component email gateway: Add a pubsub service:
Goffi <goffi@goffi.org>
parents:
4337
diff
changeset
|
194 return EmailGatewayHandler(), EmailGWPubsubService(self) |
4303 | 195 |
196 async def profile_connecting(self, client: SatXMPPEntity) -> None: | |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
197 assert isinstance(client, SatXMPPComponent) |
4303 | 198 self.client = client |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
199 self._gre.register_get_data_handler(client, self) |
4303 | 200 if not self.initalized: |
201 await self._init() | |
202 self.initalized = True | |
203 | |
204 def _message_received_trigger( | |
205 self, | |
206 client: SatXMPPEntity, | |
207 message_elt: domish.Element, | |
208 post_treat: defer.Deferred, | |
209 ) -> bool: | |
210 """add the gateway workflow on post treatment""" | |
211 if client != self.client: | |
212 return True | |
213 post_treat.addCallback( | |
214 lambda mess_data: defer.ensureDeferred( | |
215 self.on_message(client, mess_data, message_elt) | |
216 ) | |
217 ) | |
218 return True | |
219 | |
220 async def on_message( | |
221 self, client: SatXMPPEntity, mess_data: MessageData, message_elt: domish.Element | |
222 ) -> dict: | |
223 """Called once message has been parsed | |
224 | |
225 @param client: Client session. | |
226 @param mess_data: Message data. | |
227 @return: Message data. | |
228 """ | |
229 if client != self.client: | |
230 return mess_data | |
231 from_jid = mess_data["from"].userhostJID() | |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
232 extra_kw = {} |
4303 | 233 if mess_data["type"] not in ("chat", "normal"): |
234 log.warning(f"ignoring message with unexpected type: {mess_data}") | |
235 return mess_data | |
236 if not client.is_local(from_jid): | |
237 log.warning(f"ignoring non local message: {mess_data}") | |
238 return mess_data | |
239 if not mess_data["to"].user: | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
240 addresses = mess_data["extra"].get("addresses") |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
241 if not addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
242 log.warning(f"ignoring message addressed to gateway itself: {mess_data}") |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
243 return mess_data |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
244 else: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
245 to_email = None |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
246 extra_kw["addresses"] = addresses |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
247 else: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
248 try: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
249 to_email = self._e.unescape(mess_data["to"].user) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
250 except ValueError: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
251 raise exceptions.DataError( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
252 f'Invalid "to" JID, can\'t send message: {message_elt.toXml()}.' |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
253 ) |
4303 | 254 |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
255 encrypted_payload = self._gre.get_encrypted_payload(message_elt) |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
256 |
4303 | 257 try: |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
258 if encrypted_payload is not None: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
259 # We convert the base64 datat to ASCII Armor |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
260 encrypted_binary = base64.b64decode(encrypted_payload) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
261 encrypted_payload = binary_to_ascii_armor(encrypted_binary) |
4303 | 262 |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
263 assert to_email is not None |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
264 subject = "This is an encrypted message." |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
265 outer = MIMEMultipart('encrypted', protocol="application/pgp-encrypted") |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
266 outer["Subject"] = subject |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
267 # FIXME: use credentials here. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
268 outer["From"] = from_jid.userhost() |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
269 outer["To"] = to_email |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
270 outer["Content-Type"] = "multipart/encrypted; protocol=\"application/pgp-encrypted\"" |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
271 version = MIMEApplication( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
272 "Version: 1\n", |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
273 _subtype='pgp-encrypted', |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
274 _encoder=encoders.encode_7or8bit |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
275 ) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
276 version["Content-Description"] = "PGP/MIME version identification" |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
277 encrypted_part = MIMEApplication( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
278 encrypted_payload, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
279 _subtype='octet-stream', |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
280 _encoder=encoders.encode_7or8bit |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
281 ) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
282 encrypted_part["Content-Description"] = "OpenPGP encrypted message" |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
283 encrypted_part["Content-Type"] = "application/octet-stream; name=\"encrypted.asc\"" |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
284 encrypted_part["Content-Disposition"] = "inline; filename=\"encrypted.asc\"" |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
285 outer.attach(version) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
286 outer.attach(encrypted_part) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
287 body = outer.as_bytes() |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
288 await self.send_encrypted_email( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
289 from_jid=from_jid, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
290 to_email=to_email, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
291 body=body, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
292 extra=SendMailExtra(**extra_kw) if extra_kw else None, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
293 ) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
294 else: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
295 self._shim.move_keywords_to_headers(mess_data["extra"]) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
296 headers = mess_data["extra"].get("headers") |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
297 if headers: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
298 extra_kw["headers"] = headers |
4303 | 299 |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
300 try: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
301 body_lang, body = next(iter(mess_data["message"].items())) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
302 except (KeyError, StopIteration): |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
303 log.warning(f"No body found: {mess_data}") |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
304 body_lang, body = "", "" |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
305 try: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
306 subject_lang, subject = next(iter(mess_data["subject"].items())) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
307 except (KeyError, StopIteration): |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
308 subject_lang, subject = "", None |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
309 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
310 if not body and not subject: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
311 log.warning(f"Ignoring empty message: {mess_data}") |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
312 return mess_data |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
313 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
314 await self.send_email( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
315 from_jid=from_jid, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
316 to_email=to_email, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
317 body=body, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
318 subject=subject, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
319 extra=SendMailExtra(**extra_kw) if extra_kw else None, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
320 ) |
4303 | 321 except exceptions.UnknownEntityError: |
322 log.warning(f"Can't send message, user {from_jid} is not registered.") | |
323 message_error_elt = StanzaError( | |
324 "subscription-required", | |
325 text="User need to register to the gateway before sending emails.", | |
326 ).toResponse(message_elt) | |
327 await client.a_send(message_error_elt) | |
328 raise exceptions.CancelError("User not registered.") | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
329 except StanzaError as e: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
330 log.warning("Can't send message: {e}") |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
331 message_error_elt = e.toResponse(message_elt) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
332 await client.a_send(message_error_elt) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
333 raise exceptions.CancelError("Can't send message: {e}") |
4303 | 334 |
335 return mess_data | |
336 | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
337 def jid_to_email( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
338 self, client: SatXMPPEntity, address_jid: jid.JID, credentials: dict[str, str] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
339 ) -> str: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
340 """Convert a JID to an email address. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
341 |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
342 If JID is from the gateway, email address will be extracted. Otherwise, the |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
343 gateway email will be used, with XMPP address specified in name part. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
344 |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
345 @param address_jid: JID of the recipient. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
346 @param credentials: Sender credentials. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
347 @return: Email address. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
348 """ |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
349 if address_jid and address_jid.host.endswith(str(client.jid)): |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
350 return self._e.unescape(address_jid.user) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
351 else: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
352 email_address = credentials["user_email"] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
353 if address_jid: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
354 email_address = formataddr((f"xmpp:{address_jid}", email_address)) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
355 return email_address |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
356 |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
357 async def get_credentials(self, from_jid: jid.JID) -> Credentials: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
358 """Retrieve user credentials from a bare JID. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
359 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
360 @param from_jid: Entity to retrieve credentials from. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
361 @return: Credentials. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
362 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
363 @raise UnknownEntityError: If no credentials are found for the given JID. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
364 """ |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
365 # We need a bare jid. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
366 assert self.storage is not None |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
367 assert not from_jid.resource |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
368 try: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
369 user_data = self.users_data[from_jid] |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
370 except KeyError: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
371 key = KEY_CREDENTIALS.format(from_jid=from_jid) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
372 credentials = await self.storage.get(key) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
373 if credentials is None: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
374 raise exceptions.UnknownEntityError( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
375 f"No credentials found for {from_jid}." |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
376 ) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
377 self.users_data[from_jid] = UserData(credentials) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
378 else: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
379 credentials = user_data.credentials |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
380 return credentials |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
381 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
382 async def send_encrypted_email( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
383 self, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
384 from_jid: jid.JID, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
385 to_email: str | None, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
386 body: bytes, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
387 extra: SendMailExtra | None = None, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
388 ) -> None: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
389 """Send an email using sender credentials. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
390 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
391 Credentials will be retrieved from cache or database. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
392 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
393 @param from_jid: Bare JID of the sender. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
394 @param to_email: Email address of the recipient. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
395 @param body: Encrypted body of the email. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
396 @param extra: Extra data. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
397 """ |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
398 assert self.client is not None |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
399 assert isinstance(body, bytes) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
400 credentials = await self.get_credentials(from_jid) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
401 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
402 sender_domain = credentials["user_email"].split("@", 1)[-1] |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
403 recipients = [] |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
404 if to_email is not None: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
405 recipients.append(to_email.encode()) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
406 if extra is not None and extra.addresses is not None: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
407 for address in extra.addresses.addresses: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
408 recipient_jid = address.jid |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
409 if recipient_jid is None: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
410 continue |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
411 recipient_email = self.jid_to_email( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
412 self.client, recipient_jid, credentials |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
413 ) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
414 recipients.append(recipient_email.encode()) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
415 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
416 if not recipients: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
417 raise exceptions.InternalError("No recipient found.") |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
418 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
419 await smtp.sendmail( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
420 credentials["smtp_host"].encode(), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
421 credentials["user_email"].encode(), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
422 recipients, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
423 body, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
424 senderDomainName=sender_domain, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
425 port=int(credentials["smtp_port"]), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
426 username=credentials["smtp_username"].encode(), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
427 password=credentials["smtp_password"].encode(), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
428 requireAuthentication=True, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
429 # TODO: only STARTTLS is supported right now, implicit TLS should be supported |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
430 # too. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
431 requireTransportSecurity=True, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
432 ) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
433 |
4303 | 434 async def send_email( |
435 self, | |
436 from_jid: jid.JID, | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
437 to_email: str | None, |
4303 | 438 body: str, |
439 subject: str | None, | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
440 extra: SendMailExtra | None = None, |
4303 | 441 ) -> None: |
442 """Send an email using sender credentials. | |
443 | |
444 Credentials will be retrieve from cache, or database. | |
445 | |
446 @param from_jid: Bare JID of the sender. | |
447 @param to_email: Email address of the destinee. | |
448 @param body: Body of the email. | |
449 @param subject: Subject of the email. | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
450 @param extra: Extra data. |
4303 | 451 |
452 @raise exceptions.UnknownEntityError: Credentials for "from_jid" can't be found. | |
453 """ | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
454 assert self.client is not None |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
455 if extra is None: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
456 extra = SendMailExtra() |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
457 if to_email is None and (extra.addresses is None or not extra.addresses.to): |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
458 raise exceptions.InternalError( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
459 '"to_email" can\'t be None if there is no "to" address!' |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
460 ) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
461 |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
462 credentials = await self.get_credentials(from_jid) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
463 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
464 if isinstance(body, bytes): |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
465 assert to_email is not None |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
466 sender_domain = credentials["user_email"].split("@", 1)[-1] |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
467 await smtp.sendmail( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
468 credentials["smtp_host"].encode(), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
469 credentials["user_email"].encode(), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
470 [to_email.encode()], |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
471 body, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
472 senderDomainName=sender_domain, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
473 port=int(credentials["smtp_port"]), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
474 username=credentials["smtp_username"].encode(), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
475 password=credentials["smtp_password"].encode(), |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
476 requireAuthentication=True, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
477 # TODO: only STARTTLS is supported right now, implicit TLS should be supported |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
478 # too. |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
479 requireTransportSecurity=True, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
480 ) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
481 return |
4303 | 482 |
483 msg = MIMEText(body, "plain", "UTF-8") | |
484 if subject is not None: | |
485 msg["Subject"] = subject | |
486 msg["From"] = formataddr( | |
487 (credentials["user_name"] or None, credentials["user_email"]) | |
488 ) | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
489 if extra.addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
490 assert extra.addresses.to |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
491 main_to_address = extra.addresses.to[0] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
492 assert main_to_address.jid |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
493 to_email = self.jid_to_email(self.client, main_to_address.jid, credentials) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
494 for field in RECIPIENT_FIELDS: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
495 addresses = getattr(extra.addresses, field) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
496 if not addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
497 continue |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
498 for address in addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
499 if not address.delivered and ( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
500 address.jid is None or address.jid.host != str(self.client.jid) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
501 ): |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
502 log.warning( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
503 "Received undelivered message to external JID, this is not " |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
504 "allowed! Cancelling the message sending." |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
505 ) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
506 stanza_err = jabber_error.StanzaError( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
507 "forbidden", |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
508 text="Multicasting (XEP-0033 addresses) can only be used " |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
509 "with JID from this gateway, not external ones. " |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
510 f" {address.jid} can't be delivered by this gateway and " |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
511 "should be delivered by server instead.", |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
512 ) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
513 raise stanza_err |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
514 email_addresses = [ |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
515 self.jid_to_email(self.client, address.jid, credentials) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
516 for address in addresses |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
517 if address.jid |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
518 ] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
519 if email_addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
520 msg[field.upper()] = ", ".join(email_addresses) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
521 else: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
522 assert to_email is not None |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
523 msg["To"] = to_email |
4303 | 524 |
525 sender_domain = credentials["user_email"].split("@", 1)[-1] | |
526 | |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
527 if extra.headers: |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
528 if extra.headers.keywords: |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
529 msg["Keywords"] = extra.headers.keywords |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
530 if extra.headers.urgency: |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
531 urgency = extra.headers.urgency |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
532 if urgency == Urgency.medium: |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
533 importance = "normal" |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
534 else: |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
535 importance = urgency |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
536 msg["Importance"] = importance |
4350
6baea959dc33
component email gateway: convert `autocrypt` header:
Goffi <goffi@goffi.org>
parents:
4347
diff
changeset
|
537 if getattr(extra.headers, "autocrypt", None): |
6baea959dc33
component email gateway: convert `autocrypt` header:
Goffi <goffi@goffi.org>
parents:
4347
diff
changeset
|
538 msg["Autocrypt"] = extra.headers.autocrypt |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
539 |
4303 | 540 await smtp.sendmail( |
541 credentials["smtp_host"].encode(), | |
542 credentials["user_email"].encode(), | |
543 [to_email.encode()], | |
544 msg.as_bytes(), | |
545 senderDomainName=sender_domain, | |
546 port=int(credentials["smtp_port"]), | |
547 username=credentials["smtp_username"].encode(), | |
548 password=credentials["smtp_password"].encode(), | |
549 requireAuthentication=True, | |
550 # TODO: only STARTTLS is supported right now, implicit TLS should be supported | |
551 # too. | |
552 requireTransportSecurity=True, | |
553 ) | |
554 | |
555 async def _on_registration_form( | |
556 self, client: SatXMPPEntity, iq_elt: domish.Element | |
557 ) -> tuple[bool, data_form.Form] | None: | |
558 if client != self.client: | |
559 return | |
560 assert self.storage is not None | |
561 from_jid = jid.JID(iq_elt["from"]) | |
562 key = KEY_CREDENTIALS.format(from_jid=from_jid.userhost()) | |
563 credentials = await self.storage.get(key) or {} | |
564 | |
565 form = data_form.Form(formType="form", title="IMAP/SMTP Credentials") | |
566 | |
567 # Add instructions | |
568 form.instructions = [ | |
569 D_( | |
570 "Please provide your IMAP and SMTP credentials to configure the " | |
571 "connection." | |
572 ) | |
573 ] | |
574 | |
575 # Add identity fields | |
576 form.addField( | |
577 data_form.Field( | |
578 fieldType="text-single", | |
579 var="user_name", | |
580 label="User Name", | |
581 desc=D_('The display name to use in the "From" field of sent emails.'), | |
582 value=credentials.get("user_name"), | |
583 required=True, | |
584 ) | |
585 ) | |
586 | |
587 form.addField( | |
588 data_form.Field( | |
589 fieldType="text-single", | |
590 var="user_email", | |
591 label="User Email", | |
592 desc=D_('The email address to use in the "From" field of sent emails.'), | |
593 value=credentials.get("user_email"), | |
594 required=True, | |
595 ) | |
596 ) | |
597 | |
598 # Add fields for IMAP credentials | |
599 form.addField( | |
600 data_form.Field( | |
601 fieldType="text-single", | |
602 var="imap_host", | |
603 label="IMAP Host", | |
604 desc=D_("IMAP server hostname or IP address"), | |
605 value=credentials.get("imap_host"), | |
606 required=True, | |
607 ) | |
608 ) | |
609 form.addField( | |
610 data_form.Field( | |
611 fieldType="text-single", | |
612 var="imap_port", | |
613 label="IMAP Port", | |
614 desc=D_("IMAP server port (default: 993)"), | |
615 value=credentials.get("imap_port", "993"), | |
616 ) | |
617 ) | |
618 form.addField( | |
619 data_form.Field( | |
620 fieldType="text-single", | |
621 var="imap_username", | |
622 label="IMAP Username", | |
623 desc=D_("Username for IMAP authentication"), | |
624 value=credentials.get("imap_username"), | |
625 required=True, | |
626 ) | |
627 ) | |
628 form.addField( | |
629 data_form.Field( | |
630 fieldType="text-private", | |
631 var="imap_password", | |
632 label="IMAP Password", | |
633 desc=D_("Password for IMAP authentication"), | |
634 value=credentials.get("imap_password"), | |
635 required=True, | |
636 ) | |
637 ) | |
638 | |
639 # Add fields for SMTP credentials | |
640 form.addField( | |
641 data_form.Field( | |
642 fieldType="text-single", | |
643 var="smtp_host", | |
644 label="SMTP Host", | |
645 desc=D_("SMTP server hostname or IP address"), | |
646 value=credentials.get("smtp_host"), | |
647 required=True, | |
648 ) | |
649 ) | |
650 form.addField( | |
651 data_form.Field( | |
652 fieldType="text-single", | |
653 var="smtp_port", | |
654 label="SMTP Port", | |
655 desc=D_("SMTP server port (default: 587)"), | |
656 value=credentials.get("smtp_port", "587"), | |
657 ) | |
658 ) | |
659 form.addField( | |
660 data_form.Field( | |
661 fieldType="text-single", | |
662 var="smtp_username", | |
663 label="SMTP Username", | |
664 desc=D_("Username for SMTP authentication"), | |
665 value=credentials.get("smtp_username"), | |
666 required=True, | |
667 ) | |
668 ) | |
669 form.addField( | |
670 data_form.Field( | |
671 fieldType="text-private", | |
672 var="smtp_password", | |
673 label="SMTP Password", | |
674 desc=D_("Password for SMTP authentication"), | |
675 value=credentials.get("smtp_password"), | |
676 required=True, | |
677 ) | |
678 ) | |
679 | |
680 return bool(credentials), form | |
681 | |
682 def validate_field( | |
683 self, | |
684 form: data_form.Form, | |
685 key: str, | |
686 field_type: str, | |
687 min_value: int | None = None, | |
688 max_value: int | None = None, | |
689 default: str | int | None = None, | |
690 ) -> None: | |
691 """Validate a single field. | |
692 | |
693 @param form: The form containing the fields. | |
694 @param key: The key of the field to validate. | |
695 @param field_type: The expected type of the field value. | |
696 @param min_value: Optional minimum value for integer fields. | |
697 @param max_value: Optional maximum value for integer fields. | |
698 @param default: Default value to use if the field is missing. | |
699 @raise StanzaError: If the field value is invalid or missing. | |
700 """ | |
701 field = form.fields.get(key) | |
702 if field is None: | |
703 if default is None: | |
704 raise StanzaError("bad-request", text=f"{key} is required") | |
705 field = data_form.Field(var=key, value=str(default)) | |
706 form.addField(field) | |
707 | |
708 value = field.value | |
709 if field_type == "int": | |
710 try: | |
711 value = int(value) | |
712 if (min_value is not None and value < min_value) or ( | |
713 max_value is not None and value > max_value | |
714 ): | |
715 raise ValueError | |
716 except (ValueError, TypeError): | |
717 raise StanzaError("bad-request", text=f"Invalid value for {key}: {value}") | |
718 elif field_type == "str": | |
719 if not isinstance(value, str): | |
720 raise StanzaError("bad-request", text=f"Invalid value for {key}: {value}") | |
721 | |
722 # Basic email validation for user_email field | |
723 if key == "user_email": | |
724 # XXX: This is a minimal check. A complete email validation is notoriously | |
725 # difficult. | |
4350
6baea959dc33
component email gateway: convert `autocrypt` header:
Goffi <goffi@goffi.org>
parents:
4347
diff
changeset
|
726 if not regex.RE_EMAIL.match(value): |
4303 | 727 raise StanzaError( |
728 "bad-request", text=f"Invalid email address: {value}" | |
729 ) | |
730 | |
731 def validate_imap_smtp_form(self, submit_form: data_form.Form) -> None: | |
732 """Validate the submitted IMAP/SMTP credentials form. | |
733 | |
734 @param submit_form: The submitted form containing IMAP/SMTP credentials. | |
735 @raise StanzaError: If any of the values are invalid. | |
736 """ | |
737 # Validate identity fields | |
738 self.validate_field(submit_form, "user_name", "str") | |
739 self.validate_field(submit_form, "user_email", "str") | |
740 | |
741 # Validate IMAP fields | |
742 self.validate_field(submit_form, "imap_host", "str") | |
743 self.validate_field( | |
744 submit_form, "imap_port", "int", min_value=1, max_value=65535, default=993 | |
745 ) | |
746 self.validate_field(submit_form, "imap_username", "str") | |
747 self.validate_field(submit_form, "imap_password", "str") | |
748 | |
749 # Validate SMTP fields | |
750 self.validate_field(submit_form, "smtp_host", "str") | |
751 self.validate_field( | |
752 submit_form, "smtp_port", "int", min_value=1, max_value=65535, default=587 | |
753 ) | |
754 self.validate_field(submit_form, "smtp_username", "str") | |
755 self.validate_field(submit_form, "smtp_password", "str") | |
756 | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
757 def email_to_jid( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
758 self, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
759 client: SatXMPPEntity, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
760 user_email: str, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
761 user_jid: jid.JID, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
762 email_name: str, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
763 email_addr: str, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
764 ) -> tuple[jid.JID, str | None]: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
765 """Convert an email address to a JID and extract the name if present. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
766 |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
767 @param client: Client session. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
768 @param user_email: Email address of the gateway user. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
769 @param user_jid: JID of the gateway user. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
770 @param email_name: Email associated name. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
771 @param email_addr: Email address. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
772 @return: Tuple of JID and name (if present). |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
773 """ |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
774 email_name = email_name.strip() |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
775 if email_name.startswith("xmpp:"): |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
776 return jid.JID(email_name[5:]), None |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
777 elif email_addr == user_email: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
778 return (user_jid, None) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
779 else: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
780 return ( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
781 jid.JID(None, (self._e.escape(email_addr), client.jid.host, None)), |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
782 email_name or None, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
783 ) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
784 |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
785 async def on_new_email( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
786 self, user_data: UserData, user_jid: jid.JID, email: EmailMessage |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
787 ) -> None: |
4303 | 788 """Called when a new message has been received. |
789 | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
790 @param user_data: user data, used to map registered user email to corresponding |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
791 jid. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
792 @param user_jid: JID of the recipient. |
4303 | 793 @param email: Parsed email. |
794 """ | |
795 assert self.client is not None | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
796 user_email = user_data.credentials["user_email"] |
4303 | 797 name, email_addr = parseaddr(email["from"]) |
798 email_addr = email_addr.lower() | |
799 from_jid = jid.JID(None, (self._e.escape(email_addr), self.client.jid.host, None)) | |
800 | |
801 # Get the email body | |
802 body_mime = email.get_body(("plain",)) | |
803 if body_mime is not None: | |
804 charset = body_mime.get_content_charset() or "utf-8" | |
805 body = body_mime.get_payload(decode=True).decode(charset, errors="replace") | |
806 else: | |
807 log.warning(f"No body found in email:\n{email}") | |
808 body = "" | |
809 | |
810 # Decode the subject | |
811 subject = email.get("subject") | |
812 if subject: | |
813 decoded_subject = decode_header(subject) | |
814 subject = "".join( | |
815 [ | |
816 part.decode(encoding or "utf-8") if isinstance(part, bytes) else part | |
817 for part, encoding in decoded_subject | |
818 ] | |
819 ).strip() | |
820 else: | |
821 subject = None | |
822 | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
823 # Parse recipient fields |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
824 kwargs = {} |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
825 for field in RECIPIENT_FIELDS: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
826 email_addresses = email.get_all(field) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
827 if email_addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
828 jids_and_names = [ |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
829 self.email_to_jid(self.client, user_email, user_jid, name, addr) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
830 for name, addr in getaddresses(email_addresses) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
831 ] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
832 kwargs[field] = [ |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
833 AddressType(jid=jid, desc=name) for jid, name in jids_and_names |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
834 ] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
835 |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
836 # At least "to" header should be set, so kwargs should never be empty |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
837 assert kwargs |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
838 addresses_data = AddressesData(**kwargs) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
839 |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
840 # Parse reply-to field |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
841 reply_to_addresses = email.get_all("reply-to") |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
842 if reply_to_addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
843 jids_with_names = [ |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
844 self.email_to_jid(self.client, user_email, user_jid, name, addr) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
845 for name, addr in getaddresses(reply_to_addresses) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
846 ] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
847 addresses_data.replyto = [ |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
848 AddressType(jid=jid, desc=name) for jid, name in jids_with_names |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
849 ] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
850 |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
851 # Set noreply flag |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
852 # The is no flag to indicate a no-reply message, so we check common user parts in |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
853 # from and reply-to headers. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
854 from_addresses = [email_addr] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
855 if reply_to_addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
856 from_addresses.extend( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
857 addr for a in reply_to_addresses if (addr := parseaddr(a)[1]) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
858 ) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
859 for from_address in from_addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
860 from_user_part = from_address.split("@", 1)[0].lower() |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
861 if from_user_part in ( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
862 "no-reply", |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
863 "noreply", |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
864 "do-not-reply", |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
865 "donotreply", |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
866 "notification", |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
867 "notifications", |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
868 ): |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
869 addresses_data.noreply = True |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
870 break |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
871 extra = {} |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
872 |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
873 if ( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
874 not addresses_data.replyto |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
875 and not addresses_data.noreply |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
876 and not addresses_data.cc |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
877 and not addresses_data.bcc |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
878 and addresses_data.to == [AddressType(jid=user_jid)] |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
879 ): |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
880 # The main recipient is the only one, and there is no other metadata: there is |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
881 # no need to add addresses metadata. |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
882 pass |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
883 else: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
884 for address in addresses_data.addresses: |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
885 if address.jid and ( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
886 address.jid == user_jid or address.jid.host == str(self.client.jid) |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
887 ): |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
888 # Those are email address, and have been delivered by the sender, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
889 # other JID addresses will have to be delivered by us. |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
890 address.delivered = True |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
891 |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
892 extra["addresses"] = addresses_data.model_dump(mode="json", exclude_none=True) |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
893 |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
894 # We look for interesting headers |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
895 headers = {} |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
896 keywords_headers = email.get_all("keywords") |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
897 if keywords_headers: |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
898 keywords = ",".join(keywords_headers) |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
899 headers["keywords"] = keywords |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
900 |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
901 importance = email["importance"] |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
902 if importance: |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
903 # We convert to urgency |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
904 if importance in ("low", "high"): |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
905 headers["urgency"] = importance |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
906 elif importance == "normal": |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
907 headers["urgency"] = "medium" |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
908 else: |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
909 log.warning("Ignoring invalid importance header: {importance!r}") |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
910 |
4350
6baea959dc33
component email gateway: convert `autocrypt` header:
Goffi <goffi@goffi.org>
parents:
4347
diff
changeset
|
911 autocrypt = email["autocrypt"] |
6baea959dc33
component email gateway: convert `autocrypt` header:
Goffi <goffi@goffi.org>
parents:
4347
diff
changeset
|
912 if autocrypt: |
6baea959dc33
component email gateway: convert `autocrypt` header:
Goffi <goffi@goffi.org>
parents:
4347
diff
changeset
|
913 headers["autocrypt"] = autocrypt |
6baea959dc33
component email gateway: convert `autocrypt` header:
Goffi <goffi@goffi.org>
parents:
4347
diff
changeset
|
914 |
4317
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
915 if headers: |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
916 extra["headers"] = HeadersData(**headers).model_dump( |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
917 mode="json", exclude_none=True |
055930cc81f9
component email gateway: Add support for XEP-0131 headers:
Goffi <goffi@goffi.org>
parents:
4309
diff
changeset
|
918 ) |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
919 |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
920 # Handle attachments |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
921 for part in email.iter_attachments(): |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
922 await self.handle_attachment(part, user_jid) |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
923 |
4303 | 924 client = self.client.get_virtual_client(from_jid) |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
925 |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
926 await client.sendMessage( |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
927 user_jid, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
928 {"": body}, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
929 {"": subject} if subject else None, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
930 extra=extra, |
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
931 ) |
4303 | 932 |
4337
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
933 async def handle_attachment(self, part: EmailMessage, recipient_jid: jid.JID) -> None: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
934 """Handle an attachment from an email. |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
935 |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
936 @param part: The object representing the attachment. |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
937 @param recipient_jid: JID of the recipient to whom the attachment is being sent. |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
938 """ |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
939 assert self.client is not None |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
940 content_type = part.get_content_type() |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
941 filename = part.get_filename() or "attachment" |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
942 log.debug(f"Handling attachment: {filename} ({content_type})") |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
943 file_metadata = await deferToThread(self._save_attachment, part) |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
944 if file_metadata is not None: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
945 log.debug(f"Attachment {filename!r} saved to {file_metadata.path}") |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
946 try: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
947 await self.host.memory.set_file( |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
948 self.client, |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
949 filename, |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
950 file_hash=file_metadata.hash, |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
951 hash_algo="sha-256", |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
952 size=file_metadata.size, |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
953 namespace=PLUGIN_INFO[C.PI_IMPORT_NAME], |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
954 mime_type=content_type, |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
955 owner=recipient_jid, |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
956 ) |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
957 except Exception: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
958 log.exception(f"Failed to register file {filename!r}") |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
959 |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
960 def _save_attachment(self, part: EmailMessage) -> FileMetadata | None: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
961 """Save the attachment to files path. |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
962 |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
963 This method must be executed in a thread with deferToThread to avoid blocking the |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
964 reactor with IO operations if the attachment is large. |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
965 |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
966 @param part: The object representing the attachment. |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
967 @return: Attachment data, or None if an error occurs. |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
968 @raises IOError: Can't save the attachment. |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
969 """ |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
970 temp_file = None |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
971 try: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
972 with tempfile.NamedTemporaryFile(delete=False) as temp_file: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
973 payload = part.get_payload(decode=True) |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
974 if isinstance(payload, bytes): |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
975 temp_file.write(payload) |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
976 file_hash = hashlib.sha256(payload).hexdigest() |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
977 file_path = self.files_path / file_hash |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
978 shutil.move(temp_file.name, file_path) |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
979 file_size = len(payload) |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
980 return FileMetadata(path=file_path, hash=file_hash, size=file_size) |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
981 else: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
982 log.warning(f"Can't write payload of type {type(payload)}.") |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
983 return None |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
984 except Exception as e: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
985 raise IOError(f"Failed to save attachment: {e}") |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
986 finally: |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
987 if temp_file is not None and Path(temp_file.name).exists(): |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
988 Path(temp_file.name).unlink() |
95792a1f26c7
component email gateway: attachments handling:
Goffi <goffi@goffi.org>
parents:
4317
diff
changeset
|
989 |
4303 | 990 async def connect_imap(self, from_jid: jid.JID, user_data: UserData) -> None: |
991 """Connect to IMAP service. | |
992 | |
993 [self.on_new_email] will be used as callback on new messages. | |
994 | |
995 @param from_jid: JID of the user associated with given credentials. | |
996 @param credentials: Email credentials. | |
997 """ | |
998 credentials = user_data.credentials | |
999 | |
1000 connected = defer.Deferred() | |
1001 factory = IMAPClientFactory( | |
1002 user_data, | |
4309
b56b1eae7994
component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents:
4303
diff
changeset
|
1003 partial(self.on_new_email, user_data, from_jid.userhostJID()), |
4303 | 1004 connected, |
1005 ) | |
1006 reactor.connectTCP( | |
1007 credentials["imap_host"], int(credentials["imap_port"]), factory | |
1008 ) | |
1009 await connected | |
1010 | |
1011 async def _on_registration_submit( | |
1012 self, | |
1013 client: SatXMPPEntity, | |
1014 iq_elt: domish.Element, | |
1015 submit_form: data_form.Form | None, | |
1016 ) -> bool | None: | |
1017 """Handle registration submit request. | |
1018 | |
1019 Submit form is validated, and credentials are stored. | |
1020 @param client: client session. | |
1021 iq_elt: IQ stanza of the submission request. | |
1022 submit_form: submit form. | |
1023 @return: True if successful. | |
1024 None if the callback is not relevant for this request. | |
1025 """ | |
1026 if client != self.client: | |
1027 return | |
1028 assert self.storage is not None | |
1029 from_jid = jid.JID(iq_elt["from"]).userhostJID() | |
1030 | |
1031 if submit_form is None: | |
1032 # This is an unregistration request. | |
1033 try: | |
1034 user_data = self.users_data[from_jid] | |
1035 except KeyError: | |
1036 pass | |
1037 else: | |
1038 if user_data.imap_client is not None: | |
1039 try: | |
1040 await user_data.imap_client.logout() | |
1041 except Exception: | |
1042 log.exception(f"Can't log out {from_jid} from IMAP server.") | |
1043 key = KEY_CREDENTIALS.format(from_jid=from_jid) | |
1044 await self.storage.adel(key) | |
1045 log.info(f"{from_jid} unregistered from this gateway.") | |
1046 return True | |
1047 | |
1048 self.validate_imap_smtp_form(submit_form) | |
1049 credentials = {key: field.value for key, field in submit_form.fields.items()} | |
1050 user_data = self.users_data.get(from_jid) | |
1051 if user_data is None: | |
1052 # The user is not in cache, we cache current credentials. | |
1053 user_data = self.users_data[from_jid] = UserData(credentials=credentials) | |
1054 else: | |
1055 # The user is known, we update credentials. | |
1056 user_data.credentials = credentials | |
1057 key = KEY_CREDENTIALS.format(from_jid=from_jid) | |
1058 try: | |
1059 await self.connect_imap(from_jid, user_data) | |
1060 except Exception as e: | |
1061 log.warning(f"Can't connect to IMAP server for {from_jid}") | |
1062 credentials["imap_success"] = False | |
1063 await self.storage.aset(key, credentials) | |
1064 raise e | |
1065 else: | |
1066 log.debug(f"Connection successful to IMAP server for {from_jid}") | |
1067 credentials["imap_success"] = True | |
1068 await self.storage.aset(key, credentials) | |
1069 return True | |
1070 | |
1071 | |
4347
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1072 async def on_relayed_encryption_data( |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1073 self, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1074 client: SatXMPPEntity, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1075 iq_elt: domish.Element, |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1076 form: data_form.Form |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1077 ) -> None: |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1078 from_jid = jid.JID(iq_elt["from"]).userhostJID() |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1079 credentials = await self.get_credentials(from_jid) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1080 form.addField(data_form.Field(var="sender_id", value=credentials["user_email"])) |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1081 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1082 |
54df67d5646c
component email gateway: implement Gateway Relayed Encryption:
Goffi <goffi@goffi.org>
parents:
4338
diff
changeset
|
1083 |
4303 | 1084 @implementer(iwokkel.IDisco) |
1085 class EmailGatewayHandler(XMPPHandler): | |
1086 | |
1087 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): | |
1088 return [] | |
1089 | |
1090 def getDiscoItems(self, requestor, target, nodeIdentifier=""): | |
1091 return [] |