This commit adds a try-except block around the IMAP ID command to catch any exceptions that may occur during its execution. If an exception is raised, it will log a warning message with the details of the error, improving the error reporting and allowing users to understand what went wrong without crashing the application.
Replace the bare open()/close() pattern with a context manager and
wrap I/O and encoding failures in OfflineImapError so the user gets
a clear, actionable message instead of a raw Python exception.
Before this change, a missing, unreadable, or non-UTF-8 password
file would surface as an unhandled FileNotFoundError, PermissionError
or UnicodeDecodeError with no indication of which repository was
affected.
The new error message includes the expanded file path and the
repository name:
Unable to read remotepassfile '/path/to/pass' for repository 'Name': ...
Closes: #248
This patch simplifies the return logic in the get_os_sslcertfile_searchpath function by removing the unnecessary try-finally block and directly checking the length of the location list after attempting to append hardcoded paths. If no valid paths are found, it returns None. Previously, using a finally with a return is a bug because it can mask exceptions and lead to unexpected behavior. This change makes the code cleaner and more straightforward.
platform.linux_distribution() was deprecated in Python 3.7 and
removed in Python 3.8. The try/except that fell back to
distro.linux_distribution() was therefore dead code in the try branch
on any supported Python version, making the distro package a de-facto
hard dependency already.
Replace the try/except with a direct top-level import of distro and
use distro.id() instead. distro.id() returns a normalised, lowercase,
hyphen-separated identifier (e.g. "ubuntu", "opensuse-leap", "rhel")
that does not need the .split()[0].lower() post-processing that the old
code applied to the human-readable distribution name.
Because distro.id() returns different identifiers than
linux_distribution()[0].split()[0].lower() did, the CA certificate
lookup table (__DEF_OS_LOCATIONS) is updated to match:
Old key New key(s) distro.id() value
─────────────────────────────────────────────────────────────────
linux-redhat → linux-rhel "rhel"
linux-suse → linux-sles, linux-sled "sles" / "sled"
linux-opensuse → linux-opensuse-leap, "opensuse-leap" /
linux-opensuse-tumbleweed "opensuse-tumbleweed"
Based on a patch from Adam Dinwoodie, as forwarded by Etienne Buira.
When utf_8_support is False (the default, standard RFC 3501 mode),
folder names received from the server are in Modified UTF-7 and must
be kept in that encoding internally. When utf_8_support is True,
names are decoded to UTF-8 for internal use.
Previous code decoded unconditionally in IMAPFolder.__init__, which
would corrupt non-ASCII names received as Modified UTF-7 when
utf_8_support is False. The inverse problem existed in
getfullIMAPname() and the three encode_mailbox_name() call sites in
IMAPRepository: they always converted UTF-8 → Modified UTF-7 before
sending to the server, which is wrong when utf_8_support is False
(names are already in Modified UTF-7 and must not be double-encoded).
Fix by applying the same conditional pattern consistently:
if account.utf_8_support:
name = imaputil.utf8_IMAP(name) # UTF-8 → Modified UTF-7
return imaputil.foldername_to_imapname(name)
This is applied in:
- IMAPFolder.__init__ (decode on receive)
- IMAPFolder.getfullIMAPname (encode before SELECT)
- IMAPRepository.getfolders (folderincludes SELECT)
- IMAPRepository.deletefolder
- IMAPRepository.makefolder_single
encode_mailbox_name() (which always assumed UTF-8 input) is removed
as it is no longer used anywhere.
Based on patch by Etienne Buira <etienne.buira@free.fr>
When `starttls = yes` is configured and the server does not advertise
STARTTLS in its capability list, the previous code silently returned
without attempting TLS, leaving the connection in cleartext.
RFC 2595 section 9 explicitly documents this attack vector:
"A man-in-the-middle attacker can remove STARTTLS from the
capability list or generate a failure response to the STARTTLS
command."
Silently skipping STARTTLS in that case makes offlineimap completely
vulnerable to such capability-stripping attacks: the attacker wins by
simply removing the STARTTLS keyword from the server greeting.
Fix: when STARTTLS is not advertised but is configured, emit a warning
and attempt STARTTLS anyway. If the server genuinely does not support
it, imaplib will raise an error and the existing error-handling path
raises OfflineImapError, aborting the connection. If the attempt
succeeds, TLS is established normally.
The resulting behaviour is:
- Server advertises STARTTLS → unchanged, works as before.
- Server does NOT advertise STARTTLS (possible MITM strip):
- Warning is logged to alert the user.
- STARTTLS is attempted regardless.
- If the server rejects it: OfflineImapError, connection aborted.
- If the server accepts it: TLS is established, sync continues.
Reported by: hartwork
References: RFC 2595 §9
This commit refactors the IMAP folder handling in the offlineimap repository to use the `encode_mailbox_name` function for encoding folder names. This change ensures that folder names are properly encoded when interacting with the IMAP server, improving compatibility and reliability when dealing with folder names that may contain special characters or non-ASCII characters. The `encode_mailbox_name` function combines UTF-8 encoding and quoting as needed, providing a consistent way to handle folder names across the codebase.
This commit modifies the isusable method in the Blinkenlights class to properly check if the standard output is a terminal. The previous implementation had an incorrect condition that could lead to false positives. The new implementation checks only if sys.stdout is a terminal, which is the correct way to determine if curses can be used. Additionally, it includes a test to ensure that ncurses can start up without issues.
This patch corrects the method signature of the 'isusable' method in the 'Blinkenlights' class to include 'self' as the first parameter, which is necessary for instance methods in Python classes.
The previous implementation had a conditional check for Python versions to avoid calling `curses.initscr()` twice, which could cause issues in certain versions of Python. The refactored code removes the version check, because is always True (version is 3 now) and simply attempts to start ncurses, catching any exceptions that may occur. This simplifies the code and ensures that it works correctly across all supported Python versions without relying on specific version checks.
- Implement tolerance for non-standard IMAP servers that do not provide reliable CAPABILITY after STARTTLS.
- Introduce configuration option 'allow_nonstandard_capabilities' to enable fallback using pre-TLS capabilities.
Before this, authentication will fail after the access token has
expired, and if this is handled by an external program using
oauth2_access_token_eval, offlineimap3 will be able to renew the token
by itself. We should allow oauth2_access_token_eval to run in this case.
One might imagine that authentication needs to be reperformed for
example because of bad internet, and that the token will therefore be
refreshed unnecessarily. I believe most users of
oauth2_access_token_eval will use caching and that this will not be a
big problem.
Signed-off-by: Noa Torstensvik <noa@torstensvik.se>
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.
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>
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
For me at least, this changed from it from giving the message
"ERROR: Default CA bundle was requested, but OfflineIMAP doesn't know any for your current operating system"
to actually working.
Signed-off-by: Keith Bowes <keithbowes@users.noreply.github.com>