1958 Commits

Author SHA1 Message Date
Rodolfo García Peñas (kix) 8689995549 handle exceptions for IMAP ID command to improve error reporting
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.
2026-05-21 17:47:21 +02:00
Rodolfo García Peñas (kix) 0290d94182 repository/IMAP: handle errors reading remotepassfile gracefully
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
2026-05-18 01:16:28 +02:00
Rodolfo García Peñas (kix) b03c5659fc utils/distro_utils: simplify return logic in get_os_sslcertfile_searchpath
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.
2026-05-17 11:51:47 +02:00
Rodolfo García Peñas (kix) 6613752e6f utils/distro_utils: replace platform.linux_distribution with distro.id()
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.
2026-05-17 11:42:04 +02:00
Rodolfo García Peñas (kix) 1f04bbd8bf folder/IMAP, repository/IMAP: make encoding conditional on utf_8_support
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>
2026-05-17 11:22:35 +02:00
Rodolfo García Peñas (kix) 56b8cbca01 imapserver: remove outdated comment regarding tryTLS flag in authentication methods 2026-05-17 11:06:03 +02:00
Rodolfo García Peñas (kix) 3ca32e98fc imapserver: remove outdated comment regarding STARTTLS capabilities 2026-05-17 09:06:57 +02:00
Rodolfo García Peñas (kix) 61291de09d Merge branch 'pr-247' into testing 2026-05-16 10:32:57 +02:00
Rodolfo García Peñas (kix) 46505c53ef imapserver: fix STARTTLS-stripping attack vulnerability
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
2026-05-16 10:30:39 +02:00
Andreas Schacker a81e267ce5 Fix typos 2026-05-15 22:27:23 +02:00
Rodolfo García Peñas (kix) 4bb9eb2e9d Merge branch 'pr-244' into testing 2026-05-15 10:24:09 +02:00
Rodolfo García Peñas (kix) 15cadd7545 Refactor IMAP folder handling to use encode_mailbox_name for folder name encoding
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.
2026-05-15 09:51:11 +02:00
Rodolfo García Peñas (kix) 59732669f0 Fix isusable method in Blinkenlights class to correctly check terminal status
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.
2026-05-13 18:47:44 +02:00
Rodolfo García Peñas (kix) cc48cc3b4c Fix isusable method signature in Blinkenlights class to use 'self'
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.
2026-05-13 18:46:24 +02:00
Rodolfo García Peñas (kix) b9f8726fb6 Refactor ncurses startup check in Blinkenlights to simplify exception handling
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.
2026-05-13 18:44:26 +02:00
Rodolfo García Peñas (kix) 1c4f9b4f05 Enhance STARTTLS handling with fallback for non-standard IMAP servers
- 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.
2026-05-13 18:35:54 +02:00
Noa Torstensvik 450accc6f8 Reevaluate oauth2_access_token_eval every time
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>
2026-05-09 14:20:52 +02:00
Rodolfo García Peñas (kix) d16afe503d Merge branch 'pr-241' into testing 2026-04-25 11:24:02 +02:00
Michael Hohmuth d4ccbbb049 Prevent deadlock in IMAPServer.close when using 'maxsyncaccounts' and 'maxconnections'.
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>
2026-04-24 14:27:20 +02:00
Rodolfo García Peñas (kix) 854f3891aa Robustly handle dead sockets during authentication by retrying the connection
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.
2026-04-20 09:43:37 +02:00
Rodolfo García Peñas (kix) 915dd0a510 Revert "fix: handle dead sockets during authentication by retrying remaining methods via AuthMethodSocketDeadError"
This reverts commit 38da540f99.
2026-04-20 09:14:30 +02:00
Rodolfo García Peñas (kix) 38da540f99 fix: handle dead sockets during authentication by retrying remaining methods via AuthMethodSocketDeadError
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.
2026-04-19 10:42:09 +02:00
Rodolfo García Peñas (kix) 177145e7cf Fix TLS stripping vulnerability when STARTTLS is requested (Issue #222)
ç
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`.
2026-04-19 10:37:19 +02:00
Rodolfo García Peñas (kix) 1680e0c623 Merge branch 'pr-239' into testing 2026-04-19 10:04:35 +02:00
Rodolfo García Peñas (kix) 0185b11255 Fix UnicodeEncodeError on emails with malformed bytes
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 #240
Closes #229
Closes #224
Closes #160
2026-04-18 23:10:29 +02:00
Derek Schrock 4d300783c7 Use correct IMAP folder name with imapobj.select
Like other imapobj.select translate it vi
imaputil.foldername_to_imapname
2026-04-05 19:34:12 -04:00
Rodolfo García Peñas (kix) 461ea973f6 Version bump to 8.0.2, update .gitignore for PyPI files, and remove setup.py.
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.
2026-04-06 00:14:52 +02:00
Rodolfo García Peñas (kix) 8122b6c03f Quick remote folder retrieval error handling
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.
2026-04-05 09:57:58 +02:00
Rodolfo García Peñas (kix) 3a5565bb5a Catch errors with remote folders
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.
2026-04-05 09:57:09 +02:00
Rodolfo García Peñas (kix) 9324259deb Check socket health before returning pooled connection
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.
2026-04-04 20:03:17 +02:00
Rodolfo García Peñas (kix) 59663f77f2 Check if the socket is alive
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.
2026-04-04 19:35:10 +02:00
Rodolfo García Peñas (kix) 4b97c358a8 Do not shutdown the socket in start_tls
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.
2026-04-04 17:37:19 +02:00
Rodolfo García Peñas (kix) ec7a4a95ae Solve imaplib2 hangs on STARTTLS
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.
2026-04-04 13:45:34 +02:00
Rodolfo García Peñas (kix) f5b461b183 Merge branch 'pr-238' into testing 2026-04-03 10:11:42 +02:00
Franklin Bynum 25238dc6eb Update distro_utils.py
Add relocated CA bundle for Fedora 44
2026-04-01 21:46:22 -05:00
serge-sans-paille 9c352d7d9c Always pass an exception as first argument of ui.error
In various cases we were passing string messages, which is incompatible
with how ui.error handles its argument, leading to bugs like #233

Fix #233

Signed-off-by: serge-sans-paille <sergesanspaille@free.fr>
2026-04-01 08:05:12 +02:00
Rodolfo García Peñas (kix) bcf2238db5 Version 8.0.1 2025-10-28 20:22:31 +01:00
Rodolfo García Peñas (kix) f14cead44f Merge branch 'master' of https://github.com/OfflineIMAP/offlineimap3 2025-10-28 16:20:36 +01:00
Tugdual Saunier 4c64d72882 fix: "module importlib has no attribute 'machinery'"
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.
2024-09-03 09:32:39 +02:00
Rodolfo García Peñas (kix) 1ae217fefa Avoid crash if no connection with server
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.
2024-08-28 01:13:05 +02:00
Rodolfo García Peñas (kix) db34745227 Merge pull request #154 from keithbowes/extra-search-paths
Find extra paths for default CA bundle through the SSL module if possible (Python 3.4+)
2024-08-26 18:14:57 +02:00
Rodolfo García Peñas (kix) 4f5b2529e4 Full packaging review
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
2024-08-26 18:01:14 +02:00
Rodolfo García Peñas (kix) 47f74c4408 Handle bare exceptions thrown while getting message ID
This patch removes the bare exception, as proposed by Florian Snow.
See PR170.

Closes #119
2024-08-24 15:47:58 +02:00
Keith Bowes 974c31d59c No longer create empty list items for possible locations of the CA file
Signed-off-by: Keith Bowes <keithbowes@users.noreply.github.com>
2024-08-18 16:11:27 -04:00
Keith Bowes 70f760d60d Find extra paths for default CA bundle trough the SSL module if possible (Python 3.4+)
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>
2024-08-15 23:13:26 -04:00
Rodolfo García Peñas (kix) 2559d74358 Merge pull request #146 from schspa/feat-add-apple-silicon-cert
sslcertfile: add ca-certificates from homebrew on apple silicon
2024-08-15 20:42:13 +02:00
Rodolfo García Peñas (kix) 40c0429aad Merge pull request #175 from roboshim/imaputil-quote-fix
first escape backslash, then quotes
2024-08-15 20:39:55 +02:00
Rodolfo García Peñas (kix) 625ae4ccbe Merge pull request #191 from WhyNotHugo/optional-keyring
Make keyring support optional
2024-08-15 20:25:49 +02:00
Rodolfo García Peñas (kix) 0be020f1cf Merge pull request #177 from roboshim/use-foldername-to-imapname-func
use already defined function foldername_to_imapname() to quote folder name
2024-08-15 15:45:31 +02:00
Rodolfo García Peñas (kix) 49185d15ec Merge pull request #179 from roboshim/fldrname-to-imapname-fix
Fldrname to imapname fix
2024-08-15 15:31:02 +02:00