We introduce a separate closing flag that's checked in
acquireconnection, decoupling the connectionlock from the semaphore
such that they can be acquired separately.
Signed-off-by: Michael Hohmuth <hohmuth@sax.de>
This commit addresses issues where certain IMAP servers (like Protonmail Bridge)
abruptly close the socket during the authentication phase.
Following a simplified approach:
1. Updated __authn_helper to initiate STARTTLS unconditionally at the
beginning of the loop.
2. Modified acquireconnection to catch OfflineImapError. If the socket is
detected as dead during authentication, it now logs a warning and retries
the entire connection process from scratch (up to 3 attempts).
3. This ensures each retry starts with a fresh connection and all configured
authentication methods available, avoiding permanent list modification.
The original Travis CI configuration was outdated, still targeting Python 2.7.
This commit updates the test matrix to include all supported Python 3 versions (3.6 through 3.13) to match the project's goals.
Additionally:
- Set dist: jammy (Ubuntu 22.04) for Linux builds to support modern Python versions.
- Updated the macOS (OSX) environment to use Python 3.12.0 for its tests.
Certain IMAP servers (like Protonmail Bridge) suffer from bugs where they hang and drop the connection when receiving specific authentication commands (such as `AUTHENTICATE PLAIN` immediately after STARTTLS). Previously, if the socket died during an authentication attempt, offlineimap would abort the entire synchronization process immediately.
This commit introduces `AuthMethodSocketDeadError` to handle this scenario gracefully. When an authentication mechanism kills the socket, it is temporarily removed from the list of available mechanisms for that session. Offlineimap then reconnects and retries the remaining authentication mechanisms (e.g., falling back from PLAIN to LOGIN), allowing synchronization to continue successfully without entering an infinite loop.
ç
When a user sets `starttls = yes`, offlineimap expects to negotiate a secure TLS tunnel. However, if a MITM attacker intercepts the connection and removes the `STARTTLS` capability from the server's greeting, the `if 'STARTTLS' in imapobj.capabilities` check fails silently. This causes offlineimap to skip TLS negotiation and proceed to send credentials in plaintext.
This commit introduces a strict check in `__start_tls()`: if the user requested STARTTLS but the server does not advertise it, offlineimap will immediately abort with a clear error message, protecting the user's password from being exposed. To connect insecurely, the user must explicitly set `starttls = no`.
When fetching emails with defects or malformed bytes (e.g., spam or broken
MIME boundaries), the Python 3 `email.parser` handles un-decodable bytes
by safely substituting them with the Unicode Replacement Character `\ufffd`
(using the `errors='replace'` handler by default).
However, a problem arises during the `_fetch_from_imap` sync process when
OfflineIMAP3 tests if the message can be serialized back to bytes via
`as_bytes()`. If the malformed text part was originally declared as
`us-ascii` (or left unspecified, defaulting to ASCII), Python attempts to
encode the `\ufffd` string back to bytes using the ASCII codec. Since `\ufffd`
is out of the ASCII range, this triggers a `UnicodeEncodeError`, causing
OfflineIMAP3 to raise an `OfflineImapError` and entirely skip syncing the
message.
This commit fixes the issue by catching the `UnicodeEncodeError` when
`as_bytes()` fails. It then walks through the message parts, identifies
the text payloads that cannot be encoded with their current charset, and
dynamically forces their charset to `utf-8`. This allows Python to safely
encode the `\ufffd` character (automatically applying base64/quoted-printable
transfer encoding if needed), successfully serializing the message so it
can be synced without crashing.
Closes#240Closes#229Closes#224Closes#160
This patch updates the version number to 8.0.2 in both pyproject.toml and offlineimap/__init__.py, removes the now-obsolete setup.py file, and adds a line to .gitignore to ignore the /dist directory where PyPI distribution files are generated.
This patch adds error handling for the retrieval of remote folders in the `SyncableAccount` class. If an error occurs while getting the remote folders, a warning message is logged, and the account is skipped, allowing the synchronization process to continue with the next account.
This patch adds error handling when fetching remote folders. If an error occurs, it logs a warning and continues with the next account instead of crashing the entire sync process.
This patch adds a health check for pooled IMAP connections in the acquireconnection() method. After acquiring a connection from the pool, it sends a NOOP command to verify that the connection is still alive. If the NOOP command fails, it logs the failure, cleans up the stale connection, and recursively calls acquireconnection() to get a new one. This helps ensure that clients receive healthy connections from the pool and reduces the likelihood of encountering errors due to stale connections.
This patch adds a helper function `_is_socket_alive` that checks if the underlying socket connection of an IMAP object is still usable. It uses `select` to check for readability and errors on the socket, and also checks if the socket has been closed by the peer. This function is called before attempting authentication methods to avoid long timeouts if the socket is already dead (e.g., after a failed STARTTLS). If the socket is found to be broken, it logs a debug message and aborts further authentication attempts.
This commit removes the redundant shutdown() calls in start_tls() and acquireconnection() that were added to work around a TLS connection failure. The shutdown() calls were causing spurious warnings in the cleanup chain when the socket was already closed. The TLS connection failure should now be handled by the exception raised in start_tls() without needing to shutdown the socket again.
This avoid problems with the socket being closed twice and generating warnings, while still properly handling the TLS connection failure. The exception raised in start_tls() will indicate the failure without needing to shutdown the socket again.
This patch works around imaplib2's unconditional CAPABILITY call after the TLS handshake, which causes a 60-second hang with servers like Protonmail Bridge that do not respond to CAPABILITY at that stage. The workaround temporarily substitutes a no-op for imaplib2's _get_capabilities method during the handshake, allowing it to set _tls_established=True without triggering the problematic CAPABILITY call. The patch also guards against a redundant second shutdown() call on the same socket, which can raise [Errno 9] Bad file descriptor and generate spurious warnings in the cleanup chain.
In various cases we were passing string messages, which is incompatible
with how ui.error handles its argument, leading to bugs like #233Fix#233
Signed-off-by: serge-sans-paille <sergesanspaille@free.fr>
It appears urllib3 is only used in contrib/internet-urllib3.py, so put
it with certifi in the `testinternet` optional-dependencies group.
Signed-off-by: Stephen Huan <stephen.huan@cgdct.moe>
After PEP 639 the `license` field in pyproject.toml is
no longer a table but a SPDX Expression containing a
License Identifier. This fixes the following warning.
********************************************************************************
Please use a simple string containing a SPDX expression for `project.license`. You can also use `project.license-files`. (Both options available on setuptools>=77.0.0).
By 2026-Feb-18, you need to update your project and remove deprecated calls
or your builds will no longer be supported.
See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.
********************************************************************************
In addition, be more consistent about the license
being GPL v2.0 or later (as opposed to v2 only).
Signed-off-by: Stephen Huan <stephen.huan@cgdct.moe>
Newer pythons are strict on the escape sequences allowed in normal
strings, which can cause issues with regular expressions as most of them
do include escape sequences that are not allowed. In those cases, the
process will exit with the confusing warning:
<string>:1: SyntaxWarning: invalid escape sequence '\.'
To help prevent such cases, have the sample configuration use raw
strings when using regular expressions.
Signed-off-by: Luciano Rocha <lfrocha@gmail.com>
Since the merge of #190, the following errors happens:
```
ERROR: module 'importlib' has no attribute 'machinery'
ERROR: Exceptions occurred during the run!
ERROR: AttributeError: module 'importlib' has no attribute 'machinery'
```
This commit should fix this by importing the required package.
This patch allow run OfflineIMAP without connection with the server,
therefore the application is running and do not crash if the connection
fails or the network is temporaly down.
This patch includes a lot of changes:
1. Split the `requirements.txt` file in multiple files. This change holds
the required packages for OfflineIMAP in the `requirements.txt` file.
The optional packages are included in the files `requirements-option.txt`
files. Now the standard OfflineIMAP configuration does not include
packages like `cygwin`. See for example issue #192.
2. The `setup.py` process includes a lot of files (see issue #110). This
creates a problem in the setup process, because some libraries are not
found (see #39, the problem still happends). For this reason we can
read the variables from the `offlineimap/__init__py` to include them
in the `setup.py` script, without import the `offlineimap` module. I
used the method presented at `https://www.youtube.com/watch?v=fHNhhHMUW7k`.
In the setup module we don't need the testing code (it creates the import
problem too), so this code is removed. To read the variables, we use
some regex search in the `offlineimap/__init__py` file, save the
values as variables, and then use them in the `setup()` call.
3. `setup.py` uses requires and extra_requires libraries, aligned with
the `requirements` files. We use four different options: `kerberos`,
`keyring`, `cygwin`, `cygwin` and `testinternet`.
4. `pyproject.toml`. This file is fully rewritten. The file use now the
right dependencies, includes the optional dependencies aligned with
the requirements and the `setup.py` files. The file include other
details, like classifiers, URLs,... This script uses now the the
`project.scripts` option, with the module and the method to call when
the setup file is created. Then, this script includes as module
`offlineimap.init`, and the startup method is `main`. Because this
method is new, this method and the `__main__` functions are created
in the `offlineimap/init.py` file:
```python
def main():
oi = OfflineImap()
oi.run()
if __name__ == "__main__":
main()
```
With these changes, the setup process works fine, with and without
optional modules. Finally, the folder `offlineimap.egg-info`,
created in the setup process is included in the `.gitignore` file.
It is possible check the creation using:
```python
python -m pip install .
```
And then use the command `offilineimap` to use the module. Finally, the
`bin/offlineimap` command is not used, so we probably can remove it.
Fix: #192, Fix: #110, Fix: #39, Fix: #90