annotate libervia/backend/plugins/plugin_comp_email_gateway/imap.py @ 4351:6a0a081485b8

plugin autocrypt: Autocrypt protocol implementation: Implementation of autocrypt: `autocrypt` header is checked, and if present and no public key is known for the peer, the key is imported. `autocrypt` header is also added to outgoing message (only if an email gateway is detected). For the moment, the JID is use as identifier, but the real email used by gateway should be used in the future. rel 456
author Goffi <goffi@goffi.org>
date Fri, 28 Feb 2025 09:23:35 +0100
parents a7ec325246fb
children
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.message import EmailMessage
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
20 from email.parser import BytesParser, Parser
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
21 from email import policy
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
22 from typing import Callable, cast
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
23 from twisted.internet import defer, protocol, reactor
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
24 from twisted.internet.base import DelayedCall
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
25 from twisted.mail import imap4
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
26 from twisted.python.failure import Failure
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
27
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
28 from libervia.backend.core import exceptions
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
29 from libervia.backend.core.i18n import _
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
30 from libervia.backend.core.log import getLogger
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
31 from .models import UserData
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
32
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
33 log = getLogger(__name__)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
34
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
35
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
36 class IMAPClient(imap4.IMAP4Client):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
37 _idling = False
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
38 _idle_timer: DelayedCall | None = None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
39
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
40 def __init__(self, connected: defer.Deferred, *args, **kwargs) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
41 super().__init__(*args, **kwargs)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
42 self._connected = connected
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
43
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
44 def serverGreeting(self, caps: dict) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
45 """Handle the server greeting and capabilities.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
46
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
47 @param caps: Server capabilities.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
48 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
49 defer.ensureDeferred(self.on_server_greeting(caps))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
50
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
51 async def on_server_greeting(self, caps: dict) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
52 """Async method called when server greeting is received.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
53
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
54 @param caps: Server capabilities.
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 self.server_capabilities = caps
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
57 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
58 await self.authenticate(self.factory.password.encode())
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
59 except Exception as e:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
60 log.warning(f"Can't authenticate: {e}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
61 self._connected.errback(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
62 exceptions.PasswordError("Authentication error for IMAP server.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
63 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
64 return
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
65 log.debug("Authenticated.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
66 self._connected.callback(None)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
67 if b"IDLE" in caps:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
68 # We use "examine" for read-only access for now, will probably change in the
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
69 # future.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
70 await self.examine(b"INBOX")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
71 log.debug("Activating IDLE mode")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
72 await self.idle()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
73 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
74 log.warning(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
75 f'"IDLE" mode is not supported by your server, this gateways needs a '
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
76 "server supporting this mode."
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
77 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
78 return
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 async def idle(self) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
81 """Enter the IDLE mode to receive real-time updates from the server."""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
82 if self._idling:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
83 # We are already in idle state.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
84 return
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
85 self._idling = True
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
86 self._idle_timer = reactor.callLater(29 * 60, self.on_idle_timeout)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
87 await self.sendCommand(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
88 imap4.Command(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
89 b"IDLE",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
90 continuation=lambda *a, **kw: log.debug(f"continuation: {a=} {kw=}"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
91 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
92 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
93
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
94 def idle_exit(self) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
95 """Exit the IDLE mode."""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
96 assert self._idling
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
97 assert self._idle_timer is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
98 if not self._idle_timer.called:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
99 self._idle_timer.cancel()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
100 self._idle_timer = None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
101 # Send DONE command to exit IDLE mode.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
102 self.sendLine(b"DONE")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
103 self._idling = False
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
104 log.debug("IDLE mode terminated")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
105
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
106 def on_idle_timeout(self):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
107 """Called when IDLE mode timeout is reached."""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
108 if self._idling:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
109 # We've reached 29 min of IDLE mode, we restart it as recommended in the
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
110 # specifications.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
111 self.idle_exit()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
112 defer.ensureDeferred(self.idle())
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
113
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
114 def newMessages(self, exists: int | None, recent: int | None):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
115 """Called when new messages are received.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
116
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
117 @param exists: Number of existing messages.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
118 @param recent: Number of recent messages.
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 defer.ensureDeferred(self.on_new_emails(exists, recent))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
121
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
122 async def on_new_emails(self, exists: int | None, recent: int | None) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
123 """Async method called when new messages are received.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
124
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
125 @param exists: Number of existing messages.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
126 @param recent: Number of recent messages.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
127 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
128 log.debug(f"New messages: {exists}, Recent messages: {recent}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
129 log.debug("Retrieving last message.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
130 self.idle_exit()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
131 mess_data = await self.fetchMessage("*")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
132 for message in mess_data.values():
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
133 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
134 content = message["RFC822"]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
135 except KeyError:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
136 log.warning(f"Can't find content for {message}.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
137 continue
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
138 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
139 if isinstance(content, str):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
140 parser = Parser(policy=policy.default)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
141 parser_method = parser.parsestr
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
142 elif isinstance(content, bytes):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
143 parser = BytesParser(policy=policy.default)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
144 parser_method = parser.parsebytes
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
145 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
146 log.error(f"Invalid content: {content}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
147 continue
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
148 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
149 parsed = parser_method(content)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
150 except Exception as e:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
151 log.warning(f"Can't parse content of email: {e}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
152 continue
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
153 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
154 assert self.factory is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
155 factory = cast(IMAPClientFactory, self.factory)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
156 await factory.on_new_email(parsed)
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 defer.ensureDeferred(self.idle())
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
159
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
160 def connectionLost(self, reason: Failure) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
161 """Called when the connection is lost.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
162
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
163 @param reason: The reason for the lost connection.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
164 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
165 log.debug(f"connectionLost {reason=}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
166 if not self._connected.called:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
167 self._connected.errback(reason)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
168 super().connectionLost(reason)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
169
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
170 def lineReceived(self, line: bytes) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
171 """Called when a line is received from the server.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
172
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
173 @param line: The received line.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
174 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
175 if self._idling:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
176 if line == b"* OK Still here":
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
177 pass
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
178 elif line == b"+ idling":
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
179 pass
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
180 elif line.startswith(b"* "):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
181 # Handle unsolicited responses during IDLE
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
182 self._extraInfo([imap4.parseNestedParens(line[2:])])
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
183 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
184 log.warning(f"Unexpected line received: {line!r}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
185
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
186 return
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 return super().lineReceived(line)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
189
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
190 def sendCommand(self, cmd: imap4.Command) -> defer.Deferred:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
191 """Send a command to the server.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
192
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
193 This method is overriden to stop and restart IDLE mode when a command is received.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
194
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
195 @param cmd: The command to send.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
196 @return: A deferred that fires when the command is sent.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
197 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
198 if self._idling and cmd.command != b"IDLE":
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
199 self.idle_exit()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
200 d = super().sendCommand(cmd)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
201
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
202 def restart_idle_mode(ret):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
203 defer.ensureDeferred(self.idle())
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
204 return ret
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
205
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
206 d.addCallback(restart_idle_mode)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
207 return d
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
208 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
209 return super().sendCommand(cmd)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
210
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
211
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
212 class IMAPClientFactory(protocol.ClientFactory):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
213 protocol = IMAPClient
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
214
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
215 def __init__(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
216 self,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
217 user_data: UserData,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
218 on_new_email: Callable[[EmailMessage], None],
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
219 connected: defer.Deferred,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
220 ) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
221 """Initialize the IMAP client factory.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
222
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
223 @param username: The username to use for authentication.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
224 @param password: The password to use for authentication.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
225 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
226 credentials = user_data.credentials
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
227 self.user_data = user_data
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
228 self.username = credentials["imap_username"]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
229 self.password = credentials["imap_password"]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
230 self.on_new_email = on_new_email
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
231 self._connected = connected
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
232
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
233 def buildProtocol(self, addr) -> IMAPClient:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
234 """Build the IMAP client protocol.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
235
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
236 @return: The IMAP client protocol.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
237 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
238 assert self.protocol is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
239 assert isinstance(self.protocol, type(IMAPClient))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
240 protocol_ = self.protocol(self._connected)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
241 protocol_.factory = self
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
242 self.user_data.imap_client = protocol_
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
243 assert isinstance(protocol_, IMAPClient)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
244 protocol_.factory = self
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
245 encoded_username = self.username.encode()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
246
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
247 protocol_.registerAuthenticator(imap4.PLAINAuthenticator(encoded_username))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
248 protocol_.registerAuthenticator(imap4.LOGINAuthenticator(encoded_username))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
249 protocol_.registerAuthenticator(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
250 imap4.CramMD5ClientAuthenticator(encoded_username)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
251 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
252
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
253 return protocol_
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
254
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
255 def clientConnectionFailed(self, connector, reason: Failure) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
256 """Called when the client connection fails.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
257
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
258 @param reason: The reason for the failure.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
259 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
260 log.warning(f"Connection failed: {reason}")