annotate libervia/backend/plugins/plugin_comp_email_gateway/__init__.py @ 4309:b56b1eae7994

component email gateway: add multicasting: XEP-0033 multicasting is now supported both for incoming and outgoing messages. XEP-0033 metadata are converted to suitable Email headers and vice versa. Email address and JID are both supported, and delivery is done by the gateway when suitable on incoming messages. rel 450
author Goffi <goffi@goffi.org>
date Thu, 26 Sep 2024 16:12:01 +0200
parents a7ec325246fb
children 055930cc81f9
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
1 #!/usr/bin/env python3
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
2
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
3 # Libervia Email Gateway Component
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
4 # Copyright (C) 2009-2024 Jérôme Poisson (goffi@goffi.org)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
5
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
6 # This program is free software: you can redistribute it and/or modify
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
7 # it under the terms of the GNU Affero General Public License as published by
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
8 # the Free Software Foundation, either version 3 of the License, or
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
9 # (at your option) any later version.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
10
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
11 # This program is distributed in the hope that it will be useful,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
14 # GNU Affero General Public License for more details.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
15
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
16 # You should have received a copy of the GNU Affero General Public License
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
18
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
19 from email.header import decode_header
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
20 from email.message import EmailMessage
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
21 from email.mime.text import MIMEText
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
22 from email.utils import formataddr, getaddresses, parseaddr
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
23 from functools import partial
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
24 import re
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
25 from typing import cast
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
26
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
27 from pydantic import BaseModel
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
28 from twisted.internet import defer, reactor
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
29 from twisted.mail import smtp
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
30 from twisted.words.protocols.jabber import jid
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
31 from twisted.words.protocols.jabber import error as jabber_error
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
32 from twisted.words.protocols.jabber.error import StanzaError
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
33 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
34 from twisted.words.xish import domish
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
35 from wokkel import data_form, disco, iwokkel
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
36 from zope.interface import implementer
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
37
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
38 from libervia.backend.core import exceptions
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
39 from libervia.backend.core.constants import Const as C
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
40 from libervia.backend.core.core_types import SatXMPPEntity
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
41 from libervia.backend.core.i18n import D_, _
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
42 from libervia.backend.core.log import getLogger
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
43 from libervia.backend.memory.persistent import LazyPersistentBinaryDict
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
44 from libervia.backend.memory.sqla import select
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
45 from libervia.backend.memory.sqla_mapping import PrivateIndBin
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
46 from libervia.backend.models.core import MessageData
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
47 from libervia.backend.plugins.plugin_xep_0033 import (
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
48 RECIPIENT_FIELDS,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
49 AddressType,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
50 AddressesData,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
51 )
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
52 from libervia.backend.plugins.plugin_xep_0077 import XEP_0077
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
53 from libervia.backend.plugins.plugin_xep_0106 import XEP_0106
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
54 from libervia.backend.tools.utils import aio
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
55
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
56 from .models import Credentials, UserData
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
57 from .imap import IMAPClientFactory
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
58
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
59
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
60 log = getLogger(__name__)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
61
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
62 IMPORT_NAME = "email-gateway"
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
63 NAME = "Libervia Email Gateway"
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
64
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
65 PLUGIN_INFO = {
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
66 C.PI_NAME: "Email Gateway Component",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
67 C.PI_IMPORT_NAME: IMPORT_NAME,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
68 C.PI_MODES: [C.PLUG_MODE_COMPONENT],
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
69 C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
70 C.PI_PROTOCOLS: [],
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
71 C.PI_DEPENDENCIES: ["XEP-0033", "XEP-0077", "XEP-0106"],
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
72 C.PI_RECOMMENDATIONS: [],
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
73 C.PI_MAIN: "EmailGatewayComponent",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
74 C.PI_HANDLER: C.BOOL_TRUE,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
75 C.PI_DESCRIPTION: D_(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
76 "Gateway to handle email. Usual emails are handled as message, while mailing "
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
77 "lists are converted to pubsub blogs."
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
78 ),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
79 }
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
80
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
81 CONF_SECTION = f"component {IMPORT_NAME}"
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
82 PREFIX_KEY_CREDENTIALS = "CREDENTIALS_"
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
83 KEY_CREDENTIALS = f"{PREFIX_KEY_CREDENTIALS}{{from_jid}}"
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
84
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
85 email_pattern = re.compile(r"[^@]+@[^@]+\.[^@]+")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
86
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
87
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
88 class SendMailExtra(BaseModel):
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
89 addresses: AddressesData | None = None
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
90
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
91
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
92 class EmailGatewayComponent:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
93 IMPORT_NAME = IMPORT_NAME
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
94 verbose = 0
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
95
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
96 def __init__(self, host):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
97 self.host = host
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
98 self.client: SatXMPPEntity | None = None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
99 self.initalized = False
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
100 self.storage: LazyPersistentBinaryDict | None = None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
101 self._iq_register = cast(XEP_0077, host.plugins["XEP-0077"])
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
102 self._iq_register.register_handler(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
103 self._on_registration_form, self._on_registration_submit
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
104 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
105 self._e = cast(XEP_0106, host.plugins["XEP-0106"])
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
106 # TODO: For the moment, all credentials are kept in cache; we should only keep the
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
107 # X latest.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
108 self.users_data: dict[jid.JID, UserData] = {}
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
109 host.trigger.add_with_check(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
110 "message_received", self, self._message_received_trigger, priority=-1000
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
111 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
112
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
113 async def _init(self) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
114 """Initialisation done after profile is connected"""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
115 assert self.client is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
116 self.client.identities.append(disco.DiscoIdentity("gateway", "smtp", NAME))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
117 self.storage = LazyPersistentBinaryDict(IMPORT_NAME, self.client.profile)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
118 await self.connect_registered_users()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
119
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
120 @aio
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
121 async def get_registered_users(self) -> dict[jid.JID, Credentials]:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
122 """Retrieve credentials for all registered users
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
123
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
124 @return: a mapping from user JID to credentials data.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
125 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
126 assert self.client is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
127 profile_id = self.host.memory.storage.profiles[self.client.profile]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
128 async with self.host.memory.storage.session() as session:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
129 query = select(PrivateIndBin).where(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
130 PrivateIndBin.profile_id == profile_id,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
131 PrivateIndBin.namespace == IMPORT_NAME,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
132 PrivateIndBin.key.startswith(PREFIX_KEY_CREDENTIALS),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
133 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
134 result = await session.execute(query)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
135 return {
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
136 jid.JID(p.key[len(PREFIX_KEY_CREDENTIALS) :]): p.value
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
137 for p in result.scalars()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
138 }
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
139
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
140 async def connect_registered_users(self) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
141 """Connected users already registered to the gateway."""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
142 registered_data = await self.get_registered_users()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
143 for user_jid, credentials in registered_data.items():
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
144 user_data = self.users_data[user_jid] = UserData(credentials=credentials)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
145 if not credentials["imap_success"]:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
146 log.warning(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
147 f"Ignoring unsuccessful IMAP credentials of {user_jid}. This user "
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
148 "won't receive message from this gateway."
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
149 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
150 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
151 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
152 await self.connect_imap(user_jid, user_data)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
153 except Exception as e:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
154 log.warning(f"Can't connect {user_jid} to IMAP: {e}.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
155 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
156 log.debug(f"Connection to IMAP server successful for {user_jid}.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
157
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
158 def get_handler(self, __) -> XMPPHandler:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
159 return EmailGatewayHandler()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
160
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
161 async def profile_connecting(self, client: SatXMPPEntity) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
162 self.client = client
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
163 if not self.initalized:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
164 await self._init()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
165 self.initalized = True
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
166
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
167 def _message_received_trigger(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
168 self,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
169 client: SatXMPPEntity,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
170 message_elt: domish.Element,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
171 post_treat: defer.Deferred,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
172 ) -> bool:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
173 """add the gateway workflow on post treatment"""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
174 if client != self.client:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
175 return True
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
176 post_treat.addCallback(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
177 lambda mess_data: defer.ensureDeferred(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
178 self.on_message(client, mess_data, message_elt)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
179 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
180 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
181 return True
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
182
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
183 async def on_message(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
184 self, client: SatXMPPEntity, mess_data: MessageData, message_elt: domish.Element
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
185 ) -> dict:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
186 """Called once message has been parsed
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
187
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
188 @param client: Client session.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
189 @param mess_data: Message data.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
190 @return: Message data.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
191 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
192 if client != self.client:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
193 return mess_data
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
194 from_jid = mess_data["from"].userhostJID()
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
195 extra = None
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
196 if mess_data["type"] not in ("chat", "normal"):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
197 log.warning(f"ignoring message with unexpected type: {mess_data}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
198 return mess_data
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
199 if not client.is_local(from_jid):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
200 log.warning(f"ignoring non local message: {mess_data}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
201 return mess_data
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
202 if not mess_data["to"].user:
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
203 addresses = mess_data["extra"].get("addresses")
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
204 if not addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
205 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
206 return mess_data
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
207 else:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
208 to_email = None
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
209 extra = SendMailExtra(addresses=addresses)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
210 else:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
211 try:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
212 to_email = self._e.unescape(mess_data["to"].user)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
213 except ValueError:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
214 raise exceptions.DataError(
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
215 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
216 )
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
217
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
218 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
219 body_lang, body = next(iter(mess_data["message"].items()))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
220 except (KeyError, StopIteration):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
221 log.warning(f"No body found: {mess_data}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
222 body_lang, body = "", ""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
223 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
224 subject_lang, subject = next(iter(mess_data["subject"].items()))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
225 except (KeyError, StopIteration):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
226 subject_lang, subject = "", None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
227
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
228 if not body and not subject:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
229 log.warning(f"Ignoring empty message: {mess_data}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
230 return mess_data
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
231
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
232 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
233 await self.send_email(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
234 from_jid=from_jid,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
235 to_email=to_email,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
236 body=body,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
237 subject=subject,
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
238 extra=extra,
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
239 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
240 except exceptions.UnknownEntityError:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
241 log.warning(f"Can't send message, user {from_jid} is not registered.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
242 message_error_elt = StanzaError(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
243 "subscription-required",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
244 text="User need to register to the gateway before sending emails.",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
245 ).toResponse(message_elt)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
246 await client.a_send(message_error_elt)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
247 raise exceptions.CancelError("User not registered.")
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
248 except StanzaError as e:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
249 log.warning("Can't send message: {e}")
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
250 message_error_elt = e.toResponse(message_elt)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
251 await client.a_send(message_error_elt)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
252 raise exceptions.CancelError("Can't send message: {e}")
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
253
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
254 return mess_data
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
255
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
256 def jid_to_email(
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
257 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
258 ) -> str:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
259 """Convert a JID to an email address.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
260
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
261 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
262 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
263
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
264 @param address_jid: JID of the recipient.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
265 @param credentials: Sender credentials.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
266 @return: Email address.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
267 """
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
268 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
269 return self._e.unescape(address_jid.user)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
270 else:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
271 email_address = credentials["user_email"]
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
272 if address_jid:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
273 email_address = formataddr((f"xmpp:{address_jid}", email_address))
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
274 return email_address
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
275
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
276 async def send_email(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
277 self,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
278 from_jid: jid.JID,
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
279 to_email: str | None,
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
280 body: str,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
281 subject: str | None,
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
282 extra: SendMailExtra | None = None,
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
283 ) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
284 """Send an email using sender credentials.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
285
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
286 Credentials will be retrieve from cache, or database.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
287
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
288 @param from_jid: Bare JID of the sender.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
289 @param to_email: Email address of the destinee.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
290 @param body: Body of the email.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
291 @param subject: Subject of the email.
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
292 @param extra: Extra data.
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
293
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
294 @raise exceptions.UnknownEntityError: Credentials for "from_jid" can't be found.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
295 """
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
296 assert self.client is not None
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
297 if extra is None:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
298 extra = SendMailExtra()
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
299 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
300 raise exceptions.InternalError(
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
301 '"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
302 )
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
303
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
304 # We need a bare jid.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
305 assert self.storage is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
306 assert not from_jid.resource
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
307 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
308 user_data = self.users_data[from_jid]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
309 except KeyError:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
310 key = KEY_CREDENTIALS.format(from_jid=from_jid)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
311 credentials = await self.storage.get(key)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
312 if credentials is None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
313 raise exceptions.UnknownEntityError(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
314 f"No credentials found for {from_jid}."
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
315 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
316 self.users_data[from_jid] = UserData(credentials)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
317 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
318 credentials = user_data.credentials
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
319
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
320 msg = MIMEText(body, "plain", "UTF-8")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
321 if subject is not None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
322 msg["Subject"] = subject
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
323 msg["From"] = formataddr(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
324 (credentials["user_name"] or None, credentials["user_email"])
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
325 )
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
326 if extra.addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
327 assert extra.addresses.to
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
328 main_to_address = extra.addresses.to[0]
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
329 assert main_to_address.jid
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
330 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
331 for field in RECIPIENT_FIELDS:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
332 addresses = getattr(extra.addresses, field)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
333 if not addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
334 continue
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
335 for address in addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
336 if not address.delivered and (
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
337 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
338 ):
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
339 log.warning(
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
340 "Received undelivered message to external JID, this is not "
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
341 "allowed! Cancelling the message sending."
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
342 )
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
343 stanza_err = jabber_error.StanzaError(
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
344 "forbidden",
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
345 text="Multicasting (XEP-0033 addresses) can only be used "
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
346 "with JID from this gateway, not external ones. "
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
347 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
348 "should be delivered by server instead.",
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
349 )
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
350 raise stanza_err
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
351 email_addresses = [
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
352 self.jid_to_email(self.client, address.jid, credentials)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
353 for address in addresses
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
354 if address.jid
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
355 ]
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
356 if email_addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
357 msg[field.upper()] = ", ".join(email_addresses)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
358 else:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
359 assert to_email is not None
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
360 msg["To"] = to_email
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
361
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
362 sender_domain = credentials["user_email"].split("@", 1)[-1]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
363
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
364 await smtp.sendmail(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
365 credentials["smtp_host"].encode(),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
366 credentials["user_email"].encode(),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
367 [to_email.encode()],
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
368 msg.as_bytes(),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
369 senderDomainName=sender_domain,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
370 port=int(credentials["smtp_port"]),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
371 username=credentials["smtp_username"].encode(),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
372 password=credentials["smtp_password"].encode(),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
373 requireAuthentication=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
374 # TODO: only STARTTLS is supported right now, implicit TLS should be supported
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
375 # too.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
376 requireTransportSecurity=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
377 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
378
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
379 async def _on_registration_form(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
380 self, client: SatXMPPEntity, iq_elt: domish.Element
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
381 ) -> tuple[bool, data_form.Form] | None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
382 if client != self.client:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
383 return
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
384 assert self.storage is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
385 from_jid = jid.JID(iq_elt["from"])
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
386 key = KEY_CREDENTIALS.format(from_jid=from_jid.userhost())
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
387 credentials = await self.storage.get(key) or {}
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
388
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
389 form = data_form.Form(formType="form", title="IMAP/SMTP Credentials")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
390
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
391 # Add instructions
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
392 form.instructions = [
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
393 D_(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
394 "Please provide your IMAP and SMTP credentials to configure the "
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
395 "connection."
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
396 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
397 ]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
398
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
399 # Add identity fields
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
400 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
401 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
402 fieldType="text-single",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
403 var="user_name",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
404 label="User Name",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
405 desc=D_('The display name to use in the "From" field of sent emails.'),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
406 value=credentials.get("user_name"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
407 required=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
408 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
409 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
410
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
411 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
412 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
413 fieldType="text-single",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
414 var="user_email",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
415 label="User Email",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
416 desc=D_('The email address to use in the "From" field of sent emails.'),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
417 value=credentials.get("user_email"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
418 required=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
419 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
420 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
421
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
422 # Add fields for IMAP credentials
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
423 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
424 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
425 fieldType="text-single",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
426 var="imap_host",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
427 label="IMAP Host",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
428 desc=D_("IMAP server hostname or IP address"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
429 value=credentials.get("imap_host"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
430 required=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
431 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
432 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
433 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
434 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
435 fieldType="text-single",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
436 var="imap_port",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
437 label="IMAP Port",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
438 desc=D_("IMAP server port (default: 993)"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
439 value=credentials.get("imap_port", "993"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
440 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
441 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
442 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
443 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
444 fieldType="text-single",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
445 var="imap_username",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
446 label="IMAP Username",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
447 desc=D_("Username for IMAP authentication"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
448 value=credentials.get("imap_username"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
449 required=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
450 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
451 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
452 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
453 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
454 fieldType="text-private",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
455 var="imap_password",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
456 label="IMAP Password",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
457 desc=D_("Password for IMAP authentication"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
458 value=credentials.get("imap_password"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
459 required=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
460 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
461 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
462
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
463 # Add fields for SMTP credentials
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
464 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
465 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
466 fieldType="text-single",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
467 var="smtp_host",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
468 label="SMTP Host",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
469 desc=D_("SMTP server hostname or IP address"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
470 value=credentials.get("smtp_host"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
471 required=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
472 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
473 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
474 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
475 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
476 fieldType="text-single",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
477 var="smtp_port",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
478 label="SMTP Port",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
479 desc=D_("SMTP server port (default: 587)"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
480 value=credentials.get("smtp_port", "587"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
481 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
482 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
483 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
484 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
485 fieldType="text-single",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
486 var="smtp_username",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
487 label="SMTP Username",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
488 desc=D_("Username for SMTP authentication"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
489 value=credentials.get("smtp_username"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
490 required=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
491 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
492 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
493 form.addField(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
494 data_form.Field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
495 fieldType="text-private",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
496 var="smtp_password",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
497 label="SMTP Password",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
498 desc=D_("Password for SMTP authentication"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
499 value=credentials.get("smtp_password"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
500 required=True,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
501 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
502 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
503
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
504 return bool(credentials), form
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
505
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
506 def validate_field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
507 self,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
508 form: data_form.Form,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
509 key: str,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
510 field_type: str,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
511 min_value: int | None = None,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
512 max_value: int | None = None,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
513 default: str | int | None = None,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
514 ) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
515 """Validate a single field.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
516
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
517 @param form: The form containing the fields.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
518 @param key: The key of the field to validate.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
519 @param field_type: The expected type of the field value.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
520 @param min_value: Optional minimum value for integer fields.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
521 @param max_value: Optional maximum value for integer fields.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
522 @param default: Default value to use if the field is missing.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
523 @raise StanzaError: If the field value is invalid or missing.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
524 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
525 field = form.fields.get(key)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
526 if field is None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
527 if default is None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
528 raise StanzaError("bad-request", text=f"{key} is required")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
529 field = data_form.Field(var=key, value=str(default))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
530 form.addField(field)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
531
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
532 value = field.value
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
533 if field_type == "int":
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
534 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
535 value = int(value)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
536 if (min_value is not None and value < min_value) or (
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
537 max_value is not None and value > max_value
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
538 ):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
539 raise ValueError
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
540 except (ValueError, TypeError):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
541 raise StanzaError("bad-request", text=f"Invalid value for {key}: {value}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
542 elif field_type == "str":
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
543 if not isinstance(value, str):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
544 raise StanzaError("bad-request", text=f"Invalid value for {key}: {value}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
545
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
546 # Basic email validation for user_email field
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
547 if key == "user_email":
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
548 # XXX: This is a minimal check. A complete email validation is notoriously
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
549 # difficult.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
550 if not email_pattern.match(value):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
551 raise StanzaError(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
552 "bad-request", text=f"Invalid email address: {value}"
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
553 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
554
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
555 def validate_imap_smtp_form(self, submit_form: data_form.Form) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
556 """Validate the submitted IMAP/SMTP credentials form.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
557
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
558 @param submit_form: The submitted form containing IMAP/SMTP credentials.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
559 @raise StanzaError: If any of the values are invalid.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
560 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
561 # Validate identity fields
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
562 self.validate_field(submit_form, "user_name", "str")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
563 self.validate_field(submit_form, "user_email", "str")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
564
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
565 # Validate IMAP fields
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
566 self.validate_field(submit_form, "imap_host", "str")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
567 self.validate_field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
568 submit_form, "imap_port", "int", min_value=1, max_value=65535, default=993
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
569 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
570 self.validate_field(submit_form, "imap_username", "str")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
571 self.validate_field(submit_form, "imap_password", "str")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
572
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
573 # Validate SMTP fields
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
574 self.validate_field(submit_form, "smtp_host", "str")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
575 self.validate_field(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
576 submit_form, "smtp_port", "int", min_value=1, max_value=65535, default=587
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
577 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
578 self.validate_field(submit_form, "smtp_username", "str")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
579 self.validate_field(submit_form, "smtp_password", "str")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
580
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
581 def email_to_jid(
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
582 self,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
583 client: SatXMPPEntity,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
584 user_email: str,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
585 user_jid: jid.JID,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
586 email_name: str,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
587 email_addr: str,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
588 ) -> tuple[jid.JID, str | None]:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
589 """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
590
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
591 @param client: Client session.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
592 @param user_email: Email address of the gateway user.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
593 @param user_jid: JID of the gateway user.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
594 @param email_name: Email associated name.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
595 @param email_addr: Email address.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
596 @return: Tuple of JID and name (if present).
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
597 """
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
598 email_name = email_name.strip()
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
599 if email_name.startswith("xmpp:"):
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
600 return jid.JID(email_name[5:]), None
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
601 elif email_addr == user_email:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
602 return (user_jid, None)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
603 else:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
604 return (
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
605 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
606 email_name or None,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
607 )
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
608
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
609 async def on_new_email(
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
610 self, user_data: UserData, user_jid: jid.JID, email: EmailMessage
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
611 ) -> None:
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
612 """Called when a new message has been received.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
613
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
614 @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
615 jid.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
616 @param user_jid: JID of the recipient.
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
617 @param email: Parsed email.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
618 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
619 assert self.client is not None
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
620 user_email = user_data.credentials["user_email"]
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
621 name, email_addr = parseaddr(email["from"])
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
622 email_addr = email_addr.lower()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
623 from_jid = jid.JID(None, (self._e.escape(email_addr), self.client.jid.host, None))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
624
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
625 # Get the email body
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
626 body_mime = email.get_body(("plain",))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
627 if body_mime is not None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
628 charset = body_mime.get_content_charset() or "utf-8"
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
629 body = body_mime.get_payload(decode=True).decode(charset, errors="replace")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
630 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
631 log.warning(f"No body found in email:\n{email}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
632 body = ""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
633
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
634 # Decode the subject
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
635 subject = email.get("subject")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
636 if subject:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
637 decoded_subject = decode_header(subject)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
638 subject = "".join(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
639 [
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
640 part.decode(encoding or "utf-8") if isinstance(part, bytes) else part
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
641 for part, encoding in decoded_subject
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
642 ]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
643 ).strip()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
644 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
645 subject = None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
646
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
647 # Parse recipient fields
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
648 kwargs = {}
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
649 for field in RECIPIENT_FIELDS:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
650 email_addresses = email.get_all(field)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
651 if email_addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
652 jids_and_names = [
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
653 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
654 for name, addr in getaddresses(email_addresses)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
655 ]
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
656 kwargs[field] = [
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
657 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
658 ]
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
659
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
660 # 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
661 assert kwargs
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
662 addresses_data = AddressesData(**kwargs)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
663
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
664 # Parse reply-to field
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
665 reply_to_addresses = email.get_all("reply-to")
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
666 if reply_to_addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
667 jids_with_names = [
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
668 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
669 for name, addr in getaddresses(reply_to_addresses)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
670 ]
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
671 addresses_data.replyto = [
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
672 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
673 ]
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
674
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
675 # Set noreply flag
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
676 # 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
677 # from and reply-to headers.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
678 from_addresses = [email_addr]
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
679 if reply_to_addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
680 from_addresses.extend(
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
681 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
682 )
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
683 for from_address in from_addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
684 from_user_part = from_address.split("@", 1)[0].lower()
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
685 if from_user_part in (
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
686 "no-reply",
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
687 "noreply",
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
688 "do-not-reply",
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
689 "donotreply",
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
690 "notification",
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
691 "notifications",
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
692 ):
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
693 addresses_data.noreply = True
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
694 break
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
695
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
696 if (
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
697 not addresses_data.replyto
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
698 and not addresses_data.noreply
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
699 and not addresses_data.cc
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
700 and not addresses_data.bcc
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
701 and addresses_data.to == [AddressType(jid=user_jid)]
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
702 ):
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
703 # 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
704 # no need to add addresses metadata.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
705 extra = None
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
706 else:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
707 for address in addresses_data.addresses:
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
708 if address.jid and (
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
709 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
710 ):
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
711 # 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
712 # other JID addresses will have to be delivered by us.
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
713 address.delivered = True
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
714
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
715 extra = {
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
716 "addresses": addresses_data.model_dump(mode="json", exclude_none=True)
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
717 }
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
718
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
719 client = self.client.get_virtual_client(from_jid)
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
720 await client.sendMessage(
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
721 user_jid,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
722 {"": body},
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
723 {"": subject} if subject else None,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
724 extra=extra,
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
725 )
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
726
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
727 async def connect_imap(self, from_jid: jid.JID, user_data: UserData) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
728 """Connect to IMAP service.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
729
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
730 [self.on_new_email] will be used as callback on new messages.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
731
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
732 @param from_jid: JID of the user associated with given credentials.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
733 @param credentials: Email credentials.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
734 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
735 credentials = user_data.credentials
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
736
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
737 connected = defer.Deferred()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
738 factory = IMAPClientFactory(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
739 user_data,
4309
b56b1eae7994 component email gateway: add multicasting:
Goffi <goffi@goffi.org>
parents: 4303
diff changeset
740 partial(self.on_new_email, user_data, from_jid.userhostJID()),
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
741 connected,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
742 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
743 reactor.connectTCP(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
744 credentials["imap_host"], int(credentials["imap_port"]), factory
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
745 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
746 await connected
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
747
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
748 async def _on_registration_submit(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
749 self,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
750 client: SatXMPPEntity,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
751 iq_elt: domish.Element,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
752 submit_form: data_form.Form | None,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
753 ) -> bool | None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
754 """Handle registration submit request.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
755
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
756 Submit form is validated, and credentials are stored.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
757 @param client: client session.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
758 iq_elt: IQ stanza of the submission request.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
759 submit_form: submit form.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
760 @return: True if successful.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
761 None if the callback is not relevant for this request.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
762 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
763 if client != self.client:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
764 return
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
765 assert self.storage is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
766 from_jid = jid.JID(iq_elt["from"]).userhostJID()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
767
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
768 if submit_form is None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
769 # This is an unregistration request.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
770 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
771 user_data = self.users_data[from_jid]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
772 except KeyError:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
773 pass
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
774 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
775 if user_data.imap_client is not None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
776 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
777 await user_data.imap_client.logout()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
778 except Exception:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
779 log.exception(f"Can't log out {from_jid} from IMAP server.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
780 key = KEY_CREDENTIALS.format(from_jid=from_jid)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
781 await self.storage.adel(key)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
782 log.info(f"{from_jid} unregistered from this gateway.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
783 return True
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
784
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
785 self.validate_imap_smtp_form(submit_form)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
786 credentials = {key: field.value for key, field in submit_form.fields.items()}
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
787 user_data = self.users_data.get(from_jid)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
788 if user_data is None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
789 # The user is not in cache, we cache current credentials.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
790 user_data = self.users_data[from_jid] = UserData(credentials=credentials)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
791 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
792 # The user is known, we update credentials.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
793 user_data.credentials = credentials
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
794 key = KEY_CREDENTIALS.format(from_jid=from_jid)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
795 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
796 await self.connect_imap(from_jid, user_data)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
797 except Exception as e:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
798 log.warning(f"Can't connect to IMAP server for {from_jid}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
799 credentials["imap_success"] = False
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
800 await self.storage.aset(key, credentials)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
801 raise e
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
802 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
803 log.debug(f"Connection successful to IMAP server for {from_jid}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
804 credentials["imap_success"] = True
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
805 await self.storage.aset(key, credentials)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
806 return True
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
807
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
808
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
809 @implementer(iwokkel.IDisco)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
810 class EmailGatewayHandler(XMPPHandler):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
811
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
812 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
813 return []
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
814
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
815 def getDiscoItems(self, requestor, target, nodeIdentifier=""):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
816 return []