Compare commits

...

179 Commits

Author SHA1 Message Date
Chris Coleman ffbace51a2
Update BACKERS.md 2023-03-30 07:19:04 -04:00
Chris Coleman 7eacce1f58
Update BACKERS.md
Add logo (in feature branch of website, pending merge) with link to sponsor website!
2023-03-29 07:00:35 -04:00
Chris Coleman 87b04f0f54
Update BACKERS.md 2023-03-29 04:39:47 -04:00
Chris Coleman 8bd94ee8bc
Create BACKERS.md
Work in Progress. 
For all Bronze and Silver Sponsors, this page is supposed to show sponsoring company logo and link to sponsoring company website, also for Silver, same on sponsors page of our website.
Gold and Platinum go in `README.md` and on front page of our website.
2023-03-29 04:35:40 -04:00
Nicolas Sebrecht e70d3992a0 README: rewording and typo fix
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2021-08-03 01:24:07 +02:00
Nicolas Sebrecht 99a7f74805 Changelog: fix version
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2021-08-03 01:12:52 +02:00
Nicolas Sebrecht 6e837c614d v7.3.4
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2021-08-03 01:07:23 +02:00
Nicolas Sebrecht 1953001196 Merge remote-tracking branch 'OfflineIMAP/master' into next 2021-08-03 01:06:07 +02:00
Nicolas Sebrecht a4ee6675ce redirect the users to offlineimap3
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2021-08-03 00:55:07 +02:00
Nicolas Sebrecht a2a92e7433 remove outdated links to travis
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2021-08-03 00:54:44 +02:00
Nicolas Sebrecht feec770809
Merge pull request #698 from cathalgarvey/patch-1
Lack of Python 3 support needs to be more clear..
2021-07-29 01:24:24 +02:00
Cathal Garvey 069c05a0c2
Lack of Python 3 support needs to be more clear..
I saw the top line, and the requirement for `six`, and took that to mean that Python 3 was supported. After wasting some of my time setting up and dealing with opaque bugs, I discovered that Python 3 is not supported after all.

"2.7+" implies modernity, so I'm suggesting we substitute "2.7.x" to be explicit that this is Python 2 only, and strike through the Python 3 line to be blunt that it is not working.
2021-05-18 13:07:55 +01:00
Nicolas Sebrecht 2d0d07cd6a README: update regarding the offlineimap3 fork
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/670
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-09-05 01:23:38 +02:00
Rodolfo García Peñas (kix) 595b814074 ui init is lintian clean
This patch only adds an space between the hash and the first character.

Backported from:
a88a9cf28e

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-08-29 23:23:28 +02:00
Rodolfo García Peñas (kix) 2722234d16 threadutil imports not used
These imports are not used.

Backported from:
a655fa4fc2

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-08-29 23:23:28 +02:00
Rodolfo García Peñas (kix) 3d925b006a Changed wrong comparison equal
Backported from:
e84e932df4

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-08-29 22:54:15 +02:00
Rodolfo García Peñas (kix) c7cb68ad7f Comparison error
Backported from:
75ce6e71f3

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-08-29 22:53:16 +02:00
Reto Schnyder f6935342c2 Fix hooks for IDLE sync
`presynchook` and `postsynchook` for IDLE-triggered syncs were broken by
da69fd8. This fixes them.

Signed-off-by: Reto Schnyder <reto.a.schnyder@bluewin.ch>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-08-22 01:54:48 +02:00
Nicolas Sebrecht 21332efb08 folder: IMAP: fix issue when the response of searchforheaders is the same UID multiple times
When the IMAP server doesn't support the UIDPLUS extension so we fallback on the
internal legacy way of mapping the UID to the uploaded message (with the email
header). If the server responds with 2 UIDs offlineimap doesn't know which one
is correct and reports an error.

If for some reason all the returned UIDs are equals it's very likely fine to
map either one.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Tested-by: https://github.com/mpsq
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/676
2020-07-22 22:31:01 +02:00
Nicolas Sebrecht fdb9974ab7 contrib/release..py: the version information has moved to offlineimap.version
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-04-22 16:07:20 +02:00
Martin Di Paola c13e0135a7 Require the minimal dependencies in python package
When an user installs offlineimap from PyPI using pip, the dependencies
of offlineimap are not installed automatically. See #661.

Requiring explicitly the dependencies in the setup.py adds them in the
metadata of the package so pip can install them next with offlineimap.

To avoid duplicated dependencies, requirements.txt delegates to setup.py
the listing of the minimal dependencies while also adding two more
optional dependencies.

Signed-off-by: Martin Di Paola <martinp.dipaola@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-04-22 15:51:13 +02:00
Martin Di Paola 8599cab2ab Move out pkg attributes from __init__.py
The setup.py uses the version, author and others attributes as metadata
for the python package.

The setup read them from offlineimap/__init__.py doing an import of the
module first.

Unfortunately the import also try to import all the dependencies of
offlineimap which may not be installed by the time. See #661.

Moving out the attributes in a separated module allows to be imported by
setup.py whitout needing to import the whole offlineimap.

The import of test.OLItest has the same limitation. In this case the
import was delayed until the real test case run is executed avoiding
again loading offlineimap from the begin.

Signed-off-by: Martin Di Paola <martinp.dipaola@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-04-22 15:51:13 +02:00
Nicolas Sebrecht 7531ac4640 v7.3.3
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-04-11 01:09:25 +02:00
Herton R. Krzesinski 6a25f1930c Fix stale gss api authentication security context
Inside __authn_gssapi function, the else clause is never executed
because the return statement in the try section, which means if there is
an error and a reconnect is tried, the authentication will now fail with
due the stale self.gss_vc value. For example, offlineimap will be
stuck after any socket error and unable to reconnect, even if I have a
valid kerberos ticket:

========================================================================
abort: command: FETCH => socket error: <type 'exceptions.IOError'> - Too many read 0

  command: FETCH => socket error: <type 'exceptions.IOError'> - Too many read 0
 GSSAPI authentication failed: AUTHENTICATE command error: BAD ['AUTHENTICATE aborted']. Data: BLMC2 AUTHENTICATE GSSAPI

Enter password for user 'XXX':
========================================================================

You can verify this try..finally behaviour with this slightly modified
example that I copied from python documentation:

>>> def divide(x, y):
...     try:
...         result = x / y
...         return 1
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
executing finally clause
1
>>>

The else section is never executed with a return inside try.

To fix the issue here, instead of relying on else clause, just clear
gss_vc always inside finally, and we don't need to handle any exception
to set self.gssapi, it can be left False by default and just set to True
after authentication is done.

I'm running with this fix and now offlineimap doesn't stop requiring manual
intervention, and succesfully re-authenticate after errors while fetching
data.

Signed-off-by: Herton R. Krzesinski <herton@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-04-11 01:01:44 +02:00
Frank LENORMAND da69fd81ed export env. variables when running account hooks
This commit allows account hooks (pre/post sync) to access contextual
information:

* `OIMAP_ACCOUNT_NAME`: name of the account being synchronized
* `OIMAP_HOOK_NAME`: name of the hook being run (one of `postsynchook`, `presynchook`)

These variables allow using the same hook program (e.g. a script) for
all account synchronisation operations, and running different commands
depending on the stage of the synchronisation or the target account.

Signed-off-by: Frank LENORMAND <lenormf@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-03-23 23:01:30 +01:00
martin f. krafft 4562b1c5d1 exec() the tunnel command
There is no reason that the shell invoking the tunnel command needs to stay
around. Using `exec` to replace the parent with the tunnel command works just
fine and results in a cleaner process table.

Sent-by: martin f. krafft <madduck@madduck.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-03-22 09:02:41 +01:00
Chris Coleman 84efb45270 Handle [ALREADYEXISTS] and Mailbox already exists!
Make compatible with IMAP servers that give the reason code "[ALREADYEXISTS]"
and IMAP servers that give natural language reason "Mailbox already exists!" by
searching for the two words "already" and "exists" in the exception reason
string.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2020-02-06 19:10:33 +01:00
Nicolas Sebrecht 564930725e v7.3.2
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-12-17 20:01:18 +01:00
Nicolas Sebrecht 1e4990524e Revert "fix check for unsupported sep character"
This reverts commit 4208fd4a15.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-12-17 19:58:53 +01:00
Chris Coleman 50cfab5ba2
Merge pull request #639 from svetlemodry/arch-linux-name
Fixing the Arch Linux name in README.md
2019-12-16 13:28:25 -05:00
Jaroslav Lichtblau 3c80607907 Fixing the Arch Linux name
according to the naming convention as described in the wiki:
https://wiki.archlinux.org/index.php/Arch_terminology#Arch_Linux
2019-12-16 18:24:15 +01:00
Nicolas Sebrecht f6e08b8609 v7.3.1
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-12-15 23:51:24 +01:00
Chris Coleman 85c7692284
Update README.md
Move "Financial contributors" badge, just below the "build statuses".
2019-12-15 14:13:46 -05:00
Chris Coleman c35e9257fd
Merge pull request #638 from opencollective/opencollective
Activating Open Collective (add badge and show contributors and financial contributors in README.md).
2019-12-15 14:08:57 -05:00
Jess c391141b56 Added financial contributors to the README 2019-12-15 10:00:59 -08:00
Nicolas Sebrecht c53664544e introduce FUNDING.yml for opencollective
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-12-06 00:44:55 +01:00
Chris Coutinho 403ddf9c1c Additional address for sysloghandler to handle mac
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-10-12 19:58:44 +02:00
Nicolas Sebrecht 4208fd4a15 fix check for unsupported sep character
Introduced by 6b28071 but could not work.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-10-04 00:00:32 +02:00
Nicolas Sebrecht 0b18cb5e73 ensure python2 in the release workflow
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-08-19 19:35:13 +02:00
Nicolas Sebrecht 3d918ff7fe make docs: ensure py2 when running sphinx
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-08-19 19:33:34 +02:00
Nicolas Sebrecht 4464195326 contrib: use yaml.safe_load() instead of load()
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-08-19 19:20:29 +02:00
Nicolas Sebrecht ba4ecea9e4 v7.3.0
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-08-19 19:17:53 +02:00
Dario Maiocchi 805a1c156c update readme to give an hint about Linux distros
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-08-17 13:21:00 +02:00
Nicolas Sebrecht 9c6a1760b4 imaplib2 v2.101
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-06-18 23:50:29 +02:00
Ilias Tsitsimpis beaaf77a1a Do not use TIMEOUT_MAX for Condition.wait()
On some architectures, using threading.TIMEOUT_MAX for the timeout
parameter can overflow causing Condition.wait() to return immediately.
Instead of relying on TIMEOUT_MAX, remove it and wait forever.

Signed-off-by: Ilias Tsitsimpis <iliastsi@debian.org>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-06-18 23:16:04 +02:00
Julien Cristau 0a831b3cab Use SSLContext if available so we send SNI
Fixes https://github.com/imaplib2/imaplib2/issues/5

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-06-18 23:13:35 +02:00
Nicolas Sebrecht 741b33b455 imaplib2 v2.100
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-06-18 23:10:21 +02:00
Ben Cotterell 8d5b22bd09 Don't expect trailing space on command completion
While trying to see why I couldn't get my emails from an Exchange server
I found this:

[imap]: 41:31.42 someserver.com handler _put_response(IOMC1 OK)
[imap]: 41:31.42 someserver.com handler unexpected response: 'IOMC1 OK'

And shortly after that the connection was closed. IOMC1 is just the
unique tag for the session.

The pattern looks for the tag, a number, a word like "OK" or something,
*then a space*, then optionally some data.

If the data aren't there it shouldn't be expecting a space.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-06-18 23:04:26 +02:00
Nicolas Sebrecht 8406ba3bb4 travis: remove python3.6
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-06-10 17:36:20 +02:00
Nicolas Sebrecht c97fe498e6 README: add required dependency to rfc6555
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-06-10 14:37:25 +02:00
Olivier Mehani 0d5496ba0a implement Happy Eyeballs
This allows OfflineIMAP to not stall on malfunctional IPv6 connections,
and fall-back to a functional IPv4 connection, if faster, as described
in RFC6555.

Signed-off-by: Olivier Mehani <shtrom@ssji.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-06-10 14:36:53 +02:00
Nicolas Sebrecht 06ed00a211 v7.2.4
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-06-08 11:32:57 +02:00
Nicolas Sebrecht 801893cc73 make portalocker really optional
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-04-27 14:56:17 +02:00
kimim 93bc8d11ac add portalocker to README and requirements
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-04-27 14:37:44 +02:00
kimim 2806f40071 use portable locker to support cygwin in Windows
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-04-27 14:37:44 +02:00
Jelmer Vernooij 8a7946e338 Update FSF postal address.
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-04-06 23:30:47 +02:00
Nicolas Sebrecht 64c763c7f5 PULL_REQUEST_TEMPLATE: add space between brackets to enable the edition in the gui
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-02-27 08:48:59 +01:00
Nicolas Sebrecht 0d865c2e36 repository/IMAP: update copyright header date
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-02-27 08:48:59 +01:00
Kyle Altendorf 57a43f5bba mkdir -p alike folder creation
When creating a folder in one command on Gmail you end up with
one tag such as 'x/y/z' instead of three nested tags such as 'z'
inside of 'y' inside of 'x'.  Creating each layer individually
results in the desired nested label structure.

This was tested in a personal transfer of email from a remote
IMAP server to Gmail.

OfflineIMAP/offlineimap#335
OfflineIMAP/offlineimap#598

Signed-off-by: Kyle Altendorf <sda@fstab.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-02-27 08:45:57 +01:00
Nicolas Sebrecht f9aefed704 contrib/release.py: don't break if sphinx-build is missing
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-02-17 00:54:17 +01:00
Nicolas Sebrecht 5c735fd327 v7.2.3
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2019-02-17 00:31:19 +01:00
Mart Lubbers c9f71b0c64 add checks in curses ui for small windows
addch() and addstr() throw an exception if text has to be printed
outside of the window. This may occur if the terminal is very small.
Such erroneous prints are no-ops now.

Signed-off-by: Mart Lubbers <mart@martlubbers.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/595
2019-02-13 16:32:02 +01:00
Nicolas Sebrecht 137130fbd0 v7.2.2
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-12-22 18:46:34 +01:00
Robbie Harwood 096aa07650 Handle empty token with complete GSSAPI context
This fixes a potential traceback when we try to unwrap(None).

Tested-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-12-14 21:28:30 +01:00
Nicolas Sebrecht a51064e83a maxage: always compute the remote cache list for min_uid
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/581
Tested-by: https://github.com/ianbrody
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-12-11 09:29:37 +01:00
Nicolas Sebrecht 698ec64319 offlineimap.conf: minor fixes
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-12-02 00:38:05 +01:00
Frode Aannevik 8692799e65 Fix expired oauth2_access_token
Use `expires_in` from the oauth2 response
to reset the oauth2_access_token before
it expires

divides the `expires_in` by 2 to ensure
the access_token is cleared before it
expires

ref: https://github.com/OfflineIMAP/offlineimap/issues/536

Signed-off-by: Frode Aannevik <frode.aa@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-11-18 21:07:54 +01:00
Philippe Loctaux af3a35ae30 offlineimap/utilis/distro.py: identation fix
Fixed a indentation style.

Signed-off-by: Philippe Loctaux <loctauxphilippe@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-08-17 18:49:04 +02:00
Philippe Loctaux 761e10e8b1 Add Archlinux to list of supported distros
There is a bug with `platform.linux_distribution()`, which returns an
empty value on Archlinux with python2.

This bug is fixed in python3, but *will not* be fixed in python2.

This patch fixes that issue with a dirty hack: on archlinux, there is a
file that can be used to detect an archlinux machine. that file is
`/etc/arch-release`. if the file exists, then the OS variable will be
set to "linux-arch".

You can learn more about that issue on the python bug platform:
https://bugs.python.org/issue20454

Signed-off-by: Philippe Loctaux <loctauxphilippe@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-08-17 18:49:04 +02:00
Nicolas Sebrecht d9301254ff fix dates in copyright lines
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-08-13 01:15:01 +02:00
Philippe Loctaux 2890dec37f Added ssl certfile on osx for openssl pacakge on homebrew
A certfile was already specified for osx but only with MacPorts,
this patch adds the certfile given with the package `openssl` with
homebrew.

You can get more info with the command `brew info openssl` on osx with
homebrew and openssl installed.

Signed-off-by: Philippe Loctaux <loctauxphilippe@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-08-10 12:48:17 +02:00
Philippe Loctaux 7af4728d62 Add Archlinux to list of supported distros
get_os_name returns linux-arch on Archlinux, so add a line for linux-arch to __DEF_OS_LOCATIONS.

Signed-off-by: Philippe Loctaux <loctauxphilippe@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-08-10 12:42:29 +02:00
Benedikt Heine ce9a1981c1 Chain tls_level and ssl_version only if ssl is enabled
If the tls_level is set to a cipherset other than tls_compat, the
ssl cipherset has to get specified extra, if ssl is used.

But if the user explicitly disabled SSL, and set tls_level to anything
else than tls_compat required the user to explicitly set ssl_version,
which is contradicting.

Signed-off-by: Benedikt Heine <bebe@bebehei.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-07-02 18:44:08 +02:00
Benedikt Heine d3ba837900 Fix typo in exception message
Signed-off-by: Benedikt Heine <bebe@bebehei.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-07-02 18:44:08 +02:00
Nicolas Sebrecht 6ef5937a5c docs/website-doc.sh: minor improvements in comments of versions.yml
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-06-16 15:29:11 +02:00
Nicolas Sebrecht 4544bb1305 contrib/release.py: minor UI improvement
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-06-16 15:18:08 +02:00
Carnë Draug c9005cd4ff Check if username is provided before trying plain authentication.
If the username value is not provided on the configuration file, then
username if of NoneType and NULL.join will bomb since Nones can't be
joined with strings.  So check if username is empty and raise an
exception if so.

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/558
Signed-off-by: David Miguel Susano Pinto <carandraug+dev@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-06-16 15:17:35 +02:00
Carnë Draug 5f9474e10d Print username instead of accountname when asking for password
When asking for a password interactively, the username is never
displayed which may hide problems (typos on the configuration, or
issues on offlineimap parsing of the config file).  The hostname,
port, and account name are already displayed when establishing the
connection.  When asking for password, the account name is displayed
again.  Change it to display the username.

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/558
Signed-off-by: David Miguel Susano Pinto <carandraug+dev@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-06-16 15:17:35 +02:00
Nicolas Sebrecht 11313a9b9c v7.2.1
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-06-16 15:08:07 +02:00
Lorenzo c865dcc03c Script to store passwords in a file with GPG or using OSX's secure keychain
Submitted-by: https://github.com/lorenzog
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-06-10 14:21:01 +02:00
Nicolas Sebrecht b5ffa1c163 imapserver: fix copyright line
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-06-07 18:39:08 +02:00
Robbie Harwood 17cfb63db6 Pass username through in GSSAPI connections
Fix bug in GSSAPI auth where the username was not being negotiated.

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/541
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
Tested-by: Frank Lenormand <lenormf@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-06-07 18:36:58 +02:00
Nicolas Sebrecht fc61c6fd30 Gmail: allow parenthesis in labels
Reported-by Heinrich Hartmann <Heinrich@HeinrichHartmann.com>
Tested-by Heinrich Hartmann <Heinrich@HeinrichHartmann.com>
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/545
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-18 02:10:56 +02:00
Nicolas Sebrecht 356bd0b78f Merge branch 'fingerprint-colons' into next
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-15 00:41:22 +02:00
velleto cf95a1b341 Documented the now allowed use of colon separated fingerprints with examples.
Signed-off-by: velleto <rr@velleto.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-15 00:39:59 +02:00
velleto c126b4286d Allow users to keep colons between each hex pair of server certificate fingerprint in configuration file.
Signed-off-by: velleto <rr@velleto.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-15 00:39:59 +02:00
velleto c5f8603ff2 Available hashes added to documentation.
Signed-off-by: velleto <rr@velleto.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-15 00:38:06 +02:00
velleto b150daaa49 Added support for sha512, sha384, sha256, sha224 hashing algorithms to calculate server certificate fingerprints.
Signed-off-by: velleto <rr@velleto.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-15 00:38:06 +02:00
velleto 5180b964d8 Removed uneccessary call of list() on zip() object.
Signed-off-by: velleto <rr@velleto.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-15 00:38:06 +02:00
velleto ac9ed47262 Changed the 'exception raised' message, to be more understandable.
Signed-off-by: velleto <rr@velleto.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-15 00:38:06 +02:00
Michael Billington db4a5e149f Correct typographical errors in offlineimap.conf
Signed-off-by: Michael Billington <michael.billington@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-12 19:59:58 +02:00
Edgar HIPP 89b40ec3af Make CTRL-C message more clear
Signed-off-by: Edgar Hipp <ehipp@hotmail.fr>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-01 16:21:57 +02:00
Nicolas Sebrecht 33384ec6fb setup: add long_description
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-05-01 03:48:26 +02:00
Nicolas Sebrecht 487c671d88 offlineimap.conf: fix comment about gssapi
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-04-27 12:17:03 +02:00
Nicolas Sebrecht e4fa84ba1f Merge remote-tracking branch 'OfflineIMAP/master' into next 2018-04-20 23:38:08 +02:00
Chris Coleman 88197a7e90
Add self to maintainers. Update email address. 2018-04-20 12:55:25 -04:00
Eygene Ryabinkin 3a807d0f2b Create filenames with no path separators in them
We use current hostname as the element of the unique file name.
Sometimes there is non-/24 zone delegation,
{{{
$ host 144.206.233.65
65.233.206.144.in-addr.arpa is an alias for 65.26/64.233.206.144.in-addr.arpa.
}}}
as per RFC 2317,
  https://www.rfc-editor.org/rfc/rfc2317.txt

So on Un*x systems we may run into having path separator inside
the file name.  Not good, things will choke.  Prevented this
by substituting all appeared path separators in the return value.

Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
Tested-at: my MacOSX instance, my FreeBSD instances
2018-04-09 20:09:56 +03:00
Nicolas Sebrecht 0ad8bb25ad Makefile: targz: don't set the abbrev in the archive directory name
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-04-09 17:17:49 +02:00
Nicolas Sebrecht 57b2794bfb contrib: learn to build website/_uploads
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-04-09 17:07:45 +02:00
Nicolas Sebrecht dd0be5e166 docs/website-doc.sh: limit the number of exported versions in _data/announces.yml
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-04-09 01:38:18 +02:00
Nicolas Sebrecht da86c2f564 Makefile: targz: update files
Remove the --transform option for tar.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-04-09 01:21:36 +02:00
Nicolas Sebrecht 04932f18e4 Makefile: clean: remove __pycache__ directories
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-04-09 00:30:03 +02:00
Nicolas Sebrecht a6de848a23 v7.2.0
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Tested-by: Remi Locherer <remi.locherer@relo.ch>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-04-07 20:17:52 +02:00
Nicolas Sebrecht 2b64e100b0 README: travis: add badge for the next branch
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-06 22:15:37 +01:00
Nicolas Sebrecht 08ce510cfb travis: add notification to gitter room OfflineIMAP/offlineimap
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-06 01:13:44 +01:00
Nicolas Sebrecht 52e8483bbe travis: save secure variables
README: minor typo fix.

Originally-written-by: Chris Coleman/EspaceNetworks <chris001@users.noreply.github.com>
Reviewed-by: Chris Coleman/EspaceNetworks <chris001@users.noreply.github.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-06 00:46:40 +01:00
Nicolas Sebrecht cf3a6ecde2 gitignore: re-introduce *.css
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-05 19:35:13 +01:00
Nicolas Sebrecht 36dae1aefe REAMDE: remove links to a fork
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-05 19:31:51 +01:00
chris001 fc52034ea8 Autmomated testing using Travis and CodeCov.io!
Linux, Mac OSX. Outlook IMAP, Gmail. LOGIN, PLAIN, XOAUTH2. python 2.7, python 3.6!

Additional files required for Automated testing with Travis-CI and CodeCov.io!
Add gitter.im badge to README.

Signed-off-by: Chris Coleman/EspaceNetworks <chris001@users.noreply.github.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-05 19:31:51 +01:00
Nicolas Sebrecht 610308f97c offlineimap.py: fix shebang to python2
Expect more and more distributions to use python3 as default python binary.

This was previously done for bin/offlineimap but I forgot this file in the move,
as suggested by Chris.

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/530
Suggested-by: Chris Coleman <christocoleman@yahoo.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-04 04:28:30 +01:00
Nicolas Sebrecht 0f65469ce1 bin/offlineimap: fix shebang to env python2
The python executable will likely be set to python3 by default on more and more
distributions. Fix it to Python 2.

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/530
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-03 01:43:31 +01:00
Nicolas Sebrecht ea5093bc5e requirements: add gssapi as optional dependency
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-02 16:38:48 +01:00
Robbie Harwood 88724949fa Port to python-gssapi from pykerberos
python-gssapi has a visible, active upstream and a more pleasant
interface.  python-gssapi is present in most distributions, while
pykerberos is slated for removal from Fedora/RHEL/CentOS.

Github-ref: https://github.com/OfflineIMAP/offlineimap/pull/529
Tested-by: Robbie Harwood <rharwood@redhat.com>
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-03-02 16:24:36 +01:00
Musashi69 c8847ccff9 make UI output show local AND remote dirs involved
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/525
Signed-off-by: Friedemann Schorer friedemann@schorers.org
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-02-24 01:13:40 +01:00
Nicolas Sebrecht f732d6b2b6 maxsyncaccounts: improve documentation
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/520
Reviewed-by: Linus Arver <linusa@google.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-01-31 14:09:52 +01:00
Nicolas Sebrecht 67d35dbbcb v7.1.5
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Tested-by: Remi Locherer <remi.locherer@relo.ch>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2018-01-13 13:55:23 +01:00
Nicolas Sebrecht 0d6a9a44da maxage: don't consider negative UIDs when computing min UID
With new emails we could have negative UIDs in come use cases. Exclude these
from the list of UIDs. The negative UIDs lead to invalid SEARCH command:

 SEARCH command error: BAD ['Could not parse command']. Data: FMAO19 SEARCH (UID -4:*)

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/512
Tested-by: https://github.com/shubhamkrm
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-12-22 22:01:00 +01:00
Nicolas Sebrecht e802f5fbd5 folder: IMAP: improve search logging
Log when exception occured during search command, too.

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/512
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-12-22 15:13:51 +01:00
Evan Dandrea e8b31af4c2 Initial commit of snapcraft.yaml
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-12-20 08:38:18 +01:00
Nicolas Sebrecht a4b89f1034 github: remove the trick to download the PR
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-11-27 23:24:42 +01:00
John Ferlito 3c6b07b25f Add missing space to output string
It was previously printing "Upgrading LocalStatus cache from version 1to version 2 for XXX"

Signed-off-by: John Ferlito <johnf@inodes.org>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-11-26 18:05:10 +01:00
Nicolas Sebrecht 9805d3e7af no UIDPLUS: improve logging on failures
When there is not UIDPLUS we have to figure the UID by our means. When this
process fails, we don't know if the email was successfully uploaded. This patch
provides better logs to explain what happened.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-11-12 22:40:06 +01:00
Nicolas Sebrecht 681e271fc0 v7.1.4
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-29 15:23:29 +01:00
Nicolas Sebrecht 5836970d51 utf8foldernames: fix missing decode argument
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/502
Tested-by: https://github.com/pprw
Reviewed-by: Ilias Tsitsimpis <i.tsitsimpis@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-29 00:45:32 +02:00
Nicolas Sebrecht a79263bb31 utf8foldernames: support --delete-folder with UTF-8 folder name
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/505
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-29 00:44:39 +02:00
Thomas Merkel ee17e3dc3c Fix: if any tunnel (preauth_tunnel or transport_tunnel) the hostname should not be required
It's required to modify my change 1ce596d713
because a hostname shouldn't be needed if any tunnel is used. Both tunnels
provide a regular IMAP interface which is used by offlineimap.

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/503
Reported-by: Ilias Tsitsimpis <i.tsitsimpis@gmail.com>
Signed-off-by: Thomas Merkel <tm@core.io>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-27 20:03:17 +02:00
Nicolas Sebrecht 8c9cd5b7e3 contrib/release.py: email: don't write '<>' for message-id twice
Minor code refactoring.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-08 16:39:56 +02:00
Nicolas Sebrecht 832c704443 contrib/release.py: website: add all changes to the commit
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-08 16:28:44 +02:00
Nicolas Sebrecht 0d65762168 contrib/release/py: fix the 'v' character in commit message
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-08 16:20:59 +02:00
Nicolas Sebrecht e1a6feb2d5 v7.1.3
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Tested-by: Remi Locherer <remi.locherer@relo.ch>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-08 15:58:10 +02:00
Nicolas Sebrecht 392e64c3b3 upcoming.py: get header template from external file
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 22:33:57 +02:00
Nicolas Sebrecht a9514c2b8a upcoming.py: display a message with the filename once written
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:57:39 +02:00
Nicolas Sebrecht 6b64f87462 provide non-zero exit code & display configuration mismatch error at the end of the run
This behaviour is what users would expected most. The error message is about
utf8foldernames and decodefoldernames.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:24:12 +02:00
Nicolas Sebrecht 12de158ca2 Merge branch 'uliska/utf8foldernames' into next 2017-10-02 21:11:29 +02:00
Urs Liska f5198794e5 utf8: document new feature, deprecate old one
- Document the new utf8foldernames config option
- Deprecate the old decodefoldernames option
  Update its documentation, discussing the limitations.

Signed-off-by: Urs Liska <git@ursliska.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:09:43 +02:00
Urs Liska 8b398f3aa8 utf8: Safeguard against use of old *and* new option
The new 'utf8foldernames' will not work together with the existing
'decodefoldernames' option (which will be documented in the next
commit). Therefore this commit will check for this condition and
abort the synchronization of a misconfigured account before doing
any changes.
Other accounts are not affected.

Signed-off-by: Urs Liska <git@ursliska.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:09:43 +02:00
Urs Liska ef3299b7ce Remove some unnecessary whitespace (in existing code)
Addresses https://github.com/OfflineIMAP/offlineimap/pull/498#discussion_r141672756

Signed-off-by: Urs Liska <git@ursliska.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:09:43 +02:00
Urs Liska 36d726763d utf8: implement utf8foldernames option
If utf8foldernames is enabled on account level all folder names read
from the IMAP server will immediately be reencoded to UTF-8. Names
will be treated as UTF-8 as long as the IMAP server isn't contacted again,
for which they are reencoded to IMAP4-UTF-7.

This means that any further processing such as nametrans, folderfilter
etc. will act upon the UTF-8 names, which will have to be documented
carefully.

NOTE 1:
GMail repositories and folders inherit from the IMAP... classes, so I don't
know yet if these changes have ugly side-effects. But web research suggests
that GMail IMAP folders are equally encoded in UTF-7 so that should work
identically here and incorporate the same improvements.

NOTE 2:
I could not test the behaviour with idlefolders as I didn't get this option
to work at all, not even with the latest stable version.

NOTE 3:
I *did* test to sync an IMAP repository against another IMAP repository.

Signed-off-by: Urs Liska <git@ursliska.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:09:43 +02:00
Urs Liska 14d83dbf48 utf8: avoid dequoting IMAP folder names twice
Signed-off-by: Urs Liska <git@ursliska.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:09:43 +02:00
Urs Liska dca5f1846d utf8 (aside): Move code for decodefoldernames
While intending *not* to change the behaviour of the existing
decodefoldernames option this commit transparently improves
the coding.
So far this worked by overriding the folder's getvisiblename() method
which reads self.visiblename from and applies the conversion on
*every* invocation of getvisiblename().
This commit does the calculation once in the IMAPFolder's __init__.

Signed-off-by: Urs Liska <git@ursliska.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:09:43 +02:00
Urs Liska 24b3f27e5f utf8: Add utf-7/8 conversion functions
This commit adds two functions
- imaputil.IMAP_utf8()
- imaputil.utf8_IMAP()
as an interface to the new imap4_utf_7 codec.

Signed-off-by: Urs Liska <git@ursliska.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:09:43 +02:00
Urs Liska 032376efad utf8: Add imap4_utf_7 codec
Add code to reencode IMAP folder names to regular utf-8.
This starts an implementation that will add a new config option
`utf8foldernames` on account level which will fix #299 and on the
long run replace the current `decodefoldernames` option.

This commit introduces code to register an `imap4_utf_7` codec
on which two-way conversion methods will later be built.

Original code by
(https://www.blogger.com/profile/16648963337079496096),
taken from
http://piao-tech.blogspot.no/2010/03/get-offlineimap-working-with-non-ascii.html

In the comment
http://piao-tech.blogspot.com/2010/03/get-offlineimap-working-with-non-ascii.html?showComment=1316041409339#c669880170006851138
indicates that this code is expected to be incorporated into offlineIMAP and therefore the author implicitly agrees to put it under this license.

Signed-off-by: Urs Liska <git@ursliska.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 21:09:43 +02:00
Nicolas Sebrecht 31eee55672 accounts: error out when no folder to sync
Such error can be hit when the folderfilter is wrong and excludes all the
folders.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-10-02 02:43:47 +02:00
Thomas Merkel 1ce596d713 remotehost should not be required if transporttunnel is used
If the config option `transporttunnel` is used the option `remotehost` is not
needed, because the tunnel provide the IMAP connection.

Signed-off-by: Thomas Merkel <tm@core.io>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-09-21 18:50:57 +02:00
Nicolas Sebrecht 4b18ffd5e3 man: remove mention of experimental support for python 3
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-09-03 11:15:44 +02:00
Nicolas Sebrecht 172b4279ca man: mention the supported directions of the syncs
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-09-03 11:15:23 +02:00
Nicolas Sebrecht 19442d0010 folder: Gmail: fix copyright header
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-08-15 01:24:23 +02:00
Nicolas Sebrecht a5d9edc560 folder: Gmail: remove dead code
The support for the realdelete configuration option was removed because this
could lead to data loss.

See 51728ed to know more.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-08-15 01:19:56 +02:00
Nicolas Sebrecht 6079755b20 sqlite: provide better message error for insert
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/488
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-07-25 11:28:56 +02:00
Nicolas Sebrecht df7dd3e3b7 contrib/helpers: sort testers by name
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-07-12 23:18:08 +02:00
Nicolas Sebrecht ec0460a668 MAINTAINERS: Rainer is not currently active
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-07-12 19:56:12 +02:00
Nicolas Sebrecht e4bee74dad v7.1.2
Tested-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
Tested-by: benutzer193 <registerbn@gmail.com>
Tested-by: Remi Locherer <remi.locherer@relo.ch>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-07-10 16:58:50 +02:00
Nicolas Sebrecht ceb69c7033 contrib/release.py: consider positive feedbacks from testers
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-07-10 16:57:43 +02:00
Nicolas Sebrecht 7d1a540598 introduce the github CODEOWNERS file
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-07-09 17:40:19 +02:00
Nicolas Sebrecht 0903d5f33c increase imaplib2 requirement from v2.55 to v2.57
Suggested-by: Chris Coleman <christocoleman@yahoo.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-07-09 17:28:12 +02:00
Nicolas Sebrecht 4b103d6d3c contrib/tested-by.py: remove dead code
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-07-05 17:09:06 +02:00
Nicolas Sebrecht 212ed87509 imapserver: fix syntax error
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-30 16:42:47 +02:00
Nicolas Sebrecht fe2e3249ac offlineimap.conf: minor improvements
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-30 06:33:42 +02:00
Nicolas Sebrecht 481f44c784 offlineimap.conf: ssl must be disabled to force STARTTLS in some cases
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-30 05:19:43 +02:00
Nicolas Sebrecht 90244b7fa6 provide more details in error message when SSL fails on non-standard port
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-30 05:10:14 +02:00
Nicolas Sebrecht cf01d8e0eb systemd: README: fix the name of the timer unit
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-28 09:51:12 +02:00
Alvaro Pereyra ca21b2bd31 offlineimap.conf: minor typo fix
Fixed typo in the default config file, the word "negociation" is changed to
"negotiation" as the original author intended.

Originally-sent-by: Alvaro Pereyra <alvaronh@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-28 08:42:18 +02:00
Nicolas Sebrecht 67b4b0d9b4 upcoming: minor fixes
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-27 23:38:18 +02:00
Nicolas Sebrecht ba47138616 folder/IMAP: introduce dedicated parsing for davmail (not supporting UIDPLUS)
Some returned responses end with ')' rather than 'UID XXX)' as expected.

BTW, a better policy could be to request for the 'X-OfflineIMAP' header only
rather than fetching all the headers and looking for it manually.

Also:
- Strip the output when error occurs: we don't need the full response unless
  'imap' debug mode is enabled.
- Improve the comments.

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/479
Tested-by: https://github.com/secomi
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-27 20:32:58 +02:00
Nicolas Sebrecht ce83efc3c7 folder/IMAP: improve the warning when we can't parse the returned UID
Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/479
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-14 09:47:51 +02:00
Nicolas Sebrecht 05ff68c7e1 IMAP/IMAP: continue to sync if the local side does not return a valid UID on upload
There's no reason we should abort the full sync.

Github-ref: https://github.com/OfflineIMAP/offlineimap/issues/479
Tested-by: https://github.com/secomi
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-14 09:12:15 +02:00
Nicolas Sebrecht fc77de5af6 contrib: more release automation
- rewrite the release script from shell to python3
- refactoring of the upcoming script and introducing the helpers library
- introduce the tested-by.py script to manage the feedbacks from the testers

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-13 11:27:38 +02:00
Nicolas Sebrecht 47a7bdc883 advise singlethreadperfolder when offlineimap hangs
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-13 11:26:27 +02:00
Nicolas Sebrecht 6e917bf0e5 systemd: README: credit Hugo as contributor
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-01 18:46:59 +02:00
Hugo Osvaldo Barrera 71f5a7759b Respect systemd conventions for timers
systemd conventions specify that timers trigger services with the same
name (this can be overridden, of course).

We're currently providing:

* offlineimap-oneshot.service
* offlineimap.service
* offlineimap.timer

This is rather confusing, since the timer doesn't actually provide the
service of the same name (even though they're bundled together!!), but a
different one.

Signed-off-by: Hugo Osvaldo Barrera <hugo@barrera.io>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-01 18:40:42 +02:00
Hugo Osvaldo Barrera e7a3fd55ac Use a pre-existing target for systemd services
Providing mail.target is really confusing and poor UX:

* When a user enables a unit, it's not truly enabled until they ALSO
  enable mail.target. This is very counter-intuitive.
* `mail.target` provides no extra value in itself, nor is it anything
  "standard".
* Any user wanting this specific target can still continue using it just
  dropping in a `mail.target` file.

Signed-off-by: Hugo Osvaldo Barrera <hugo@barrera.io>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-01 18:40:42 +02:00
Hugo Osvaldo Barrera 87d92badfc Remove invalid systemd setting
oneshot units CANNOT have a `Restart=` setting, and including one
invalidates the service file.

Signed-off-by: Hugo Osvaldo Barrera <hugo@barrera.io>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-01 18:40:42 +02:00
Hugo Osvaldo Barrera a242a07582 Use basic logger (since systemd picks up stdout)
No need to overcomplicate things; systemd grabs all stdout output and
logs that.

Signed-off-by: Hugo Osvaldo Barrera <hugo@barrera.io>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-01 18:40:42 +02:00
Hugo Osvaldo Barrera 8bbfca9016 Explain how to override systemd values
Signed-off-by: Hugo Osvaldo Barrera <hugo@barrera.io>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-01 18:40:42 +02:00
benutzer193 efd2548f67 systemd: remove unused watchdog functionality
Add restart on failure and increase timeout to kill service.

Signed-off-by: benutzer193 <registerbn@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-01 15:32:41 +02:00
Nicolas Sebrecht f3b9963a48 systemd: add documentation entry in configuration files
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-06-01 11:13:17 +02:00
Nicolas Sebrecht f9960f9293 MAINTAINERS: Remi Locherer joins the team of testers
Remi is the maintainer of offlineimap for OpenBSD. Welcome!

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-05-29 01:30:06 +02:00
Nicolas Sebrecht 176fc19c55 gitignore genrated css file
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-05-28 18:27:38 +02:00
Nicolas Sebrecht 33889c04bb Changelog: fix syntax
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
2017-05-28 18:20:19 +02:00
68 changed files with 2871 additions and 535 deletions

14
.coveragerc Normal file
View File

@ -0,0 +1,14 @@
[run]
branch = True
source = offlineimap
[report]
exclude_lines =
if self.debug:
pragma: no cover
raise NotImplementedError
if __name__ == .__main__.:
ignore_errors = True
omit =
tests/*
test/*

19
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,19 @@
# This is a comment.
# Each line is a file pattern followed by one or more owners.
# These owners will be the default owners for everything in the repo.
# Unless a later match takes precedence, @global-owner1 and @global-owner2
# will be requested for review when someone opens a pull request.
#* @global-owner1 @global-owner2
# Order is important; the last matching pattern takes the most precedence.
# When someone opens a pull request that only modifies JS files, only @js-owner
# and not the global owner(s) will be requested for a review.
#*.js @js-owner
# You can also use email addresses if you prefer. They'll be used to look up
# users just like we do for commit author emails.
#docs/* docs@example.com
* @chris001

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
open_collective: offlineimap-organization

View File

@ -1,24 +1,14 @@
> This v1.0 template stands in `.github/`.
### Peer reviews
Trick to [fetch the pull
request](https://help.github.com/articles/checking-out-pull-requests-locally):
there is a (read-only) `refs/pull/` namespace.
``` bash
git fetch OFFICIAL_REPOSITORY_NAME pull/PULL_ID/head:LOCAL_BRANCH_NAME
```
> This v1.1 template stands in `.github/`.
### This PR
> Add character x `[x]`.
- [] I've read the [DCO](http://www.offlineimap.org/doc/dco.html).
- [] I've read the [Coding Guidelines](http://www.offlineimap.org/doc/CodingGuidelines.html)
- [] The relevant informations about the changes stands in the commit message, not here in the message of the pull request.
- [] Code changes follow the style of the files they change.
- [] Code is tested (provide details).
- [ ] I've read the [DCO](http://www.offlineimap.org/doc/dco.html).
- [ ] I've read the [Coding Guidelines](http://www.offlineimap.org/doc/CodingGuidelines.html)
- [ ] The relevant informations about the changes stands in the commit message, not here in the message of the pull request.
- [ ] Code changes follow the style of the files they change.
- [ ] Code is tested (provide details).
### References

5
.gitignore vendored
View File

@ -1,12 +1,13 @@
# Backups.
.*.swp
.*.swo
*.html
*~
# websites.
/website/
/wiki/
# Generated files.
*.html
*.css
/docs/dev-doc/
/build/
*.pyc
@ -14,3 +15,5 @@ offlineimap.1
offlineimapui.7
# Editors/IDEs
tags
# Generated conf files for Travis-CI tests.
oli-travis.conf

62
.travis.yml Normal file
View File

@ -0,0 +1,62 @@
language: python
python:
- '2.7'
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/975e807e0314c9fa189c
on_success: always # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never
os:
- linux
env:
global:
- secure: jehlvkFxQbkvr73A0z3HGNC/knZQPKcaXLf6nByGpNE0ZTQKF7Y5KkNfeTcw4st7L7KuRZ1S/1bFtpMXTaplE6G0OtIEC4//SM+z+Dnadn2OY6wHiaapwZmmqDC5qVvcXPdmz/wTRsdrJSGLb2l6kEb91vRGbCCfHHf6Z2cF71U=
- secure: kWdmWAFK4qrA73ONz1X8CJdHSER3bCBXjLfYHYEEMPCZep21bTITUXIfZBlSNN1888SQtYksuloRJmvj7xiY/hf/4lyWiqM3RgWQ+YptJMVOQX+Gara6vm4nGntKQwaXgZF2YHSh+NYwQm1VY6m0n1ye/vfOIJnYfgGTk5qAZYU=
- secure: MzytYRX6HxgBj6Q3efkACTtDed8ZYO+P6UJrDA9IDtvffi8fAFb+wkQtKJrdcvMXNOap6fPe4c0EVGjgL5hFxmgC8yAh5t2YK7OhstAtq0ptKFlOcU24/drrkqoq040sAM/4Lc0nQCvYpz7bH370jzZl69rpbQWttwQR0i1e3Gw=
- secure: RWvIOHSiv2kt6cfZR7MEueiAmC61bWMXAtgsC6gKq1u3BfENfqSBTA/heIy+nlu7AXK1b6hPMZDCHWK09Zz6Klkd9xZ1gkE/AARWseoo9UWgGjmfvqng1S6qpESeX2GnZGR9CuBXTPGhtbYLgtNlxAo+6uZLolz2utW2XNk3Z/Y=
- secure: spivQv+vSJhE+ttn/Z6tANaINqiMSaJSucRqtoXR7PtioVDTOTmmL01Ja6dXuo8Ua5iVFtpZPDzqVpntQLKtjcywSK2zWnC9qbZYDfENr1/yIvfbSRjGeseq0eoY+fFp67FGZV4mIasdC3LOB0lRGOyrsX787fNKVQ8ZH0CRz0o=
- secure: ZcY0TvTQnRCdoFkdbJPfDJJNx91tViwbpiOBkxNEa3u0RN48xkZkii35kNVBaEcVZHcT9C81ctHk4QX+plBkCsoj5GDf25scgcv1j9R9UoN/rIkmyTu1Znmc+3UQ2J+EnGLWVn5xJ7yT/l9NZeLfNbULQRjttwT4j2MBGxezgdM=
matrix:
- OUTLOOK_AUTH=PLAIN GMAIL_AUTH=XOAUTH2
- OUTLOOK_AUTH=LOGIN GMAIL_AUTH=XOAUTH2
matrix:
include:
- os: osx
language: generic
env: PYTHON=2.7.14 OUTLOOK_AUTH=PLAIN GMAIL_AUTH=XOAUTH2
- os: osx
language: generic
env: PYTHON=2.7.14 OUTLOOK_AUTH=LOGIN GMAIL_AUTH=XOAUTH2
allow_failures:
- os: osx
cache: pip
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew install openssl readline;
fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export OSX_BREW_SSLCACERTFILE="/usr/local/etc/openssl/cert.pem";
fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated pyenv || brew upgrade pyenv;
fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install pyenv-virtualenv; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pyenv install $PYTHON; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PYENV_VERSION="${PYTHON}"; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH="/Users/travis/.pyenv/shims:${PATH}";
fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pyenv virtualenv $PYTHON myvenv; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pyenv versions; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then python --version; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pyenv version; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then python --version; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then python -m pip install -U pip; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then python -m easy_install -U setuptools;
fi
install:
- pip install -r requirements.txt
- pip install -r tests/requirements.txt
- export PATH=$PATH:.
- python tests/create_conf_file.py
script:
- "./offlineimap.py -c ./oli-travis.conf"
- codecov

24
BACKERS.md Normal file
View File

@ -0,0 +1,24 @@
### Many thanks to our wonderful Sponsors! Your generous support helps us maintain OfflineIMAP.
<!-- PLATINUM sponsors: in the README.md and on the front page of website -->
<!-- https://shields.io/opencollective/tier/offlineimap-organization/12940 -->
<!-- GOLD sponsors: in the README.md and on the front page of website -->
<!-- https://shields.io/opencollective/tier/offlineimap-organization/12941 -->
# SILVER sponsors <!-- here and on sponsors page of website -->
<!-- https://shields.io/opencollective/tier/offlineimap-organization/12942 -->
[![silver-zeronet-logo]][silver-zeronet-link]
![Silver Sponsors][silver]
# BRONZE sponsors <!-- here only -->
<!-- https://shields.io/opencollective/tier/offlineimap-organization/12873 -->
![Bronze Sponsors][bronze]
<!-- links / references -->
[silver]: https://opencollective.com/offlineimap-organization/tiers/silver-sponsor.svg "Our Silver Sponsors"
[silver-zeronet-logo]: https://github.com/OfflineIMAP/offlineimap.github.io/raw/master/assets/img/sponsors/zeronet.svg
[silver-zeronet-link]: https://zeronet.co.nz/ "Zeronet - naturally fast internet"
[bronze]: https://opencollective.com/offlineimap-organization/tiers/bronze-sponsor.svg "Our Bronze Sponsors"

View File

@ -15,6 +15,643 @@ Note to mainainers:
* The following excerpt is only usefull when rendered in the website.
{:toc}
### OfflineIMAP v7.3.4 (2021-08-03)
#### Notes
The release is likely the last minor release. This project will be maintained
for minor bug fixes only.
In this version we've backported patches from the offlineimap3 project. There's
no new features. Users should try this fork. The official project is there and
is maintained by Rodolfo:
https://github.com/OfflineIMAP/offlineimap3
This release was tested by:
- Nicolas Sebrecht
#### Authors
- Nicolas Sebrecht (5)
- Rodolfo García Peñas (kix) (4)
- Martin Di Paola (2)
- Reto Schnyder (1)
#### Fixes
- folder: IMAP: fix issue when the response of searchforheaders is the same UID multiple times. [Nicolas Sebrecht]
- Fix hooks for IDLE sync. [Reto Schnyder]
- Changed wrong comparison equal. [Rodolfo García Peñas (kix)]
- Comparison error. [Rodolfo García Peñas (kix)]
- remove outdated links to travis. [Nicolas Sebrecht]
- ui init is lintian clean. [Rodolfo García Peñas (kix)]
- Require the minimal dependencies in python package. [Martin Di Paola]
#### Changes
- README: update regarding the offlineimap3 fork. [Nicolas Sebrecht]
- redirect the users to offlineimap3. [Nicolas Sebrecht]
- threadutil imports not used. [Rodolfo García Peñas (kix)]
- Move out pkg attributes from __init__.py. [Martin Di Paola]
### OfflineIMAP v7.3.3 (2020-04-11)
#### Notes
Here is a small release after nearly 4 months of slow moves. Still, the patches
are very usefull for some use cases. Thanks to the contributors!
This release was tested by:
- Nicolas Sebrecht
#### Authors
- Chris Coleman (1)
- Frank LENORMAND (1)
- Herton R. Krzesinski (1)
- martin f. krafft (1)
#### Features
- export env. variables when running account hooks. [Frank LENORMAND]
#### Fixes
- Fix stale gss api authentication security context. [Herton R. Krzesinski]
- Handle [ALREADYEXISTS] and Mailbox already exists!. [Chris Coleman]
#### Changes
- exec() the tunnel command. [martin f. krafft]
### OfflineIMAP v7.3.2 (2019-12-17)
#### Notes
This is a very small bug fix release.
This release was tested by:
- Nicolas Sebrecht
#### Authors
- Jaroslav Lichtblau (1)
- Nicolas Sebrecht (1)
#### Fixes
- Revert "fix check for unsupported sep character". [Nicolas Sebrecht]
- Fixing the Arch Linux name. [Jaroslav Lichtblau]
### OfflineIMAP v7.3.1 (2019-12-15)
#### Notes
This release was tested by:
- Nicolas Sebrecht
#### Authors
- Nicolas Sebrecht (5)
- Chris Coleman (1)
- Chris Coutinho (1)
- Jess (1)
#### Features
- Additional address for sysloghandler to handle mac. [Chris Coutinho]
- Added financial contributors to the README. [Jess]
- Introduce FUNDING.yml for opencollective. [Nicolas Sebrecht]
#### Fixes
- Fix check for unsupported sep character. [Nicolas Sebrecht]
- Contrib: use yaml.safe_load() instead of load(). [Nicolas Sebrecht]
- Ensure python2 in the release workflow. [Nicolas Sebrecht]
- Make docs: ensure py2 when running sphinx. [Nicolas Sebrecht]
#### Changes
- Update README.md. [Chris Coleman]
### OfflineIMAP v7.3.0 (2019-08-19)
#### Notes
Here comes a new release.
The upstream imaplib2 project is discontinued. That's why I've decided to take
over the maintenance of imaplib2 for offlineimap.
For the use of offlineimap I've applied the pending PRs from imaplib2. I have
applied another change sent to offlineimap (see OfflineIMAP/offlineimap#623).
However, there are 3 important limitations:
- I intend to maintain imaplib2 for offlineimap only. Everything will take place
in the offlineimap project. I'll neither package imaplib2 nor maintain any
"official" repository dedicated to imaplib2. If you want imaplib2 but not
offlineimap, you should extract the file `offlineimap/bundled_imaplib2.py` from
the offlineimap repository. Please, send your patches for imaplib2.py to the
offlineimap project directly.
- Starting from imaplib2 v2.100 (tagged: imaplib2-v2.100) I'm taking the patches
in the lazy mode. This means that I won't make deep checks/tests of the
changes. Hence, the quality and the stability might become a bit more
fluctuating. For more stability, you might like to only consider the imaplib2
versions released with the stable versions of offlineimap. Don't expect
changelogs dedicated to imaplib2. They will be part of the offlineimap
changelogs.
- All of this only applies to the py2 version of imaplib2. Sadly, offlineimap
has few chances to be ported on py3 so I don't aim to maintain the py3 version
of imaplib2.
In this release, offlineimap is learning Happy Eyeballs.
This release was tested by:
- Nicolas Sebrecht
#### Authors
- Nicolas Sebrecht (4)
- Ben Cotterell (1)
- Dario Maiocchi (1)
- Ilias Tsitsimpis (1)
- Julien Cristau (1)
- Olivier Mehani (1)
#### Features
- Implement Happy Eyeballs. [Olivier Mehani]
- imaplib2 v2.101. [Nicolas Sebrecht]
- imaplib2 v2.100. [Nicolas Sebrecht]
#### Changes
- Update readme to give an hint about Linux distros. [Dario Maiocchi]
- travis: remove python3.6. [Nicolas Sebrecht]
- README: add required dependency to rfc6555. [Nicolas Sebrecht]
#### imaplib2
- Do not use TIMEOUT_MAX for Condition.wait(). [Ilias Tsitsimpis]
- Use SSLContext if available so we send SNI. [Julien Cristau]
- Don't expect trailing space on command completion. [Ben Cotterell]
### OfflineIMAP v7.2.4 (2019-06-08)
#### Notes
This release introduces mkdir -p alike folder creation and fixes cygwin support
in Windows.
This release was tested by:
- Nicolas Sebrecht
#### Authors
- Nicolas Sebrecht (4)
- kimim (2)
- Jelmer Vernooij (1)
- Kyle Altendorf (1)
#### Features
- mkdir -p alike folder creation. [Kyle Altendorf]
#### Fixes
- Use portable locker to support cygwin in Windows. [kimim]
- contrib/release.py: don't break if sphinx-build is missing. [Nicolas Sebrecht]
#### Changes
- Update FSF postal address. [Jelmer Vernooij]
- repository/IMAP: update copyright header date. [Nicolas Sebrecht]
- PULL_REQUEST_TEMPLATE: add space between brackets to enable the edition in the gui. [Nicolas Sebrecht]
### OfflineIMAP v7.2.3 (2019-02-17)
#### Notes
A tiny release for one minor bug fix.
This release was tested by:
- Nicolas Sebrecht
#### Authors
- Mart Lubbers (1)
#### Fixes
- add checks in curses ui for small windows. [Mart Lubbers]
### OfflineIMAP v7.2.2 (2018-12-22)
#### Notes
With this release offlineimap can renew the token for OAUTH2. There is better
integration for ArchLinux and OSX. SSL configuration options are more
consistent.
There are bug fixes about maxage and GSSAPI.
The imaplib2 library looks discontinued. I wonder we'll have no other choice
than maintaining our own fork.
This release was tested by:
- Nicolas Sebrecht
#### Authors
- Nicolas Sebrecht (5)
- Philippe Loctaux (4)
- Benedikt Heine (2)
- Carnë Draug (2)
- Frode Aannevik (1)
- Robbie Harwood (1)
#### Features
- 2890dec Added ssl certfile on osx for openssl pacakge on homebrew. [Philippe Loctaux]
- 761e10e Add Archlinux to list of supported distros. [Philippe Loctaux]
#### Fixes
- 8692799 Fix expired oauth2_access_token. [Frode Aannevik]
- 096aa07 Handle empty token with complete GSSAPI context. [Robbie Harwood]
- a51064e maxage: always compute the remote cache list for min_uid. [Nicolas Sebrecht]
- 698ec64 offlineimap.conf: minor fixes. [Nicolas Sebrecht]
- af3a35a offlineimap/utilis/distro.py: indentation fix. [Philippe Loctaux]
- d3ba837 Fix typo in exception message. [Benedikt Heine]
- c9005cd Check if username is provided before trying plain authentication.. [Carnë Draug]
#### Changes
- 5f9474e Print username instead of accountname when asking for password. [Carnë Draug]
- ce9a198 Chain tls_level and ssl_version only if ssl is enabled. [Benedikt Heine]
- 6ef5937 docs/website-doc.sh: minor improvements in comments of versions.yml. [Nicolas Sebrecht]
- 4544bb1 contrib/release.py: minor UI improvement. [Nicolas Sebrecht]
- d930125 fix dates in copyright lines. [Nicolas Sebrecht]
### OfflineIMAP v7.2.1 (2018-06-16)
#### Notes
This new version introduces interesting features. The fingerprints now accepts
hashes in sha224, sha256, sha384 and sha512 to improve the compatibility with
IMAP servers.
There's a new script in ./contrib to store passwords with GPG.
The new GSSAPI library for kerberos gets a fix about authentication. Gmail
labels can now have parenthesis and the hostname can have path separators in
theirs names.
There's a lot of other minors improvements to make offlineimap better
(in the documentation, UI, configuration file and the code).
This release was tested by:
- Nicolas Sebrecht
Thanks to all the contributors. A lot of patches are first time contributions to
this project. This is very pleasant.
Special thanks to Ilias Tsitsimpis, Eygene Ryabinkin, Chris Coleman our long
time contributors involved in this release and Sebastian Spaeth who is still
paying for the domain name!
#### Authors
- Nicolas Sebrecht (9)
- velleto (6)
- Chris Coleman (1)
- Edgar HIPP (1)
- Eygene Ryabinkin (1)
- Lorenzo (1)
- Michael Billington (1)
- Robbie Harwood (1)
#### Features
- Script to store passwords in a file with GPG or using OSX's secure keychain. [Lorenzo]
- Added support for sha512, sha384, sha256, sha224 hashing algorithms to calculate server certificate fingerprints.. [velleto]
#### Fixes
- Pass username through in GSSAPI connections. [Robbie Harwood]
- Gmail: allow parenthesis in labels. [Nicolas Sebrecht]
- Correct typographical errors in offlineimap.conf. [Michael Billington]
- Create filenames with no path separators in them. [Eygene Ryabinkin]
#### Changes
- imapserver: fix copyright line. [Nicolas Sebrecht]
- Available hashes added to documentation.. [velleto]
- Documented the now allowed use of colon separated fingerprints with examples.. [velleto]
- Allow users to keep colons between each hex pair of server certificate fingerprint in configuration file.. [velleto]
- Removed uneccessary call of list() on zip() object.. [velleto]
- Changed the 'exception raised' message, to be more understandable.. [velleto]
- Make CTRL-C message more clear. [Edgar HIPP]
- setup: add long_description. [Nicolas Sebrecht]
- offlineimap.conf: fix comment about gssapi. [Nicolas Sebrecht]
- Add self to maintainers. Update email address.. [Chris Coleman]
- Makefile: targz: don't set the abbrev in the archive directory name. [Nicolas Sebrecht]
- contrib: learn to build website/_uploads. [Nicolas Sebrecht]
- docs/website-doc.sh: limit the number of exported versions in _data/announces.yml. [Nicolas Sebrecht]
- Makefile: targz: update files. [Nicolas Sebrecht]
- Makefile: clean: remove __pycache__ directories. [Nicolas Sebrecht]
### OfflineIMAP v7.2.0 (2018-04-07)
#### Notes
The biggest change with this release is the introduction of automated tests;
thanks to Chris from http://www.espacenetworks.com.
Robbie Hardwood from RedHat switched the GSSAPI dependency from pykerberos to
python-gssapi because it's more active and has more pleasant interface.
The shebang is fixed back to python2 to fix issues on some environments.
The UI was improved to show both the local and remote foldernames (usefull when
nametrans is enabled).
Thanks to all the contributors.
This release was tested by:
- Nicolas Sebrecht
- Remi Locherer
#### Authors
- Nicolas Sebrecht (9)
- Musashi69 (1)
- Robbie Harwood (1)
- chris001 (1)
#### Features
- Autmomated testing using Travis and CodeCov.io!. [chris001]
- README: travis: add badge for the next branch. [Nicolas Sebrecht]
- travis: add notification to gitter room OfflineIMAP/offlineimap. [Nicolas Sebrecht]
#### Fixes
- offlineimap.py: fix shebang to python2. [Nicolas Sebrecht]
- bin/offlineimap: fix shebang to env python2. [Nicolas Sebrecht]
#### Changes
- Port to python-gssapi from pykerberos. [Robbie Harwood]
- requirements: add gssapi as optional dependency. [Nicolas Sebrecht]
- make UI output show local AND remote dirs involved. [Musashi69]
- maxsyncaccounts: improve documentation. [Nicolas Sebrecht]
### OfflineIMAP v7.1.5 (2018-01-13)
#### Notes
This minor release fixes a bug about maxage failing to upload some emails. Also,
this introduces the snapcraft.yaml to package offlineimap with this packaging
system.
This release was tested by:
- Nicolas Sebrecht
- Remi Locherer
#### Authors
- Nicolas Sebrecht (4)
- Evan Dandrea (1)
- John Ferlito (1)
#### Features
- Initial commit of snapcraft.yaml. [Evan Dandrea]
#### Fixes
- maxage: don't consider negative UIDs when computing min UID. [Nicolas Sebrecht]
- Add missing space to output string. [John Ferlito]
#### Changes
- folder: IMAP: improve search logging. [Nicolas Sebrecht]
- no UIDPLUS: improve logging on failures. [Nicolas Sebrecht]
- github: remove the trick to download the PR. [Nicolas Sebrecht]
### OfflineIMAP v7.1.4 (2017-10-29)
#### Notes
Here is a bugfix release for v7.1.3. Two regressions got fixes and the
--delete-folder CLI option now expects an UTF-8 folder name when utf8foldernames
is enabled.
This release was tested by:
- Nicolas Sebrecht
#### Authors
- Nicolas Sebrecht (5)
- Thomas Merkel (1)
#### Fixes
- utf8foldernames: fix missing decode argument. [Nicolas Sebrecht]
- Fix: if any tunnel (preauth_tunnel or transport_tunnel) the hostname should not be required. [Thomas Merkel]
#### Changes
- utf8foldernames: support --delete-folder with UTF-8 folder name. [Nicolas Sebrecht]
- contrib/release.py improvements
### OfflineIMAP v7.1.3 (2017-10-08)
#### Notes
This release introduces a new experimental utf8foldernames configuration option.
We already had the "tricky" decodefoldernames which is now deprecated. The new
code is the correct implementation for this feature. The changes are neat and
rather small. All the users having decodefoldernames are requested to move to
utf8foldernames. This requires to update almost all the functions like
nametrans, folderfilter, etc, because they work on the UTF-8 encoding. See the
documentation for more. Thank you Urs Liska for this contribution!
In the long run, the idea is to:
1. Remove decodefoldernames in favour of utf8foldernames.
2. Promote utf8foldernames up to stable.
3. Turn utf8foldernames on by default.
Currently, folders with non-ASCII characters in their name have to be fully
re-downloaded. So, there's a bit more work to be done to have (3) and maybe (2).
Also, this release includes a fix about remotehost and transporttunnel that
would require some testing. Thanks Thomas Merkel!
There are documentation improvements, improved errors and minor code cleanups,
too.
This release was tested by:
- Nicolas Sebrecht
- Remi Locherer
#### Authors
- Nicolas Sebrecht (11)
- Urs Liska (8)
- Thomas Merkel (1)
#### Features
- utf8: implement utf8foldernames option. [Urs Liska]
- utf8: document new feature, deprecate old one. [Urs Liska]
#### Fixes
- remotehost should not be required if transporttunnel is used. [Thomas Merkel]
- accounts: error out when no folder to sync. [Nicolas Sebrecht]
- sqlite: provide better message error for insert. [Nicolas Sebrecht]
- folder: Gmail: fix copyright header. [Nicolas Sebrecht]
#### Changes
- man: remove mention of experimental support for python 3. [Nicolas Sebrecht]
- man: mention the supported directions of the syncs. [Nicolas Sebrecht]
- folder: Gmail: remove dead code. [Nicolas Sebrecht]
- upcoming.py: get header template from external file. [Nicolas Sebrecht]
- upcoming.py: display a message with the filename once written. [Nicolas Sebrecht]
- contrib/helpers: sort testers by name. [Nicolas Sebrecht]
- Remove some unnecessary whitespace (in existing code). [Urs Liska]
- MAINTAINERS: Rainer is not currently active. [Nicolas Sebrecht]
### OfflineIMAP v7.1.2 (2017-07-10)
#### Notes
This release introduces better Davmail support, better reliability when in
IMAP/IMAP mode, better output on some errors, and minor fixes. The provided
systemd files are improved.
The imaplib2 requirement is now v2.57.
Remi Locherer is joining our tester team. Great!
Starting with this release, the feedbacks from the testers are recorded in the
release notes, the git logs and the Changelog. Thanks to all of them for
improving the releases.
This release was tested by:
- benutzer193
- Nicolas Sebrecht
- Remi Locherer
#### Authors
- Nicolas Sebrecht (20)
- Hugo Osvaldo Barrera (5)
- Alvaro Pereyra (1)
- benutzer193 (1)
#### Features
- contrib/release.py: consider positive feedbacks from testers. [Nicolas Sebrecht]
- Introduce the github CODEOWNERS file. [Nicolas Sebrecht]
- IMAP/IMAP: continue to sync if the local side does not return a valid UID on upload. [Nicolas Sebrecht]
#### Fixes
- folder/IMAP: introduce dedicated parsing for davmail (not supporting UIDPLUS). [Nicolas Sebrecht]
- offlineimap.conf: minor typo fix. [Alvaro Pereyra]
- Respect systemd conventions for timers. [Hugo Osvaldo Barrera]
- Use a pre-existing target for systemd services. [Hugo Osvaldo Barrera]
- Remove invalid systemd setting. [Hugo Osvaldo Barrera]
- systemd: remove unused watchdog functionality. [benutzer193]
- gitignore generated css file. [Nicolas Sebrecht]
- Changelog: fix syntax. [Nicolas Sebrecht]
#### Changes
- Increase imaplib2 requirement from v2.55 to v2.57. [Nicolas Sebrecht]
- folder/IMAP: improve the warning when we can't parse the returned UID. [Nicolas Sebrecht]
- Provide more details in error message when SSL fails on non-standard port. [Nicolas Sebrecht]
- Use basic logger (since systemd picks up stdout). [Hugo Osvaldo Barrera]
- Explain how to override systemd values. [Hugo Osvaldo Barrera]
- systemd: add documentation entry in configuration files. [Nicolas Sebrecht]
- offlineimap.conf: ssl must be disabled to force STARTTLS in some cases. [Nicolas Sebrecht]
- Advise singlethreadperfolder when offlineimap hangs. [Nicolas Sebrecht]
- offlineimap.conf: minor improvements. [Nicolas Sebrecht]
- contrib: more release automation. [Nicolas Sebrecht]
- MAINTAINERS: Remi Locherer joins the team of testers. [Nicolas Sebrecht]
- systemd: README: credit Hugo as contributor. [Nicolas Sebrecht]
### OfflineIMAP v7.1.1 (2017-05-28)
#### Notes
@ -29,12 +666,12 @@ Furthermore, this release was tested by:
#### Authors
17 Nicolas Sebrecht
1 Chris Coleman
1 Ilias Tsitsimpis
1 Maximilian Kaul
1 benutzer193
1 Ævar Arnfjörð Bjarmason
- Nicolas Sebrecht (17)
- Chris Coleman (1)
- Ilias Tsitsimpis (1)
- Maximilian Kaul (1)
- benutzer193 (1)
- Ævar Arnfjörð Bjarmason (1)
#### Features

View File

@ -16,7 +16,7 @@ Contacts
- github: benutzer193
- Chris Coleman
- email: christocoleman at yahoo.com
- email: chris at espacenetworks.com
- github: chris001
- Darshit Shah
@ -51,10 +51,9 @@ Contacts
- github: nicolas33
- system: Linux
- Rainer M Krug
- email: Rainer at krugs.de
- github: rkrug
- system: OSX
- Remi Locherer
- email: remi.locherer at relo.ch
- system: OpenBSD maintainer
- Sebastian Spaeth
- email: sebastian at sspaeth.de
@ -75,7 +74,7 @@ Testers
- "J"
- Łukasz Żarnowiecki
- Nicolas Sebrecht
- Rainer M Krug
- Remi Locherer
Maintainers
@ -84,6 +83,7 @@ Maintainers
- Eygene Ryabinkin
- Sebastian Spaeth
- Nicolas Sebrecht
- Chris Coleman
Github

View File

@ -1,5 +1,4 @@
# Copyright (C) 2002 - 2006 John Goerzen
# <jgoerzen@complete.org>
# Copyright (C) 2002 - 2018 John Goerzen & contributors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,27 +14,29 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# Warning: VERSION, ABBREV and TARGZ are used in docs/build-uploads.sh.
VERSION=$(shell ./offlineimap.py --version)
ABBREV=$(shell git log --format='%h' HEAD~1..)
TARGZ=offlineimap-$(VERSION)-$(ABBREV)
TARGZ=offlineimap-v$(VERSION)-$(ABBREV)
SHELL=/bin/bash
RST2HTML=`type rst2html >/dev/null 2>&1 && echo rst2html || echo rst2html.py`
all: build
build:
python setup.py build
python2 setup.py build
@echo
@echo "Build process finished, run 'python setup.py install' to install" \
"or 'python setup.py --help' for more information".
@echo "Build process finished, run 'python2 setup.py install' to install" \
"or 'python2 setup.py --help' for more information".
clean:
-python setup.py clean --all
-python2 setup.py clean --all
-rm -f bin/offlineimapc 2>/dev/null
-find . -name '*.pyc' -exec rm -f {} \;
-find . -name '*.pygc' -exec rm -f {} \;
-find . -name '*.class' -exec rm -f {} \;
-find . -name '.cache*' -exec rm -f {} \;
-find . -type d -name '__pycache__' -exec rm -rf {} \;
-rm -f manpage.links manpage.refs 2>/dev/null
-find . -name auth -exec rm -vf {}/password {}/username \;
-$(MAKE) -C docs clean
@ -49,7 +50,7 @@ websitedoc:
targz: ../$(TARGZ)
../$(TARGZ):
cd .. && tar -zhcv --transform s,^offlineimap,$(TARGZ), -f $(TARGZ).tar.gz --exclude '*.pyc' offlineimap/{bin,Changelog.md,contrib,CONTRIBUTING.rst,COPYING,docs,MAINTAINERS.rst,MANIFEST.in,offlineimap,offlineimap.conf,offlineimap.conf.minimal,offlineimap.py,README.md,scripts,setup.py,test,TODO.rst}
cd .. && tar -zhcv --transform s,^offlineimap,offlineimap-v$(VERSION), -f $(TARGZ).tar.gz --exclude '.*.swp' --exclude '.*.swo' --exclude '*.pyc' --exclude '__pycache__' offlineimap/{bin,Changelog.md,Changelog.maint.md,contrib,CONTRIBUTING.rst,COPYING,docs,MAINTAINERS.rst,Makefile,MANIFEST.in,offlineimap,offlineimap.conf,offlineimap.conf.minimal,offlineimap.py,README.md,requirements.txt,scripts,setup.cfg,setup.py,snapcraft.yaml,test,tests,TODO.rst}
rpm: targz
cd .. && sudo rpmbuild -ta $(TARGZ)

View File

@ -1,13 +1,30 @@
Financial contributors: [![Financial Contributors on Open Collective](https://opencollective.com/offlineimap-organization/all/badge.svg?label=financial+contributors)](https://opencollective.com/offlineimap-organization)
[offlineimap]: http://github.com/OfflineIMAP/offlineimap
[offlineimap3]: http://github.com/OfflineIMAP/offlineimap3
[website]: http://www.offlineimap.org
[wiki]: http://github.com/OfflineIMAP/offlineimap/wiki
[blog]: http://www.offlineimap.org/posts.html
Links:
* Official github code repository: [offlineimap]
* Website: [website]
* Wiki: [wiki]
* Blog: [blog]
# OfflineIMAP
***"Get the emails where you need them."***
[Official offlineimap][offlineimap].
> IMPORTANT NOTE: This repository is for python2 only. The support for offlineimap3
> is happening in [Official offlineimap for python3][offlineimap3].
>
> I'll still lazily maintain this legacy offlineimap but users should definitely go with
> offlineimap3.
- [Official offlineimap for python3][offlineimap3].
- [Official offlineimap for python2][offlineimap].
## Description
@ -29,24 +46,41 @@ message without internet connection? No problem, the message is still there.
## Project status and future
> As one of the maintainer of OfflineIMAP, I'd like to put my efforts into
> [imapfw](http://github.com/OfflineIMAP/imapfw). **imapfw** is software in
> development that I intend to replace OfflineIMAP with in the long term.
>
> That's why I'm not going to continue OfflineIMAP development. I'll continue
> to maintain OfflineIMAP (fixing small bugs, reviewing patches and merging,
> and rolling out new releases), but that's all.
>
> While I keep tracking issues for OfflineIMAP, you should not expect future support.
>
> You won't be left at the side. OfflineIMAP's community is large enough so that
> you'll find people for most of your issues.
>
> Get news from the [blog][blog].
>
> Nicolas Sebrecht. ,-)
The [offlineimap][offlineimap] project was forked to
[offlineimap3][offlineimap3] to support python3. Contributions are welcome to
this project.
## Contributors
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/OfflineIMAP/offlineimap/graphs/contributors"><img src="https://opencollective.com/offlineimap-organization/contributors.svg?width=890&button=false" /></a>
### Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/offlineimap-organization/contribute)]
#### Individuals
<a href="https://opencollective.com/offlineimap-organization"><img src="https://opencollective.com/offlineimap-organization/individuals.svg?width=890"></a>
#### Organizations
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/offlineimap-organization/contribute)]
<a href="https://opencollective.com/offlineimap-organization/organization/0/website"><img src="https://opencollective.com/offlineimap-organization/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/offlineimap-organization/organization/1/website"><img src="https://opencollective.com/offlineimap-organization/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/offlineimap-organization/organization/2/website"><img src="https://opencollective.com/offlineimap-organization/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/offlineimap-organization/organization/3/website"><img src="https://opencollective.com/offlineimap-organization/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/offlineimap-organization/organization/4/website"><img src="https://opencollective.com/offlineimap-organization/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/offlineimap-organization/organization/5/website"><img src="https://opencollective.com/offlineimap-organization/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/offlineimap-organization/organization/6/website"><img src="https://opencollective.com/offlineimap-organization/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/offlineimap-organization/organization/7/website"><img src="https://opencollective.com/offlineimap-organization/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/offlineimap-organization/organization/8/website"><img src="https://opencollective.com/offlineimap-organization/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/offlineimap-organization/organization/9/website"><img src="https://opencollective.com/offlineimap-organization/organization/9/avatar.svg"></a>
## License
GNU General Public License v2.
@ -57,6 +91,11 @@ GNU General Public License v2.
You should first check if your distribution already packages OfflineIMAP for you.
Downloads releases as [tarball or zipball](https://github.com/OfflineIMAP/offlineimap/tags).
If you are running Linux Os, you can install offlineimap with:
- openSUSE `zypper in offlineimap`
- Arch Linux `pacman -S offlineimap`
- fedora `dnf install offlineimap`
## Feedbacks and contributions
@ -86,11 +125,15 @@ Bugs, issues and contributions can be requested to both the mailing list or the
## Requirements & dependencies
* Python v2.7+
* Python v3.4+ ***[STALLED] (experimental: [see known issues](https://github.com/OfflineIMAP/offlineimap/issues?q=is%3Aissue+is%3Aopen+label%3APy3))***
* Python v2.7.x
* six (required)
* rfc6555 (required)
* imaplib2 >= 2.57 (optional)
* gssapi (optional), for Kerberos authentication
* portalocker (optional), if you need to run offlineimap in Cygwin for Windows
* Python v3: See the [offlineimap3][offlineimap3] fork of
[offlineimap][offlineimap].
## Documentation

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python
#!/usr/bin/env python2
# Startup from system-wide installation
# Copyright (C) 2002 - 2009 John Goerzen
# <jgoerzen@complete.org>
# Copyright (C) 2002-2018 John Goerzen & contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by

327
contrib/helpers.py Normal file
View File

@ -0,0 +1,327 @@
"""
Put into Public Domain, by Nicolas Sebrecht.
Helpers for maintenance scripts.
"""
from os import chdir, makedirs, system, getcwd
from os.path import expanduser
import shlex
from subprocess import check_output, check_call, CalledProcessError
import yaml
FS_ENCODING = 'UTF-8'
ENCODING = 'UTF-8'
MAILING_LIST = 'offlineimap-project@lists.alioth.debian.org'
CACHEDIR = '.git/offlineimap-release'
EDITOR = 'vim'
MAILALIASES_FILE = expanduser('~/.mutt/mail_aliases')
TESTERS_FILE = "{}/testers.yml".format(CACHEDIR)
ME = "Nicolas Sebrecht <nicolas.s-dev@laposte.net>"
def run(cmd):
return check_output(cmd, timeout=5).rstrip()
def goTo(path):
try:
chdir(path)
return True
except FileNotFoundError:
print("Could not find the '{}' directory in '{}'...".format(
path, getcwd())
)
return False
class Author(object):
def __init__(self, name, count, email):
self.name = name
self.count = count
self.email = email
def getName(self):
return self.name
def getCount(self):
return self.count
def getEmail(self):
return self.email
class Git(object):
@staticmethod
def getShortlog(ref):
shortlog = ""
cmd = shlex.split("git shortlog --no-merges -n v{}..".format(ref))
output = run(cmd).decode(ENCODING)
for line in output.split("\n"):
if len(line) > 0:
if line[0] != " ":
line = " {}\n".format(line)
else:
line = " {}\n".format(line.lstrip())
else:
line = "\n"
shortlog += line
return shortlog
@staticmethod
def add(files):
cmd = shlex.split("git add -- {}".format(files))
return run(cmd).decode(ENCODING)
@staticmethod
def commit(msg):
cmd = shlex.split("git commit -s -m '{}'".format(msg))
return run(cmd).decode(ENCODING)
@staticmethod
def tag(version):
cmd = shlex.split("git tag -a 'v{}' -m 'v{}'".format(version, version))
return run(cmd).decode(ENCODING)
@staticmethod
def stash(msg):
cmd = shlex.split("git stash create '{}'".format(msg))
return run(cmd).decode(ENCODING)
@staticmethod
def mergeFF(ref):
cmd = shlex.split("git merge --ff '{}'".format(ref))
return run(cmd).decode(ENCODING)
@staticmethod
def getDiffstat(ref):
cmd = shlex.split("git diff --stat v{}..".format(ref))
return run(cmd).decode(ENCODING)
@staticmethod
def isClean():
try:
check_call(shlex.split("git diff --quiet"))
check_call(shlex.split("git diff --cached --quiet"))
except CalledProcessError:
return False
return True
@staticmethod
def buildMessageId():
cmd = shlex.split(
"git log HEAD~1.. --oneline --pretty='%H.%t.upcoming.%ce'")
return run(cmd).decode(ENCODING)
@staticmethod
def resetKeep(ref):
return run(shlex.split("git reset --keep {}".format(ref)))
@staticmethod
def getRef(ref):
return run(shlex.split("git rev-parse {}".format(ref))).rstrip()
@staticmethod
def rmTag(tag):
return run(shlex.split("git tag -d {}".format(tag)))
@staticmethod
def checkout(ref, create=False):
if create:
create = "-b"
else:
create = ""
cmd = shlex.split("git checkout {} {}".format(create, ref))
run(cmd)
head = shlex.split("git rev-parse HEAD")
revparseRef = shlex.split("git rev-parse {}".format(ref))
if run(head) != run(revparseRef):
raise Exception("checkout to '{}' did not work".format(ref))
@staticmethod
def makeCacheDir():
try:
makedirs(CACHEDIR)
except FileExistsError:
pass
@staticmethod
def getLocalUser():
cmd = shlex.split("git config --get user.name")
name = run(cmd).decode(ENCODING)
cmd = shlex.split("git config --get user.email")
email = run(cmd).decode(ENCODING)
return name, email
@staticmethod
def buildDate():
cmd = shlex.split("git log HEAD~1.. --oneline --pretty='%cD'")
return run(cmd).decode(ENCODING)
@staticmethod
def getAuthorsList(sinceRef):
authors = []
cmd = shlex.split("git shortlog --no-merges -sne v{}..".format(sinceRef))
output = run(cmd).decode(ENCODING)
for line in output.split("\n"):
count, full = line.strip().split("\t")
full = full.split(' ')
name = ' '.join(full[:-1])
email = full[-1]
authors.append(Author(name, count, email))
return authors
@staticmethod
def getCommitsList(sinceRef):
cmd = shlex.split(
"git log --no-merges --format='- %h %s. [%aN]' v{}..".format(sinceRef)
)
return run(cmd).decode(ENCODING)
@staticmethod
def chdirToRepositoryTopLevel():
cmd = shlex.split("git rev-parse --show-toplevel")
topLevel = run(cmd)
chdir(topLevel)
class OfflineimapInfo(object):
def getVersion(self):
cmd = shlex.split("./offlineimap.py --version")
return run(cmd).rstrip().decode(FS_ENCODING)
def editVersion(self):
return system("{} ./offlineimap/version.py".format(EDITOR))
class User(object):
"""Interact with the user."""
@staticmethod
def request(msg, prompt='--> '):
print(msg)
return input(prompt)
@staticmethod
def pause(msg=False):
return User.request(msg, prompt="Press Enter to continue..")
@staticmethod
def yesNo(msg, defaultToYes=False, prompt='--> '):
endMsg = " [y/N]: No"
if defaultToYes:
endMsg = " [Y/n]: Yes"
msg += endMsg
answer = User.request(msg, prompt).lower()
if answer in ['y', 'yes']:
return True
if defaultToYes is not False and answer not in ['n', 'no']:
return True
return False
class Tester(object):
def __init__(self, name, email, feedback):
self.name = name
self.email = email
self.feedback = feedback
def __str__(self):
return "{} {}".format(self.name, self.email)
def getName(self):
return self.name
def getEmail(self):
return self.email
def getFeedback(self):
return self.feedback
def positiveFeedback(self):
return self.feedback is True
def setFeedback(self, feedback):
assert feedback in [True, False, None]
self.feedback = feedback
def switchFeedback(self):
self.feedback = not self.feedback
class Testers(object):
def __init__(self):
self.testers = None
self._read()
def _read(self):
self.testers = []
with open(TESTERS_FILE, 'r') as fd:
testers = yaml.safe_load(fd)
for tester in testers:
name = tester['name']
email = tester['email']
feedback = tester['feedback']
self.testers.append(Tester(name, email, feedback))
self.testers.sort(key=lambda x: x.getName().lower())
@staticmethod
def listTestersInTeam():
"""Returns a list of emails extracted from my mailaliases file."""
cmd = shlex.split("grep offlineimap-testers {}".format(MAILALIASES_FILE))
output = run(cmd).decode(ENCODING)
emails = output.lstrip("alias offlineimap-testers ").split(', ')
return emails
def add(self, name, email, feedback=None):
self.testers.append(Tester(name, email, feedback))
def remove(self, tester):
self.testers.remove(tester)
def get(self):
return self.testers
def getList(self):
testersList = ""
for tester in self.testers:
testersList += "- {}\n".format(tester.getName())
return testersList
def getListOk(self):
testersOk = []
for tester in self.testers:
if tester.positiveFeedback():
testersOk.append(tester)
return testersOk
def reset(self):
for tester in self.testers:
tester.setFeedback(None)
def write(self):
testers = []
for tester in self.testers:
testers.append({
'name': tester.getName(),
'email': tester.getEmail(),
'feedback': tester.getFeedback(),
})
with open(TESTERS_FILE, 'w') as fd:
fd.write(yaml.dump(testers))

477
contrib/release.py Executable file
View File

@ -0,0 +1,477 @@
#!/usr/bin/python3
"""
Put into Public Domain, by Nicolas Sebrecht.
Make a new release.
"""
#TODO: announce: cc list on announce includes all testers
#TODO: announce: remove empty sections
#TODO: websitedoc up
#TODO: website branch not including all changes!
from os import system, path, rename
from datetime import datetime
from subprocess import check_call
import shlex
import time
from email import utils
from helpers import (
MAILING_LIST, CACHEDIR, EDITOR, Git, OfflineimapInfo, Testers, User, run, goTo
)
__VERSION__ = "0.2"
SPHINXBUILD = 'sphinx-build'
DOCSDIR = 'docs'
CHANGELOG_MAGIC = '{:toc}'
WEBSITE_LATEST = "website/_data/latest.yml"
CHANGELOG_EXCERPT = "{}/changelog.excerpt.md".format(CACHEDIR)
CHANGELOG_EXCERPT_OLD = "{}.old".format(CHANGELOG_EXCERPT)
CHANGELOG = "Changelog.md"
ANNOUNCE_FILE = "{}/announce.txt".format(CACHEDIR)
WEBSITE_LATEST_SKEL = """# DO NOT EDIT MANUALLY: it is generated by the release script.
stable: v{stable}
"""
CHANGELOG_SKEL = """
### OfflineIMAP v{version} ({date})
#### Notes
This release was tested by:
{testersList}
#### Authors
{authorsList}
#### Features
#### Fixes
#### Changes
{commitsList}
"""
END_MESSAGE = """
Release is ready!
Make your checks and push the changes for both offlineimap and the website.
Announce template stands in '{announce}'.
Command samples to do manually:
- git push <remote> master next {new_version}
- python2 setup.py sdist && twine upload dist/* && rm -rf dist MANIFEST
- cd website
- git checkout master
- git merge {website_branch}
- git push <remote> master
- cd ..
- git send-email {announce}
...and write a Twitter message.
Have fun! ,-)
"""
class State(object):
def __init__(self):
self.master = None
self.next = None
self.website = None
self.tag = None
def setTag(self, tag):
self.tag = tag
def save(self):
self.master = Git.getRef('master')
self.next = Git.getRef('next')
def saveWebsite(self):
Git.chdirToRepositoryTopLevel()
goTo('website')
self.website = Git.getRef('master')
goTo('..')
def restore(self):
Git.chdirToRepositoryTopLevel()
try:
Git.checkout('-f')
except:
pass
# Git.checkout('master')
# Git.resetKeep(self.master)
# Git.checkout('next')
# Git.resetKeep(self.next)
if self.tag is not None:
Git.rmTag(self.tag)
if self.website is not None:
if goTo('website'):
Git.checkout(self.website)
goTo('..')
class Changelog(object):
def __init__(self):
self.shouldUsePrevious = False
def edit(self):
return system("{} {}".format(EDITOR, CHANGELOG_EXCERPT))
def update(self):
# Insert excerpt to CHANGELOG.
system("sed -i -e '/{}/ r {}' '{}'".format(
CHANGELOG_MAGIC, CHANGELOG_EXCERPT, CHANGELOG
)
)
# Remove trailing whitespaces.
system("sed -i -r -e 's, +$,,' '{}'".format(CHANGELOG))
def savePrevious(self):
rename(CHANGELOG_EXCERPT, CHANGELOG_EXCERPT_OLD)
def isPrevious(self):
if path.isfile(CHANGELOG_EXCERPT_OLD):
return True
return False
def showPrevious(self):
output = run(shlex.split("cat '{}'".format(CHANGELOG_EXCERPT_OLD)))
for line in output.splitlines():
print(line.decode('utf-8')) # Weird to have to decode bytes here.
def usePrevious(self):
rename(CHANGELOG_EXCERPT_OLD, CHANGELOG_EXCERPT)
self.shouldUsePrevious = True
def usingPrevious(self):
return self.shouldUsePrevious
def writeExcerpt(self, version, date,
testersList, authorsList, commitsList):
with open(CHANGELOG_EXCERPT, 'w+') as fd:
fd.write(CHANGELOG_SKEL.format(
version=version,
date=date,
testersList=testersList,
authorsList=authorsList,
commitsList=commitsList,
))
def getSectionsContent(self):
dict_Content = {}
with open(CHANGELOG_EXCERPT, 'r') as fd:
currentSection = None
for line in fd:
line = line.rstrip()
if line == "#### Notes":
currentSection = 'Notes'
dict_Content['Notes'] = ""
continue # Don't keep this title.
elif line == "#### Authors":
currentSection = 'Authors'
dict_Content['Authors'] = ""
continue # Don't keep this title.
elif line == "#### Features":
currentSection = 'Features'
dict_Content['Features'] = ""
continue # Don't keep this title.
elif line == "#### Fixes":
currentSection = 'Fixes'
dict_Content['Fixes'] = ""
continue # Don't keep this title.
elif line == "#### Changes":
currentSection = 'Changes'
dict_Content['Changes'] = ""
continue # Don't keep this title.
elif line == "-- ":
break # Stop extraction.
if currentSection is not None:
dict_Content[currentSection] += "{}\n".format(line)
#TODO: cleanup empty sections.
return dict_Content
class Announce(object):
def __init__(self, version):
self.fd = open(ANNOUNCE_FILE, 'w')
self.version = version
def setHeaders(self, messageId, date):
self.fd.write("Message-Id: {}\n".format(messageId))
self.fd.write("Date: {}\n".format(date))
self.fd.write("From: Nicolas Sebrecht <nicolas.s-dev@laposte.net>\n")
self.fd.write("To: {}\n".format(MAILING_LIST))
self.fd.write(
"Subject: [ANNOUNCE] OfflineIMAP v{} released\n".format(self.version))
self.fd.write("\n")
self.fd.write("""
OfflineIMAP v{version} is out.
Downloads:
http://github.com/OfflineIMAP/offlineimap/archive/v{version}.tar.gz
http://github.com/OfflineIMAP/offlineimap/archive/v{version}.zip
Pip:
wget "https://raw.githubusercontent.com/OfflineIMAP/offlineimap/v{version}/requirements.txt" -O requirements.txt
pip install -r ./requirements.txt --user git+https://github.com/OfflineIMAP/offlineimap.git@v{version}
""".format(version=self.version)
)
def setContent(self, dict_Content):
self.fd.write("\n")
for section in ['Notes', 'Authors', 'Features', 'Fixes', 'Changes']:
if section in dict_Content:
if section != "Notes":
self.fd.write("# {}\n".format(section))
self.fd.write(dict_Content[section])
self.fd.write("\n")
# Signature.
self.fd.write("-- \n")
self.fd.write("Nicolas Sebrecht\n")
def close(self):
self.fd.close()
class Website(object):
def updateUploads(self):
req = ("add new archive to uploads/ on the website? "
"(warning: checksums will change if it already exists)")
if User.yesNo(req, defaultToYes=True) is False:
return False
if check_call(shlex.split("./docs/build-uploads.sh")) != 0:
return exit(5)
return True
def updateAPI(self):
req = "update API of the website? (requires {})".format(SPHINXBUILD)
if User.yesNo(req, defaultToYes=True) is False:
return False
try:
if check_call(shlex.split("{} --version".format(SPHINXBUILD))) != 0:
raise RuntimeError("{} not found".format(SPHINXBUILD))
except:
print("""
Oops! you don't have {} installed?"
Cannot update the webite documentation..."
You should install it and manually run:"
$ cd {}"
$ make websitedoc"
Then, commit and push changes of the website.""".format(SPHINXBUILD, DOCSDIR))
User.pause()
return False
Git.chdirToRepositoryTopLevel()
if not goTo('website'):
User.pause()
return False
if not Git.isClean:
print("There is WIP in the website repository: stashing")
Git.stash('WIP during offlineimap API import')
goTo('..')
return True
def buildLatest(self, version):
Git.chdirToRepositoryTopLevel()
with open(WEBSITE_LATEST, 'w') as fd:
fd.write(WEBSITE_LATEST_SKEL.format(stable=version))
def exportDocs(self):
if not goTo(DOCSDIR):
User.pause()
return
if check_call(shlex.split("make websitedoc")) != 0:
print("error while calling 'make websitedoc'")
exit(3)
def createImportBranch(self, version):
branchName = "import-v{}".format(version)
Git.chdirToRepositoryTopLevel()
if not goTo("website"):
User.pause()
return
Git.checkout(branchName, create=True)
Git.add('.')
Git.commit("update for offlineimap v{}".format(version))
User.pause(
"website: branch '{}' is ready for a merge in master!".format(
branchName
)
)
goTo('..')
return branchName
class Release(object):
def __init__(self):
self.state = State()
self.offlineimapInfo = OfflineimapInfo()
self.testers = Testers()
self.changelog = Changelog()
self.websiteBranch = "NO_BRANCH_NAME_ERROR"
def getVersion(self):
return self.offlineimapInfo.getVersion()
def prepare(self):
if not Git.isClean():
print("The git repository is not clean; aborting")
exit(1)
Git.makeCacheDir()
Git.checkout('next')
def requestVersion(self, currentVersion):
User.request("going to make a new release after {}".format(currentVersion))
def updateVersion(self):
self.offlineimapInfo.editVersion()
def checkVersions(self, current, new):
if new == current:
print("version was not changed; stopping.")
exit(1)
def updateChangelog(self):
if self.changelog.isPrevious():
self.changelog.showPrevious()
if User.yesNo("A previous Changelog excerpt was found. Use it?"):
self.changelog.usePrevious()
if not self.changelog.usingPrevious():
date = datetime.now().strftime('%Y-%m-%d')
testersList = ""
testers = self.testers.getListOk()
authorsList = ""
authors = Git.getAuthorsList(currentVersion)
for tester in testers:
testersList += "- {}\n".format(tester.getName())
for author in authors:
authorsList += "- {} ({})\n".format(
author.getName(), author.getCount()
)
commitsList = Git.getCommitsList(currentVersion)
date = datetime.now().strftime('%Y-%m-%d')
self.changelog.writeExcerpt(
newVersion, date, testersList, authorsList, commitsList
)
self.changelog.edit()
self.changelog.update()
def writeAnnounce(self):
announce = Announce(newVersion)
messageId = utils.make_msgid('release.py', 'laposte.net')
nowtuple = datetime.now().timetuple()
nowtimestamp = time.mktime(nowtuple)
date = utils.formatdate(nowtimestamp)
announce.setHeaders(messageId, date)
announce.setContent(self.changelog.getSectionsContent())
announce.close()
def make(self):
Git.add('offlineimap/version.py')
Git.add('Changelog.md')
commitMsg = "v{}\n".format(newVersion)
for tester in self.testers.getListOk():
commitMsg = "{}\nTested-by: {} {}".format(
commitMsg, tester.getName(), tester.getEmail()
)
Git.commit(commitMsg)
self.state.setTag(newVersion)
Git.tag(newVersion)
Git.checkout('master')
Git.mergeFF('next')
Git.checkout('next')
def updateWebsite(self, newVersion):
self.state.saveWebsite()
website = Website()
website.buildLatest(newVersion)
res_upload = website.updateUploads()
res_api = website.updateAPI()
if res_api:
res_export = website.exportDocs()
if True in [res_upload, res_api, res_export]:
self.websiteBranch = website.createImportBranch(newVersion)
def getWebsiteBranch(self):
return self.websiteBranch
def after(self):
for protectedRun in [self.testers.reset, self.changelog.savePrevious]:
try:
protectedRun()
except Exception as e:
print(e)
def restore(self):
self.state.restore()
if __name__ == '__main__':
release = Release()
Git.chdirToRepositoryTopLevel()
try:
release.prepare()
currentVersion = release.getVersion()
release.requestVersion(currentVersion)
release.updateVersion()
newVersion = release.getVersion()
release.checkVersions(currentVersion, newVersion)
release.updateChangelog()
release.writeAnnounce()
User.pause()
release.make()
release.updateWebsite(newVersion)
release.after()
websiteBranch = release.getWebsiteBranch()
print(END_MESSAGE.format(
announce=ANNOUNCE_FILE,
new_version=newVersion,
website_branch=websiteBranch)
)
except Exception as e:
release.restore()
raise

View File

@ -0,0 +1,37 @@
# gpg-offlineimap
Python bindings for offlineimap to use gpg instead of storing cleartext passwords
Author: Lorenzo G.
[GitHub](https://github.com/lorenzog/gpg-offlineimap)
## Quickstart
Requirements: a working GPG set-up. Ideally with gpg-agent. Should work
out of the box on most modern Linux desktop environments.
1. Enable IMAP in gmail (if you have two factor authentication, you
need to create an app-specific password)
2. Create a directory `~/Mail`
3. In `~/Mail`, create a password file `passwords-gmail.txt`. Format:
`account@gmail.com password`. Look at the example file in this
directory.
4. **ENCRYPT** the file: `gpg -e passwords-gmail.txt`. It should create
a file `passwords-gmail.txt.gpg`. Check you can decrypt it: `gpg -d
passwords-gmail.txt.gpg`: it will ask you for your GPG password and
show it to you.
5. Use the file `offlineimaprc.sample` as a sample for your own
`.offlineimaprc`; edit it by following the comments. Minimal items
to configure: the `remoteuser` field and the `pythonfile` parameter
pointing at the `offlineimap.py` file in this directory.
6. Run it: `offlineimap`. It should ask you for your GPG passphrase to
decrypt the password file.
7. If all works well, delete the cleartext password file.

View File

@ -0,0 +1,99 @@
#!/usr/bin/python
# Originally taken from: http://stevelosh.com/blog/2012/10/the-homely-mutt/
# by Steve Losh
# Modified by Lorenzo Grespan on Jan, 2014
import re
import subprocess
from sys import argv
import logging
from os.path import expanduser
import unittest
import os
import sys
logging.basicConfig(level=logging.INFO)
DEFAULT_PASSWORDS_FILE = os.path.join(
os.path.expanduser('~/Mail'),
'passwords.gpg')
def get_keychain_pass(account=None, server=None):
'''Mac OSX keychain password extraction'''
params = {
'security': '/usr/bin/security',
'command': 'find-internet-password',
'account': account,
'server': server,
'keychain': expanduser('~') + '/Library/Keychains/login.keychain',
}
command = ("%(security)s -v %(command)s"
" -g -a %(account)s -s %(server)s %(keychain)s" % params)
output = subprocess.check_output(
command, shell=True, stderr=subprocess.STDOUT)
outtext = [l for l in output.splitlines()
if l.startswith('password: ')][0]
return find_password(outtext)
def find_password(text):
'''Helper method for osx password extraction'''
# a non-capturing group
r = re.match(r'password: (?:0x[A-F0-9]+ )?"(.*)"', text)
if r:
return r.group(1)
else:
logging.warn("Not found")
return None
def get_gpg_pass(account, storage):
'''GPG method'''
command = ("gpg", "-d", storage)
# get attention
print '\a' # BEL
output = subprocess.check_output(command)
# p = subprocess.Popen(command, stdout=subprocess.PIPE)
# output, err = p.communicate()
for line in output.split('\n'):
r = re.match(r'{} ([a-zA-Z0-9]+)'.format(account), line)
if r:
return r.group(1)
return None
def get_pass(account=None, server=None, passwd_file=None):
'''Main method'''
if not passwd_file:
storage = DEFAULT_PASSWORDS_FILE
else:
storage = os.path.join(
os.path.expanduser('~/Mail'),
passwd_file)
if os.path.exists('/usr/bin/security'):
return get_keychain_pass(account, server)
if os.path.exists(storage):
logging.info("Using {}".format(storage))
return get_gpg_pass(account, storage)
else:
logging.warn("No password file found")
sys.exit(1)
return None
# test with: python -m unittest <this module name>
# really basic tests.. nothing to see. move along
class Tester(unittest.TestCase):
def testMatchSimple(self):
text = 'password: "exampleonetimepass "'
self.assertTrue(find_password(text))
def testMatchComplex(self):
text = r'password: 0x74676D62646D736B646970766C66696B0A "anotherexamplepass\012"'
self.assertTrue(find_password(text))
if __name__ == "__main__":
print get_pass(argv[1], argv[2], argv[3])

View File

@ -0,0 +1,63 @@
[general]
# GPG quirks, leave unconfigured
ui = ttyui
# you can use any name as long as it matches the 'account1, 'account2' in the rest
# of the file
accounts = account1, account2
# this is where the `gpg-pw.py` file is on disk
pythonfile=~/where/is/the/file/gpg-pw.py
fsync = False
# you can call this any way you like
[Account account1]
localrepository = account1-local
remoterepository = account1-remote
# no need to touch this
status_backend = sqlite
[Account account2]
localrepository = account2-local
remoterepository = account2-remote
status_backend = sqlite
# thi sis a gmail account
[Repository account1-local]
type = Maildir
# create with maildirmake or by hand by creating cur, new, tmp
localfolders = ~/Mail/Mailboxes/account1
# standard Gmail stuff
nametrans = lambda folder: { 'drafts': '[Gmail]/Drafts',
'sent': '[Gmail]/Sent mail',
'flagged': '[Gmail]/Starred',
'trash': '[Gmail]/Trash',
'archive': '[Gmail]/All Mail'
}.get(folder, folder)
[Repository account1-remote]
maxconnections = 1
type = Gmail
ssl=yes
# for osx, you might need to download the certs by hand
#sslcacertfile=~/Mail/certs.pem
#sslcacertfile=~/Mail/imap.gmail.com.pem
# sslcacertfile=/etc/ssl/cert.pem
# or use Linux's standard certs
sslcacertfile=/etc/ssl/certs/ca-certificates.crt
# your account
remoteuser = account1@gmail.com
remotepasseval = get_pass(account="account1@gmail.com", server="imap.gmail.com", passwd_file="passwords-gmail.txt.gpg")
realdelete = no
createfolders = no
nametrans = lambda folder: {'[Gmail]/Drafts': 'drafts',
'[Gmail]/Sent Mail': 'sent',
'[Gmail]/Starred': 'star',
'[Gmail]/Trash': 'trash',
'[Gmail]/All Mail': 'archive',
}.get(folder, folder)
folderfilter = lambda folder: folder not in ['[Gmail]/Trash',
'[Gmail]/Spam',
]
[Repository account2-remote]
# copy the stanza above, change the 'account' parameter of get_pass, etc.

View File

@ -0,0 +1,2 @@
account1@gmail.com password1
account2@gmail.com password2

View File

@ -3,8 +3,8 @@ layout: page
title: Integrating OfflineIMAP into systemd
author: Ben Boeckel
date: 2015-03-22
contributors: Abdo Roig-Maranges
updated: 2015-03-25
contributors: Abdo Roig-Maranges, benutzer193, Hugo Osvaldo Barrera
updated: 2017-06-01
---
<!-- This file is copied to the website by script. -->
@ -18,32 +18,10 @@ into `/etc/systemd/user` or `${XDG_DATA_HOME}/systemd/user` followed by
These files are meant to be triggered either manually using `systemctl --user
start offlineimap.service` or by enabling the timer unit using `systemctl --user
enable offlineimap.timer`. Additionally, specific accounts may be triggered by
using `offlineimap@myaccount.timer` or `offlineimap@myaccount.service`.
These unit files are installed as being enabled via a `mail.target` unit which
is intended to be a catch-all for mail-related unit files. A simple
`mail.target` file is also provided.
## Signals
Systemd supports a watchdog (via the WatchdogSec service file option) which
will send the program a SIGABRT when the timer expires.
Offlineimap handles it in the same manner as SIGUSR2, so that the current
synchronisation is completed before the program exits safely.
This makes offlineimap more flexible and robust for persistent setups that make
use of holdconnectionopen and autorefresh options.
For example, it may be useful in assisting with the occasional situation where
offlineimap may not return successfully after a suspend and resume.
To make use of this, users could add the following to the [Service] section of
their corresponding systemd offlineimap-oneshot service file (restart every 5 minutes):
``` conf
Restart=on-watchdog
WatchdogSec=300
```
enable offlineimap-oneshot.timer`. Additionally, specific accounts may be
triggered by using `offlineimap@myaccount.timer` or
`offlineimap-oneshot@myaccount.service`.
If the defaults provided by these units doesn't suit your setup, any of the
values may be overridden by using `systemctl --user edit offlineimap.service`.
This'll prevent having to copy-and-edit the original file.

View File

@ -1,5 +0,0 @@
[Unit]
Description=Mail Target
[Install]
WantedBy=default.target

View File

@ -1,13 +1,12 @@
[Unit]
Description=Offlineimap Service (oneshot)
Documentation=man:offlineimap(1)
[Service]
Type=oneshot
ExecStart=/usr/bin/offlineimap -o -u syslog
# Give 12 seconds for offlineimap to gracefully stop before hard killing it.
TimeoutStopSec=12
#Restart=on-watchdog
#WatchdogSec=300
ExecStart=/usr/bin/offlineimap -o -u basic
# Give 120 seconds for offlineimap to gracefully stop before hard killing it:
TimeoutStopSec=120
[Install]
WantedBy=mail.target

View File

@ -4,7 +4,6 @@ Description=Offlineimap Query Timer
[Timer]
OnBootSec=1m
OnUnitInactiveSec=15m
Unit=offlineimap-oneshot.service
[Install]
WantedBy=mail.target
WantedBy=default.target

View File

@ -1,11 +1,12 @@
[Unit]
Description=Offlineimap Service for account %i (oneshot)
Documentation=man:offlineimap(1)
[Service]
Type=oneshot
ExecStart=/usr/bin/offlineimap -o -a %i -u syslog
#Restart=on-watchdog
#WatchdogSec=300
ExecStart=/usr/bin/offlineimap -o -a %i -u basic
# Give 120 seconds for offlineimap to gracefully stop before hard killing it.
TimeoutStopSec=120
[Install]
WantedBy=mail.target
WantedBy=default.target

View File

@ -4,7 +4,6 @@ Description=Offlineimap Query Timer for account %i
[Timer]
OnBootSec=1m
OnUnitInactiveSec=15m
Unit=offlineimap-oneshot@%i.service
[Install]
WantedBy=mail.target
WantedBy=default.target

View File

@ -1,8 +1,11 @@
[Unit]
Description=Offlineimap Service
Documentation=man:offlineimap(1)
[Service]
ExecStart=/usr/bin/offlineimap -u syslog
ExecStart=/usr/bin/offlineimap -u basic
Restart=on-failure
RestartSec=60
[Install]
WantedBy=mail.target
WantedBy=default.target

View File

@ -1,8 +1,11 @@
[Unit]
Description=Offlineimap Service for account %i
Documentation=man:offlineimap(1)
[Service]
ExecStart=/usr/bin/offlineimap -a %i -u syslog
ExecStart=/usr/bin/offlineimap -a %i -u basic
Restart=on-failure
RestartSec=60
[Install]
WantedBy=mail.target
WantedBy=default.target

142
contrib/tested-by.py Executable file
View File

@ -0,0 +1,142 @@
#!/usr/bin/python3
"""
Put into Public Domain, by Nicolas Sebrecht.
Manage the feedbacks of the testers for the release notes.
"""
from os import system
import argparse
from helpers import CACHEDIR, EDITOR, Testers, User, Git
class App(object):
def __init__(self):
self.args = None
self.testers = Testers()
self.feedbacks = None
def _getTestersByFeedback(self):
if self.feedbacks is not None:
return self.feedbacks
feedbackOk = []
feedbackNo = []
for tester in self.testers.get():
if tester.getFeedback() is True:
feedbackOk.append(tester)
else:
feedbackNo.append(tester)
for array in [feedbackOk, feedbackNo]:
array.sort(key=lambda t: t.getName())
self.feedbacks = feedbackOk + feedbackNo
def parseArgs(self):
parser = argparse.ArgumentParser(description='Manage the feedbacks.')
parser.add_argument('--add', '-a', dest='add_tester',
help='Add tester')
parser.add_argument('--delete', '-d', dest='delete_tester',
type=int,
help='Delete tester NUMBER')
parser.add_argument('--list', '-l', dest='list_all_testers',
action='store_true',
help='List the testers')
parser.add_argument('--switchFeedback', '-s', dest='switch_feedback',
action='store_true',
help='Switch the feedback of a tester')
self.args = parser.parse_args()
def run(self):
if self.args.list_all_testers is True:
self.listTesters()
if self.args.switch_feedback is True:
self.switchFeedback()
elif self.args.add_tester:
self.addTester(self.args.add_tester)
elif type(self.args.delete_tester) == int:
self.deleteTester(self.args.delete_tester)
def addTester(self, strTester):
try:
splitted = strTester.split('<')
name = splitted[0].strip()
email = "<{}".format(splitted[1]).strip()
except Exception as e:
print(e)
print("expected format is: 'Firstname Lastname <email>'")
exit(2)
self.testers.add(name, email)
self.testers.write()
def deleteTester(self, number):
self.listTesters()
removed = self.feedbacks.pop(number)
self.testers.remove(removed)
print("New list:")
self.feedbacks = None
self.listTesters()
print("Removed: {}".format(removed))
ans = User.request("Save on disk? (s/Q)").lower()
if ans in ['s']:
self.testers.write()
def listTesters(self):
self._getTestersByFeedback()
count = 0
for tester in self.feedbacks:
feedback = "ok"
if tester.getFeedback() is not True:
feedback = "no"
print("{:02d} - {} {}: {}".format(
count, tester.getName(), tester.getEmail(), feedback
)
)
count += 1
def switchFeedback(self):
self._getTestersByFeedback()
msg = "Switch tester: [<number>/s/q]"
self.listTesters()
number = User.request(msg)
while number.lower() not in ['s', 'save', 'q', 'quit']:
if number == '':
continue
try:
number = int(number)
self.feedbacks[number].switchFeedback()
except (ValueError, IndexError) as e:
print(e)
exit(1)
finally:
self.listTesters()
number = User.request(msg)
if number in ['s', 'save']:
self.testers.write()
self.listTesters()
def reset(self):
self.testers.reset()
self.testers.write()
#def updateMailaliases(self):
if __name__ == '__main__':
Git.chdirToRepositoryTopLevel()
app = App()
app.parseArgs()
app.run()

View File

@ -8,178 +8,56 @@ Produce the "upcoming release" notes.
"""
from os import chdir, system
from os.path import expanduser
import shlex
from subprocess import check_output
from os import system
from helpers import (
MAILING_LIST, CACHEDIR, EDITOR, Testers, Git, OfflineimapInfo, User
)
__VERSION__ = "0.1"
FS_ENCODING = 'UTF-8'
ENCODING = 'UTF-8'
MAILING_LIST = 'offlineimap-project@lists.alioth.debian.org'
CACHEDIR = '.git/offlineimap-release'
UPCOMING_FILE = "{}/upcoming.txt".format(CACHEDIR)
EDITOR = 'vim'
MAILALIASES_FILE = expanduser('~/.mutt/mail_aliases')
UPCOMING_HEADER = "{}/upcoming-header.txt".format(CACHEDIR)
UPCOMING_HEADER = """
Message-Id: <{messageId}>
Date: {date}
From: {name} <{email}>
To: {mailinglist}
Cc: {ccList}
Subject: [ANNOUNCE] upcoming offlineimap v{expectedVersion}
# Notes
I think it's time for a new release.
I aim to make the new release in one week, approximately. If you'd like more
time, please let me know. ,-)
# Authors
"""
def run(cmd):
return check_output(cmd, timeout=5).rstrip()
def getTesters():
"""Returns a list of emails extracted from my mailaliases file."""
cmd = shlex.split("grep offlineimap-testers {}".format(MAILALIASES_FILE))
output = run(cmd).decode(ENCODING)
emails = output.lstrip("alias offlineimap-testers ").split(', ')
return emails
class Author(object):
def __init__(self, name, count, email):
self.name = name
self.count = count
self.email = email
def getName(self):
return self.name
def getCount(self):
return self.count
def getEmail(self):
return self.email
class Git(object):
@staticmethod
def getShortlog(ref):
shortlog = ""
cmd = shlex.split("git shortlog --no-merges -n v{}..".format(ref))
output = run(cmd).decode(ENCODING)
for line in output.split("\n"):
if len(line) > 0:
if line[0] != " ":
line = " {}\n".format(line)
else:
line = " {}\n".format(line.lstrip())
else:
line = "\n"
shortlog += line
return shortlog
@staticmethod
def getDiffstat(ref):
cmd = shlex.split("git diff --stat v{}..".format(ref))
return run(cmd).decode(ENCODING)
@staticmethod
def buildMessageId():
cmd = shlex.split(
"git log HEAD~1.. --oneline --pretty='%H.%t.upcoming.%ce'")
return run(cmd).decode(ENCODING)
@staticmethod
def getLocalUser():
cmd = shlex.split("git config --get user.name")
name = run(cmd).decode(ENCODING)
cmd = shlex.split("git config --get user.email")
email = run(cmd).decode(ENCODING)
return name, email
@staticmethod
def buildDate():
cmd = shlex.split("git log HEAD~1.. --oneline --pretty='%cD'")
return run(cmd).decode(ENCODING)
@staticmethod
def getAuthors(ref):
authors = []
cmd = shlex.split("git shortlog --no-merges -sne v{}..".format(ref))
output = run(cmd).decode(ENCODING)
for line in output.split("\n"):
count, full = line.strip().split("\t")
full = full.split(' ')
name = ' '.join(full[:-1])
email = full[-1]
authors.append(Author(name, count, email))
return authors
@staticmethod
def chdirToRepositoryTopLevel():
cmd = shlex.split("git rev-parse --show-toplevel")
topLevel = run(cmd)
chdir(topLevel)
class OfflineimapInfo(object):
def __init__(self):
self.version = None
def getCurrentVersion(self):
if self.version is None:
cmd = shlex.split("./offlineimap.py --version")
self.version = run(cmd).rstrip().decode(FS_ENCODING)
return self.version
class User(object):
"""Interact with the user."""
prompt = '-> '
@staticmethod
def request(msg):
print(msg)
return input(User.prompt)
# Header is like:
#
#Message-Id: <{messageId}>
#Date: {date}
#From: {name} <{email}>
#To: {mailinglist}
#Cc: {ccList}
#Subject: [ANNOUNCE] upcoming offlineimap v{expectedVersion}
#
## Notes
#
#I think it's time for a new release.
#
#I aim to make the new release in one week, approximately. If you'd like more
#time, please let me know. ,-)
#
#Please, send me a mail to confirm it works for you. This will be written in the
#release notes and the git logs.
#
#
## Authors
#
if __name__ == '__main__':
offlineimapInfo = OfflineimapInfo()
print("Will read headers from {}".format(UPCOMING_HEADER))
Git.chdirToRepositoryTopLevel()
oVersion = offlineimapInfo.getCurrentVersion()
ccList = getTesters()
authors = Git.getAuthors(oVersion)
oVersion = offlineimapInfo.getVersion()
ccList = Testers.listTestersInTeam()
authors = Git.getAuthorsList(oVersion)
for author in authors:
email = author.getEmail()
if email not in ccList:
ccList.append(email)
with open(UPCOMING_FILE, 'w') as upcoming:
with open(UPCOMING_FILE, 'w') as upcoming, \
open(UPCOMING_HEADER, 'r') as fd_header:
header = {}
header['messageId'] = Git.buildMessageId()
@ -189,7 +67,7 @@ if __name__ == '__main__':
header['expectedVersion'] = User.request("Expected new version?")
header['ccList'] = ", ".join(ccList)
upcoming.write(UPCOMING_HEADER.format(**header).lstrip())
upcoming.write(fd_header.read().format(**header).lstrip())
upcoming.write(Git.getShortlog(oVersion))
upcoming.write("\n\n# Diffstat\n\n")
@ -197,3 +75,4 @@ if __name__ == '__main__':
upcoming.write("\n\n\n-- \n{}\n".format(Git.getLocalUser()[0]))
system("{} {}".format(EDITOR, UPCOMING_FILE))
print("{} written".format(UPCOMING_FILE))

View File

@ -7,7 +7,7 @@ HTML_TARGETS = $(patsubst %.rst,%.html,$(SOURCES))
RM = rm
RST2HTML=`type rst2html >/dev/null 2>&1 && echo rst2html || echo rst2html.py`
RST2MAN=`type rst2man >/dev/null 2>&1 && echo rst2man || echo rst2man.py`
SPHINXBUILD = sphinx-build
SPHINXBUILD = python2 -msphinx
docs: man api

34
docs/build-uploads.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/sh
#
# vim: expandtab ts=2 :
WEBSITE_UPLOADS='./website/_uploads'
while true
do
test -d .git && break
cd ..
done
set -e
echo "make clean"
make clean >/dev/null
echo "make targz"
make targz >/dev/null
# Defined in the root Makefile.
version="$(./offlineimap.py --version)"
abbrev="$(git log --format='%h' HEAD~1..)"
targz="../offlineimap-v${version}-${abbrev}.tar.gz"
filename="offlineimap-v${version}.tar.gz"
mv -v "$targz" "${WEBSITE_UPLOADS}/${filename}"
cd "$WEBSITE_UPLOADS"
for digest in sha1 sha256 sha512
do
target="${filename}.${digest}"
echo "Adding digest ${WEBSITE_UPLOADS}/${target}"
"${digest}sum" "$filename" > "$target"
done

View File

@ -102,6 +102,11 @@ folder behind offlineimap's back, causing them to get assigned a new UID, or
when offlineimap first syncs a pre-existing Maildir. In the latter case, it
could appear as if a noticeable and random subset of old messages are synced.
* Offlineimap hangs.
+
When having unexpected hangs it's advised to set `singlethreadperfolder' to
'yes', especially when in IMAP/IMAP mode (no maildir).
* Passwords in netrc.
+
Offlineimap doesn't know how to retrieve passwords when more than one account is

View File

@ -4,7 +4,7 @@ offlineimap(1)
NAME
----
offlineimap - Synchronize mailboxes and Maildirs
offlineimap - Synchronize mailboxes and Maildirs both ways or one either way.
SYNOPSIS
--------
@ -18,7 +18,7 @@ Synchronize the accounts configured in the configuration file via IMAP. Each
account has two sides. One of the side must be an IMAP server. The other side
can either be a Maildir or another IMAP server.
Python 3 is supported while still EXPERIMENTAL.
Works with Python 2.
OPTIONS
@ -160,9 +160,10 @@ blinkenlights, machineui.
--delete-folder::
Delete a folder on the remote repository.
+
Only one account must be specified/configured for this feature to work. The
folder name must be provided in IMAP encoding with the remote separators (likely
'/'). E.g.: "Remote/folder/name".
Only one account must be specified/configured for this feature to work or you
must provide one account with -a. The folder name must be provided with the
remote separators (likely '/') in UTF-8 if utf8foldernames is enabled or in IMAP
otherwise. E.g.: "Remote/folder/name".
--migrate-fmd5-using-nametrans::

View File

@ -11,6 +11,7 @@ DOCBASE="${WEBSITE}/_doc"
DESTBASE="${DOCBASE}/versions"
VERSIONS_YML="${WEBSITE}/_data/versions.yml"
ANNOUNCES_YML="${WEBSITE}/_data/announces.yml"
ANNOUNCES_YML_LIMIT=31
ANNOUNCES_YML_TMP="${ANNOUNCES_YML}.tmp"
CONTRIB_YML="${WEBSITE}/_data/contribs.yml"
CONTRIB="${DOCBASE}/contrib"
@ -54,9 +55,13 @@ function api () {
# This let know the website about the available APIs documentations.
echo "Building Jekyll data: $VERSIONS_YML"
# Erase previous content.
echo "$HEADER" > "$VERSIONS_YML"
echo "# However, it's correct to /remove/ old API docs here."
echo "# While at it, don't forget to adjust the _doc/versions directory."
echo > "$VERSIONS_YML" <<EOF
$HEADER
# Used to publish the APIs.
#
# However, it's correct to _remove_ old API docs here. In this case, don't
# forget to adjust the _doc/versions directory too.
EOF
for version in $(ls "$DESTBASE" -1 | sort -nr)
do
echo "- $version"
@ -122,7 +127,7 @@ function releases () {
d="$(parse_releases_get_date "$title")"
echo "- {date: '${d}', version: '${v}', link: 'Changelog.maint.html#${link}'}"
done | tee -a "$ANNOUNCES_YML_TMP"
sort -nr "$ANNOUNCES_YML_TMP" >> "$ANNOUNCES_YML"
sort -nr "$ANNOUNCES_YML_TMP" | head -n $ANNOUNCES_YML_LIMIT >> "$ANNOUNCES_YML"
rm -f "$ANNOUNCES_YML_TMP"
}

View File

@ -77,14 +77,16 @@ accounts = Test
# This option stands in the [general] section.
#
# Offlineimap can synchronize more than one account at a time. If you want to
# enable this feature, set the below value to something greater than 1. To
# force it to synchronize only one account at a time, set it to 1.
# Offlineimap can synchronize more than one account at a time. There are two
# ways to sync accounts concurrently:
#
# NOTE: if you are using autorefresh and have more than one account, you must
# set this number to be >= to the number of accounts you have; since any given
# sync run never "finishes" due to a timer, you will never sync your additional
# accounts if this is 1.
# 1. By running one offlineimap instance for each account (with the -a CLI
# option). This is the recommended way. In this case, keep the following option
# to 1.
#
# 2. By allowing offlineimap to sync more than one account for an instance (not
# recommended). In this case, set the maxsyncaccounts option to a value greater
# than 1.
#
#maxsyncaccounts = 1
@ -273,8 +275,13 @@ remoterepository = RemoteExample
# your mail periodically. If you want that, specify how frequently to do that
# (in minutes) here. Fractional minutes (ie, 3.25) is allowed.
#
# If you want more than one account concurrently synced in this mode, don't
# forget to set the maxsyncaccounts option accordingly.
# If you want more than one account concurrently synced in one instance of
# offlineimap (not recommended), don't forget to increase the maxsyncaccounts
# option accordingly.
#
# NOTE: If you run systemd it's recommended to not enable this option and use
# the systemd timer instead (see the ./contrib/systemd/ directory in the
# repository).
#
#autorefresh = 5
@ -358,7 +365,7 @@ remoterepository = RemoteExample
# This option stands in the [Account Test] section.
#
# Maildir file format uses colon (:) separator between uniq name and info.
# Unfortunatelly colon is not allowed character in windows file name. If you
# Unfortunately colon is not allowed character in windows file name. If you
# enable maildir-windows-compatible option, Offlineimap will be able to store
# messages on windows drive, but you will probably loose compatibility with
# other programs working with the maildir.
@ -413,7 +420,7 @@ remoterepository = RemoteExample
#
# This knob is respected only by IMAP-based accounts. Value of labelsheader
# for GMail-based accounts is automatically added to this list, you don't
# need to specify it explicitely.
# need to specify it explicitly.
#
# Use ASCII characters only.
#
@ -422,7 +429,7 @@ remoterepository = RemoteExample
# This option stands in the [Account Test] section.
#
# Use proxy connection for this account. Usefull to bypass the GFW in China.
# Use proxy connection for this account. Useful to bypass the GFW in China.
# To specify a proxy connection, join proxy type, host and port with colons.
# Available proxy types are SOCKS5, SOCKS4, HTTP.
# You also need to install PySocks through pip.
@ -432,6 +439,55 @@ remoterepository = RemoteExample
#proxy = SOCKS5:IP:9999
# EXPERIMENTAL: This option stands in the [Account Test] section.
#
# IMAP defines an encoding for non-ASCII ("international") characters, and most
# IMAP servers store folder names in this encoding. Note that the IMAP 4rev1
# specification (RFC 3501) allows both UTF-8 and modified UTF-7 folder names
# so it is *possible* that an IMAP server already uses UTF-8 encoded folder
# names. But usually Folders that are shown as, say, "Gäste" will be represented
# as "G&AOQ-ste", and by default will be synchronized like this by offlineIMAP.
#
# This option converts IMAP folder names from IMAP4-UTF-7 to UTF-8 and back
# in order to have nicely readable UTF-8 folder names in the local copy.
#
# WARNING: with this option enabled:
# - compatibility with any other version is NOT GUARANTEED (including newer);
# - existing set-ups will probably break.
# - no support is provided.
#
# IMPORTANT: READ THIS SECTION if you intend to enable this feature for an
# EXISTING ACCOUNT that has already been synchronized!
# Enabling UTF-8 encoded folder names will change many things on the local
# repository of an account, so you really have to create a new local repository
# and review the configuration. The least that would happen otherwise is a
# duplication of all folders containing non-ASCII characters.
# But also the following functionality may change, so the configuration in the
# remote repository configuration has to be reviewed/updated:
# - decodefoldernames
# This option is replaced by utf8foldernames and must be removed
# If both utf8foldernames and decodefoldernames are enabled the synchronization
# for the given account is aborted before doing any changes.
# - nametrans
# With utf8foldernames enabled any nametrans function will operate on the
# UTF-8 encoded folder names, while even with decodefoldernames enabled they
# operate on the original IMAP4-UTF-7 encoded names.
# - folderfilter
# Folder filters still work on the untranslated names before applying a
# nametrans function, but still this operates on the UTF-8 encoded names.
# - folderincludes
# With utf8foldernames enabled this function expects UTF-8 encoded folder
# names.
# - foldersort
# With utf8foldernames enabled the folder names passed to the sorting routine
# will be the UTF encoded names.
# - idlefolders
# With utf8foldernames enabled folders passed to this function are expected to
# be UTF-8 encoded.
#
#utf8foldernames = no
# TESTING: This option stands in the [Account Test] section.
#
# Use authproxy connection for this account. Useful to bypass the GFW in China.
@ -533,20 +589,19 @@ localfolders = ~/Test
# This option stands in the [Repository LocalExample] section.
#
# This option is similar to "utime_from_header" and could be use as a
# This option is similar to "utime_from_header" and could be used as a
# complementary feature to keep track of a message date. This option only
# makes sense for the Maildir type.
#
# By default each message is stored in a file which prefix is the fetch
# timestamp and an order rank such as "1446590057_0". In a multithreading
# environment message are fetched in a random order, then you can't trust
# the file name to sort your boxes.
# the filename to sort your boxes.
#
# If set to "yes" the file name prefix if build on the message "Date" header
# If set to "yes" the filename prefix is built from the message "Date" header
# (which should be present) or the "Received-date" if "Date" is not
# found. If neither "Received-date" nor "Date" is found, the current system
# date is used. Now you can quickly sort your messages using their file
# names.
# date is used. Now you can quickly sort your messages using their filenames.
#
# Used in combination with "utime_from_header" all your message would be in
# order with the correct mtime attribute.
@ -643,13 +698,14 @@ remotehost = examplehost
# This option stands in the [Repository RemoteExample] section.
#
# Whether or not to use STARTTLS. STARTTLS allows to upgrade a plain connection
# to TLS or SSL after negociation with the server. While a server might pretend
# to TLS or SSL after negotiation with the server. While a server might pretend
# to support STARTTLS, the communication might not be properly established or
# the secure tunnel might be broken in some way. In this case you might want to
# disable STARTTLS. Unless you hit issues with STARTTLS, you are strongly
# encouraged to keep STARTTLS enabled.
#
# STARTTLS can be used even if the 'ssl' option is disabled.
# STARTTLS can be used even if the 'ssl' option is disabled. If you want to
# _force_ STARTTLS, you might need to disable 'ssl'.
#
# Default is yes.
#
@ -730,10 +786,14 @@ remotehost = examplehost
#
# In Windows, Microsoft uses the term "thumbprint" instead of "fingerprint".
#
# Fingerprints must be in hexadecimal form without leading '0x':
# 40 hex digits like bbfe29cf97acb204591edbafe0aa8c8f914287c9.
# Supported fingerprint hashes are sha512, sha384, sha256, sha224 and sha1.
# Fingerprints must be in hexadecimal form without leading '0x', and may have
# the separating colons. This is non case-sensitive.
# Examples:
# sha1 "bbfe29cf97acb204591edbafe0aa8c8f914287c9".
# sha1 with colons "BB:FE:29:CF:97:AC:B2:04:59:1E:DB:AF:E0:AA:8C:8F:91:42:87:C9"
#
#cert_fingerprint = <SHA1_of_server_certificate_here>[, <another_SHA1>]
#cert_fingerprint = <SHAn_of_server_certificate_here>[, <another_SHAm>]
# This option stands in the [Repository RemoteExample] section.
@ -783,8 +843,8 @@ remotehost = examplehost
# - ssl3 (less desirable than tls1)
# - ssl23 (can fallback up to ssl3)
#
# When tls_level is not set to tls_compat, the ssl_version configuration option
# must be explicitly set.
# When tls_level is not set to tls_compat and ssl is still enabled,
# the ssl_version configuration option must be explicitly set.
#
#tls_level = tls_compat
@ -844,7 +904,7 @@ remoteuser = username
# For Gmail (and maybe others), XOAUTH2 requires ssl. This means that STARTTLS
# won't work and that Offlineimap will perform certificate validation. IOW, the
# following configuration is used:
# - sslcacertfile: MUST BE correclty configured
# - sslcacertfile: MUST BE correctly configured
# - ssl = yes (optional, will be used anyway)
# - starttls = no (optional, will be tried but won't work anyway)
#
@ -940,14 +1000,14 @@ remoteuser = username
#
# 4. With a preauth tunnel. With this method, you invoke an external
# program that is guaranteed *NOT* to ask for a password, but rather
# to read from stdin and write to stdout an IMAP procotol stream that
# to read from stdin and write to stdout an IMAP protocol stream that
# begins life in the PREAUTH state. When you use a tunnel, you do
# NOT specify a user or password (if you do, they'll be ignored.)
# Instead, you specify a preauthtunnel, as this example illustrates
# for Courier IMAP on Debian:
#preauthtunnel = ssh -q imaphost '/usr/bin/imapd ./Maildir'
#
# 5. If you are using Kerberos and have the Python Kerberos package
# 5. If you are using Kerberos and have the Python gssapi package
# installed, you should not specify a remotepass. If the user has a
# valid Kerberos TGT, Offlineimap will figure out the rest all by
# itself, and fall back to password authentication if needed.
@ -995,7 +1055,7 @@ remoteuser = username
#
# This is most commonly needed with UW IMAP, where you might need to specify the
# directory in which your mail is stored. The 'reference' value will be prefixed
# to all folder paths refering to that repository. E.g. accessing folder 'INBOX'
# to all folder paths referring to that repository. E.g. accessing folder 'INBOX'
# with "reference = Mail" will try to access Mail/INBOX.
#
# The nametrans and folderfilter functions will apply to the full path,
@ -1004,7 +1064,7 @@ remoteuser = username
#reference = Mail
# This option stands in the [Repository RemoteExample] section.
# DEPRECATED: This option stands in the [Repository RemoteExample] section.
#
# IMAP defines an encoding for non-ASCII ("international") characters. Enable
# this option if you want to decode them to the nowadays ubiquitous UTF-8.
@ -1012,10 +1072,26 @@ remoteuser = username
# Note that the IMAP 4rev1 specification (RFC 3501) allows both UTF-8 and
# modified UTF-7 folder names.
#
# This option converts IMAP folder names from IMAP4-UTF-7 to UTF-8.
#
# NOTE/LIMITATION:
# - The reencoding is applied *after* a nametrans function that may be given,
# so it is important to note that nametrans will work on the undecoded
# UTF-7 names.
# - This option only works from a remote IMAP to a local Maildir repository
# - It only works *once*, so it can only be used for one-off backups
# (see https://github.com/OfflineIMAP/offlineimap/issues/299 and especially
# https://github.com/OfflineIMAP/offlineimap/issues/299#issuecomment-331243827)
#
# WARNING: with this option enabled:
# - compatibility with any other version is NOT GUARANTED (including newer);
# - no support is provided.
#
# DEPRECATION:
# This option is only there for backward compatibility with existing set-ups.
# For newly created accounts please use the utf8foldernames option on account
# level.
#
# This feature was merged because it's small changes in the code. However, this
# might seriously decrease the stability of the program. That's why it will
# likely never be marked stable. The approach is: if it works for you, you're
@ -1080,7 +1156,8 @@ remoteuser = username
# download in UIDs order.
#
# If this is unset (the default), then up to maxconnections threads are used
# across all currently syncing folders.
# across all currently syncing folders. This option is sightly recommended in
# IMAP/IMAP mode (no maildir).
#
#singlethreadperfolder = no
@ -1226,7 +1303,7 @@ remoteuser = username
# This option stands in the [Repository RemoteExample] section.
#
# Propagate deletions from remote to local. Messages deleted in this repository
# won't get deleted on the local repositor if set to "no". Default is yes.
# won't get deleted on the local repository if set to "no". Default is yes.
#
# See sync_deletes in the LocalExample section, too.
#
@ -1297,7 +1374,7 @@ remoteuser = username
# This option stands in the [Repository RemoteExample] section.
#
# If offlineiamp is having troubles to download some UIDS, it's possible to get
# If offlineimap is having troubles to download some UIDS, it's possible to get
# them ignored in a list. This only ignore the download.
#
# The function must return the list of UIDs (integers), None otherwise. It is
@ -1320,7 +1397,7 @@ remoteuser = username
#
# http://mail.google.com/support/bin/answer.py?answer=78799&topic=12814
#
# This means ssl is enabled and must be configured correcly when connecting to
# This means ssl is enabled and must be configured correctly when connecting to
# Gmail.
#
# In addition we provide defaults for "oauth2_request_url",

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python
#!/usr/bin/env python2
# Startup from single-user installation
# Copyright (C) 2002 - 2008 John Goerzen
# <jgoerzen@complete.org>
# Copyright (C) 2002-2018 John Goerzen & contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,7 +14,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import sys

View File

@ -1,18 +1,17 @@
__all__ = ['OfflineImap']
__productname__ = 'OfflineIMAP'
# Expecting trailing "-rcN" or "" for stable releases.
__version__ = "7.1.1"
__copyright__ = "Copyright 2002-2017 John Goerzen & contributors"
__author__ = "John Goerzen"
__author_email__= "offlineimap-project@lists.alioth.debian.org"
__description__ = "Disconnected Universal IMAP Mail Synchronization/Reader Support"
__license__ = "Licensed under the GNU GPL v2 or any later version (with an OpenSSL exception)"
__bigcopyright__ = """%(__productname__)s %(__version__)s
%(__license__)s""" % locals()
__homepage__ = "http://www.offlineimap.org"
banner = __bigcopyright__
from offlineimap.version import (
__productname__,
__version__,
__copyright__,
__author__,
__author_email__,
__description__,
__license__,
__bigcopyright__,
__homepage__,
banner
)
from offlineimap.error import OfflineImapError
# put this last, so we don't run into circular dependencies using

View File

@ -34,9 +34,12 @@ SYNC_MUTEXES = {}
SYNC_MUTEXES_LOCK = Lock()
try:
import fcntl
import portalocker
except:
pass # Ok if this fails, we can do without.
try:
import fcntl
except:
pass # Ok if this fails, we can do without.
# FIXME: spaghetti code alert!
def getaccountlist(customconfig):
@ -69,6 +72,8 @@ class Account(CustomConfig.ConfigHelperMixin):
self.name = name
self.metadatadir = config.getmetadatadir()
self.localeval = config.getlocaleval()
# Store utf-8 support as a property of Account object
self.utf_8_support = self.getconfboolean('utf8foldernames', False)
# Current :mod:`offlineimap.ui`, can be used for logging:
self.ui = getglobalui()
self.refreshperiod = self.getconffloat('autorefresh', 0.0)
@ -230,10 +235,13 @@ class SyncableAccount(Account):
self._lockfd = open(self._lockfilepath, 'w')
try:
fcntl.lockf(self._lockfd, fcntl.LOCK_EX|fcntl.LOCK_NB)
portalocker.lock(self._lockfd, portalocker.LOCK_EX)
except NameError:
#fcntl not available (Windows), disable file locking... :(
pass
# portalocker not available for Windows.
try:
fcntl.lockf(self._lockfd, fcntl.LOCK_EX|fcntl.LOCK_NB)
except NameError:
pass # fnctl not available, disable file locking... :(
except IOError:
self._lockfd.close()
six.reraise(OfflineImapError,
@ -248,6 +256,10 @@ class SyncableAccount(Account):
#If we own the lock file, delete it
if self._lockfd and not self._lockfd.closed:
try:
portalocker.unlock(self._lockfd)
except NameError:
pass
self._lockfd.close()
try:
os.unlink(self._lockfilepath)
@ -321,8 +333,20 @@ class SyncableAccount(Account):
folderthreads = []
hook = self.getconf('presynchook', '')
self.callhook(hook)
hook_env = {
'OIMAP_ACCOUNT_NAME': self.getname(),
}
self.callhook('presynchook', hook_env)
if self.utf_8_support and self.remoterepos.getdecodefoldernames():
raise OfflineImapError("Configuration mismatch in account " +
"'%s'. "% self.getname() +
"\nAccount setting 'utf8foldernames' and repository " +
"setting 'decodefoldernames'\nmay not be used at the " +
"same time. This account has not been synchronized.\n" +
"Please check the configuration and documentation.",
OfflineImapError.ERROR.REPO)
quickconfig = self.getconfint('quick', 0)
if quickconfig < 0:
@ -338,6 +362,7 @@ class SyncableAccount(Account):
quick = False
try:
startedThread = False
remoterepos = self.remoterepos
localrepos = self.localrepos
statusrepos = self.statusrepos
@ -360,7 +385,7 @@ class SyncableAccount(Account):
if not remotefolder.sync_this:
self.ui.debug('', "Not syncing filtered folder '%s'"
"[%s]"% (remotefolder, remoterepos))
"[%s]"% (remotefolder.getname(), remoterepos))
continue # Ignore filtered folder.
# The remote folder names must not have the local sep char in
@ -378,7 +403,7 @@ class SyncableAccount(Account):
localfolder = self.get_local_folder(remotefolder)
if not localfolder.sync_this:
self.ui.debug('', "Not syncing filtered folder '%s'"
"[%s]"% (localfolder, localfolder.repository))
"[%s]"% (localfolder.getname(), localfolder.repository))
continue # Ignore filtered folder.
if not globals.options.singlethreading:
@ -394,10 +419,15 @@ class SyncableAccount(Account):
folderthreads.append(thread)
else:
syncfolder(self, remotefolder, quick)
startedThread = True
# Wait for all threads to finish.
for thr in folderthreads:
thr.join()
mbnames.writeIntermediateFile(self.name) # Write out mailbox names.
if startedThread is True:
mbnames.writeIntermediateFile(self.name) # Write out mailbox names.
else:
msg = "Account {}: no folder to sync (folderfilter issue?)".format(self)
raise OfflineImapError(msg, OfflineImapError.ERROR.REPO)
localrepos.forgetfolders()
remoterepos.forgetfolders()
except:
@ -411,20 +441,23 @@ class SyncableAccount(Account):
localrepos.holdordropconnections()
remoterepos.holdordropconnections()
hook = self.getconf('postsynchook', '')
self.callhook(hook)
self.callhook('postsynchook', hook_env)
def callhook(self, cmd):
def callhook(self, name, env={}):
# Check for CTRL-C or SIGTERM and run postsynchook.
if Account.abort_NOW_signal.is_set():
return
cmd = self.getconf(name, '')
if not cmd:
return
try:
self.ui.callhook("Calling hook: " + cmd)
if self.dryrun:
return
p = Popen(cmd, shell=True,
env = env.copy()
env.update(os.environ)
env['OIMAP_HOOK_NAME'] = name
p = Popen(cmd, shell=True, env=env,
stdin=PIPE, stdout=PIPE, stderr=PIPE,
close_fds=True)
r = p.communicate()
@ -494,6 +527,11 @@ def syncfolder(account, remotefolder, quick):
uids = remotefolder.getmessageuidlist()
localfolder.dropmessagelistcache()
if len(uids) > 0:
# Reload the remote message list from min_uid. This avoid issues for
# old messages, which has been added from local on any previous run
# (IOW, message is older than maxage _and_ has high enough UID).
remotefolder.dropmessagelistcache()
remotefolder.cachemessagelist(min_uid=min(uids))
localfolder.cachemessagelist(min_uid=min(uids))
else:
# Remote folder UIDs list is empty for the given range. We still
@ -501,8 +539,12 @@ def syncfolder(account, remotefolder, quick):
# emails).
localfolder.cachemessagelist(min_date=date)
uids = localfolder.getmessageuidlist()
# Take care to only consider positive uids. Negative UIDs might be
# present due to new emails.
uids = [uid for uid in uids if uid > 0]
if len(uids) > 0:
# Update the remote cache list for this new min(uids).
remotefolder.dropmessagelistcache()
remotefolder.cachemessagelist(min_uid=min(uids))
def cachemessagelists_startdate(new, partial, date):

View File

@ -18,9 +18,9 @@ __all__ = ("IMAP4", "IMAP4_SSL", "IMAP4_stream",
"Internaldate2Time", "ParseFlags", "Time2Internaldate",
"Mon2num", "MonthNames", "InternalDate")
__version__ = "2.57"
__version__ = "2.101"
__release__ = "2"
__revision__ = "57"
__revision__ = "101"
__credits__ = """
Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998.
String method conversion by ESR, February 2001.
@ -53,7 +53,7 @@ Fix for correct Python 3 exception handling by Tobias Brink <tobias.brink@gmail.
Fix to allow interruptible IDLE command by Tim Peoples <dromedary512@users.sf.net> September 2015.
Add support for TLS levels by Ben Boeckel <mathstuf@gmail.com> September 2015.
Fix for shutown exception by Sebastien Gross <seb@chezwam.org> November 2015."""
__author__ = "Piers Lauder <piers@janeelix.com>"
__author__ = "Piers Lauder <piers@janeelix.com> & offlineimap team"
__URL__ = "http://imaplib2.sourceforge.net"
__license__ = "Python License"
@ -67,7 +67,6 @@ if bytes != str:
else:
import Queue as queue
string_types = basestring
threading.TIMEOUT_MAX = 9223372036854.0
select_module = select
@ -192,7 +191,7 @@ class Request(object):
def get_response(self, exc_fmt=None):
self.callback = None
if __debug__: self.parent._log(3, '%s:%s.ready.wait' % (self.name, self.tag))
self.ready.wait(threading.TIMEOUT_MAX)
self.ready.wait()
if self.aborted is not None:
typ, val = self.aborted
@ -342,7 +341,7 @@ class IMAP4(object):
self.tagpre = Int2AP(random.randint(4096, 65535))
self.tagre = re.compile(r'(?P<tag>'
+ self.tagpre
+ r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
+ r'\d+) (?P<type>[A-Z]+) ?(?P<data>.*)')
self._mode_ascii() # Only option in py2
@ -545,7 +544,16 @@ class IMAP4(object):
ssl_version = TLS_MAP[self.tls_level][self.ssl_version]
self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile, ca_certs=self.ca_certs, cert_reqs=cert_reqs, ssl_version=ssl_version)
if getattr(ssl, 'HAS_SNI', False):
ctx = ssl.SSLContext(ssl_version)
ctx.verify_mode = cert_reqs
if self.ca_certs is not None:
ctx.load_verify_locations(self.ca_certs)
if self.certfile or self.keyfile:
ctx.load_cert_chain(self.certfile, self.keyfile)
self.sock = ctx.wrap_socket(self.sock, server_hostname=self.host)
else:
self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile, ca_certs=self.ca_certs, cert_reqs=cert_reqs, ssl_version=ssl_version)
ssl_exc = ssl.SSLError
self.read_fd = self.sock.fileno()
except ImportError:
@ -1391,7 +1399,7 @@ class IMAP4(object):
self.commands_lock.release()
if need_event:
if __debug__: self._log(3, 'sync command %s waiting for empty commands Q' % name)
self.state_change_free.wait(threading.TIMEOUT_MAX)
self.state_change_free.wait()
if __debug__: self._log(3, 'sync command %s proceeding' % name)
if self.state not in Commands[name][CMD_VAL_STATES]:

View File

@ -1,5 +1,5 @@
# Gmail IMAP folder support
# Copyright (C) 2002-2016 John Goerzen & contributors.
# Copyright (C) 2002-2017 John Goerzen & contributors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,6 +15,8 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Folder implementation to support features of the Gmail IMAP server."""
import re
import six
from sys import exc_info
@ -23,7 +25,6 @@ from offlineimap import imaputil, imaplibutil, OfflineImapError
import offlineimap.accounts
from .IMAP import IMAPFolder
"""Folder implementation to support features of the Gmail IMAP server."""
class GmailFolder(IMAPFolder):
"""Folder implementation to support features of the Gmail IMAP server.
@ -41,11 +42,8 @@ class GmailFolder(IMAPFolder):
https://developers.google.com/google-apps/gmail/imap_extensions
"""
def __init__(self, imapserver, name, repository):
super(GmailFolder, self).__init__(imapserver, name, repository)
self.trash_folder = repository.gettrashfolder(name)
# Gmail will really delete messages upon EXPUNGE in these folders
self.real_delete_folders = [self.trash_folder, repository.getspamfolder()]
def __init__(self, imapserver, name, repository, decode=True):
super(GmailFolder, self).__init__(imapserver, name, repository, decode)
# The header under which labels are stored
self.labelsheader = self.repository.account.getconf('labelsheader', 'X-Keywords')
@ -81,7 +79,7 @@ class GmailFolder(IMAPFolder):
# Embed the labels into the message headers
if self.synclabels:
m = re.search('X-GM-LABELS\s*\(([^\)]*)\)', data[0][0])
m = re.search('X-GM-LABELS\s*[(](.*)[)]', data[0][0])
if m:
labels = set([imaputil.dequote(lb) for lb in imaputil.imapsplit(m.group(1))])
else:
@ -155,6 +153,7 @@ class GmailFolder(IMAPFolder):
if messagestr == None:
continue
messagestr = messagestr.split(' ', 1)[1]
# e.g.: {'X-GM-LABELS': '("Webserver (RW.net)" "\\Inbox" GInbox)', 'FLAGS': '(\\Seen)', 'UID': '275440'}
options = imaputil.flags2hash(messagestr)
if not 'UID' in options:
self.ui.warn('No UID in message with options %s' %\
@ -164,7 +163,8 @@ class GmailFolder(IMAPFolder):
uid = int(options['UID'])
self.messagelist[uid] = self.msglist_item_initializer(uid)
flags = imaputil.flagsimap2maildir(options['FLAGS'])
m = re.search('\(([^\)]*)\)', options['X-GM-LABELS'])
# e.g.: '("Webserver (RW.net)" "\\Inbox" GInbox)'
m = re.search('^[(](.*)[)]', options['X-GM-LABELS'])
if m:
labels = set([imaputil.dequote(lb) for lb in imaputil.imapsplit(m.group(1))])
else:

View File

@ -41,12 +41,21 @@ MSGCOPY_NAMESPACE = 'MSGCOPY_'
class IMAPFolder(BaseFolder):
def __init__(self, imapserver, name, repository):
# FIXME: decide if unquoted name is from the responsability of the
# caller or not, but not both.
def __init__(self, imapserver, name, repository, decode=True):
# decode the folder name from IMAP4_utf_7 to utf_8 if
# - utf8foldernames is enabled for the *account*
# - the decode argument is given
# (default True is used when the folder name is the result of
# querying the IMAP server, while False is used when creating
# a folder object from a locally available utf_8 name)
# In any case the given name is first dequoted.
name = imaputil.dequote(name)
if decode and repository.account.utf_8_support:
name = imaputil.IMAP_utf8(name)
self.sep = imapserver.delim
super(IMAPFolder, self).__init__(name, repository)
if repository.getdecodefoldernames():
self.visiblename = imaputil.decode_mailbox_name(self.visiblename)
self.idle_mode = False
self.expunge = repository.getexpunge()
self.root = None # imapserver.root
@ -67,7 +76,6 @@ class IMAPFolder(BaseFolder):
if self.repository.getidlefolders():
self.idle_mode = True
def __selectro(self, imapobj, force=False):
"""Select this folder when we do not need write access.
@ -78,9 +86,15 @@ class IMAPFolder(BaseFolder):
:param: Enforce new SELECT even if we are on that folder already.
:returns: raises :exc:`OfflineImapError` severity FOLDER on error"""
try:
imapobj.select(self.getfullname(), force = force)
imapobj.select(self.getfullIMAPname(), force=force)
except imapobj.readonly:
imapobj.select(self.getfullname(), readonly = True, force = force)
imapobj.select(self.getfullIMAPname(), readonly=True, force=force)
def getfullIMAPname(self):
name = self.getfullname()
if self.repository.account.utf_8_support:
name = imaputil.utf8_IMAP(name)
return name
# Interface from BaseFolder
def suggeststhreads(self):
@ -145,7 +159,7 @@ class IMAPFolder(BaseFolder):
imapobj = self.imapserver.acquireconnection()
try:
# Select folder and get number of messages.
restype, imapdata = imapobj.select(self.getfullname(), True,
restype, imapdata = imapobj.select(self.getfullIMAPname(), True,
True)
self.imapserver.releaseconnection(imapobj)
except OfflineImapError as e:
@ -195,11 +209,17 @@ class IMAPFolder(BaseFolder):
Returns: range(s) for messages or None if no messages
are to be fetched."""
res_type, res_data = imapobj.search(None, search_conditions)
if res_type != 'OK':
try:
res_type, res_data = imapobj.search(None, search_conditions)
if res_type != 'OK':
raise OfflineImapError("SEARCH in folder [%s]%s failed. "
"Search string was '%s'. Server responded '[%s] %s'"% (
self.getrepository(), self, search_cond, res_type, res_data),
OfflineImapError.ERROR.FOLDER)
except Exception as e:
raise OfflineImapError("SEARCH in folder [%s]%s failed. "
"Search string was '%s'. Server responded '[%s] %s'"% (
self.getrepository(), self, search_cond, res_type, res_data),
"Search string was '%s'. Error: %s"% (
self.getrepository(), self, search_cond, str(e)),
OfflineImapError.ERROR.FOLDER)
# Davmail returns list instead of list of one element string.
# On first run the first element is empty.
@ -211,7 +231,7 @@ class IMAPFolder(BaseFolder):
res_data.remove(0)
return res_data
res_type, imapdata = imapobj.select(self.getfullname(), True, True)
res_type, imapdata = imapobj.select(self.getfullIMAPname(), True, True)
if imapdata == [None] or imapdata[0] == '0':
# Empty folder, no need to populate message list.
return None
@ -290,13 +310,6 @@ class IMAPFolder(BaseFolder):
'keywords': keywords}
self.ui.messagelistloaded(self.repository, self, self.getmessagecount())
# Interface from BaseFolder
def getvisiblename(self):
vname = super(IMAPFolder, self).getvisiblename()
if self.repository.getdecodefoldernames():
return imaputil.decode_mailbox_name(vname)
return vname
# Interface from BaseFolder
def getmessage(self, uid):
"""Retrieve message with UID from the IMAP server (incl body).
@ -389,6 +402,7 @@ class IMAPFolder(BaseFolder):
return 0
matchinguids = matchinguids.split(' ')
matchinguids = list(set(matchinguids)) # Remove duplicates.
self.ui.debug('imap', '__savemessage_searchforheader: matchinguids now '
+ repr(matchinguids))
if len(matchinguids) != 1 or matchinguids[0] is None:
@ -448,24 +462,46 @@ class IMAPFolder(BaseFolder):
raise OfflineImapError('Error fetching mail headers: %s'%
'. '.join(result[1]), OfflineImapError.ERROR.MESSAGE)
# result is like:
# [
# ('185 (RFC822.HEADER {1789}', '... mail headers ...'), ' UID 2444)',
# ('186 (RFC822.HEADER {1789}', '... 2nd mail headers ...'), ' UID 2445)'
# ]
result = result[1]
found = 0
found = None
# item is like:
# ('185 (RFC822.HEADER {1789}', '... mail headers ...'), ' UID 2444)'
for item in result:
if found == 0 and type(item) == type( () ):
if found is None and type(item) == tuple:
# Walk just tuples.
if re.search("(?:^|\\r|\\n)%s:\s*%s(?:\\r|\\n)"% (headername, headervalue),
item[1], flags=re.IGNORECASE):
found = 1
elif found == 1:
if type(item) == type (""):
found = item[0]
elif found is not None:
if type(item) == type(""):
uid = re.search("UID\s+(\d+)", item, flags=re.IGNORECASE)
if uid:
return int(uid.group(1))
else:
self.ui.warn("Can't parse FETCH response, can't find UID: %s", result.__repr__())
# This parsing is for Davmail.
# https://github.com/OfflineIMAP/offlineimap/issues/479
# item is like:
# ')'
# and item[0] stored in "found" is like:
# '1694 (UID 1694 RFC822.HEADER {1294}'
uid = re.search("\d+\s+\(UID\s+(\d+)", found, flags=re.IGNORECASE)
if uid:
return int(uid.group(1))
self.ui.warn("Can't parse FETCH response, can't find UID in %s"%
item
)
self.ui.debug('imap', "Got: %s"% repr(result))
else:
self.ui.warn("Can't parse FETCH response, we awaited string: %s", result.__repr__())
self.ui.warn("Can't parse FETCH response, we awaited string: %s"%
repr(item)
)
return 0
@ -613,7 +649,7 @@ class IMAPFolder(BaseFolder):
try:
# Select folder for append and make the box READ-WRITE.
imapobj.select(self.getfullname())
imapobj.select(self.getfullIMAPname())
except imapobj.readonly:
# readonly exception. Return original uid to notify that
# we did not save the message. (see savemessage in Base.py)
@ -622,7 +658,7 @@ class IMAPFolder(BaseFolder):
# Do the APPEND.
try:
(typ, dat) = imapobj.append(self.getfullname(),
(typ, dat) = imapobj.append(self.getfullIMAPname(),
imaputil.flagsmaildir2imap(flags), date, content)
# This should only catch 'NO' responses since append()
# will raise an exception for 'BAD' responses:
@ -693,21 +729,32 @@ class IMAPFolder(BaseFolder):
OfflineImapError.ERROR.MESSAGE)
if uid == 0:
self.ui.warn("savemessage: Server supports UIDPLUS, but"
" we got no usable uid back. APPENDUID reponse was "
" we got no usable UID back. APPENDUID reponse was "
"'%s'"% str(resp))
else:
# We don't support UIDPLUS.
uid = self.__savemessage_searchforheader(imapobj, headername,
headervalue)
# See docs for savemessage in Base.py for explanation
# of this and other return values.
if uid == 0:
self.ui.debug('imap', 'savemessage: attempt to get new UID '
'UID failed. Search headers manually.')
uid = self.__savemessage_fetchheaders(imapobj, headername,
try:
# We don't use UIDPLUS.
uid = self.__savemessage_searchforheader(imapobj, headername,
headervalue)
self.ui.warn('imap', "savemessage: Searching mails for new "
"Message-ID failed. Could not determine new UID.")
# See docs for savemessage in Base.py for explanation
# of this and other return values.
if uid == 0:
self.ui.debug('imap', 'savemessage: attempt to get new UID '
'UID failed. Search headers manually.')
uid = self.__savemessage_fetchheaders(imapobj, headername,
headervalue)
self.ui.warn("savemessage: Searching mails for new "
"Message-ID failed. Could not determine new UID "
"on %s."% self.getname())
# Something wrong happened while trying to get the UID. Explain
# the error might be about the 'get UID' process not necesseraly
# the APPEND.
except Exception:
self.ui.warn("%s: could not determine the UID while we got "
"no error while appending the email with '%s: %s'"%
(self.getname(), headername, headervalue)
)
raise
finally:
if imapobj:
self.imapserver.releaseconnection(imapobj)
@ -735,7 +782,7 @@ class IMAPFolder(BaseFolder):
fails_left = retry_num # Retry on dropped connection.
while fails_left:
try:
imapobj.select(self.getfullname(), readonly=True)
imapobj.select(self.getfullIMAPname(), readonly=True)
res_type, data = imapobj.uid('fetch', uids, query)
break
except imapobj.abort as e:
@ -795,7 +842,7 @@ class IMAPFolder(BaseFolder):
- field: field name to be stored/updated
- data: field contents
"""
imapobj.select(self.getfullname())
imapobj.select(self.getfullIMAPname())
res_type, retdata = imapobj.uid('store', uid, field, data)
if res_type != 'OK':
severity = OfflineImapError.ERROR.MESSAGE
@ -856,7 +903,7 @@ class IMAPFolder(BaseFolder):
imapobj = self.imapserver.acquireconnection()
try:
try:
imapobj.select(self.getfullname())
imapobj.select(self.getfullIMAPname())
except imapobj.readonly:
self.ui.flagstoreadonly(self, uidlist, flags)
return
@ -931,7 +978,7 @@ class IMAPFolder(BaseFolder):
imapobj = self.imapserver.acquireconnection()
try:
try:
imapobj.select(self.getfullname())
imapobj.select(self.getfullIMAPname())
except imapobj.readonly:
self.ui.deletereadonly(self, uidlist)
return

View File

@ -119,7 +119,7 @@ class LocalStatusFolder(BaseFolder):
# Convert from format v1.
elif line == (self.magicline % 1):
self.ui._msg('Upgrading LocalStatus cache from version 1'
self.ui._msg('Upgrading LocalStatus cache from version 1 '
'to version 2 for %s:%s'% (self.repository, self))
self.readstatus_v1(cachefd)
cachefd.close()

View File

@ -352,8 +352,14 @@ class LocalStatusSQLiteFolder(BaseFolder):
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime, 'mtime': mtime, 'labels': labels}
flags = ''.join(sorted(flags))
labels = ', '.join(sorted(labels))
self.__sql_write('INSERT INTO status (id,flags,mtime,labels) VALUES (?,?,?,?)',
(uid,flags,mtime,labels))
try:
self.__sql_write('INSERT INTO status (id,flags,mtime,labels) VALUES (?,?,?,?)',
(uid,flags,mtime,labels))
except Exception as e:
six.reraise(UserWarning,
UserWarning("%s while inserting UID %s"%
(str(e), str(uid))),
exc_info()[2])
return uid

View File

@ -82,6 +82,10 @@ class MaildirFolder(BaseFolder):
"general", "utime_from_header", False)
self._utime_from_header = self.config.getdefaultboolean(
self.repoconfname, "utime_from_header", utime_from_header_global)
# What do we substitute pathname separator in names (if any)
self.sep_subst = '-'
if os.path.sep == self.sep_subst:
self.sep_subst = '_'
# Interface from BaseFolder
def getfullname(self):
@ -286,9 +290,10 @@ class MaildirFolder(BaseFolder):
:returns: String containing unique message filename"""
timeval, timeseq = _gettimeseq(date)
return '%d_%d.%d.%s,U=%d,FMD5=%s%s2,%s'% \
uniq_name = '%d_%d.%d.%s,U=%d,FMD5=%s%s2,%s' % \
(timeval, timeseq, os.getpid(), socket.gethostname(),
uid, self._foldermd5, self.infosep, ''.join(sorted(flags)))
return uniq_name.replace(os.path.sep, self.sep_subst)
def save_to_tmp_file(self, filename, content):

View File

@ -40,8 +40,8 @@ class MappedIMAPFolder(IMAPFolder):
diskr2l: dict mapping message uids: self.r2l[remoteuid]=localuid
diskl2r: dict mapping message uids: self.r2l[localuid]=remoteuid"""
def __init__(self, *args, **kwargs):
IMAPFolder.__init__(self, *args, **kwargs)
def __init__(self, imapserver, name, repository, decode=True):
IMAPFolder.__init__(self, imapserver, name, repository, decode=False)
self.dryrun = self.config.getdefaultboolean("general", "dry-run", True)
self.maplock = Lock()
self.diskr2l, self.diskl2r = self._loadmaps()
@ -49,7 +49,7 @@ class MappedIMAPFolder(IMAPFolder):
# Representing the local IMAP Folder using local UIDs.
# XXX: This should be removed since we inherit from IMAPFolder.
# See commit 3ce514e92ba7 to know more.
self._mb = IMAPFolder(*args, **kwargs)
self._mb = IMAPFolder(imapserver, name, repository, decode=False)
def _getmapfilename(self):
return os.path.join(self.repository.getmapdir(),
@ -267,8 +267,12 @@ class MappedIMAPFolder(IMAPFolder):
newluid = self._mb.savemessage(-1, content, flags, rtime)
if newluid < 1:
raise ValueError("Backend could not find uid for message, "
"returned %s"% newluid)
raise OfflineImapError("server of repository '%s' did not return "
"a valid UID (got '%s') for UID '%s' from '%s'"% (
self._mb.getname(), newluid, uid, self.getname()
),
OfflineImapError.ERROR.MESSAGE
)
with self.maplock:
self.diskl2r[newluid] = uid
self.diskr2l[uid] = newluid

View File

@ -19,11 +19,12 @@ import fcntl
import time
import subprocess
import threading
import rfc6555
import socket
import errno
import zlib
from sys import exc_info
from hashlib import sha1
from hashlib import sha512, sha384, sha256, sha224, sha1
import six
@ -78,8 +79,15 @@ class UsefulIMAPMixIn(object):
def open_socket(self):
"""open_socket()
Open socket choosing first address family available."""
if self.af == socket.AF_UNSPEC:
# happy-eyeballs!
return rfc6555.create_connection((self.host, self.port))
else:
return self._open_socket_for_af(self.af)
def _open_socket_for_af(self, af):
msg = (-1, 'could not open socket')
for res in socket.getaddrinfo(self.host, self.port, self.af, socket.SOCK_STREAM):
for res in socket.getaddrinfo(self.host, self.port, af, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
# use socket of our own, possiblly socksified socket.
@ -124,7 +132,7 @@ class IMAP4_Tunnel(UsefulIMAPMixIn, IMAP4):
"""The tunnelcmd comes in on host!"""
self.host = host
self.process = subprocess.Popen(host, shell=True, close_fds=True,
self.process = subprocess.Popen('exec %s'%host, shell=True, close_fds=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(self.outfd, self.infd) = (self.process.stdin, self.process.stdout)
# imaplib2 polls on this fd
@ -201,15 +209,18 @@ class WrappedIMAP4_SSL(UsefulIMAPMixIn, IMAP4_SSL):
"having SSL helps nothing.", OfflineImapError.ERROR.REPO)
super(WrappedIMAP4_SSL, self).open(host, port)
if self._fingerprint:
server_cert = self.sock.getpeercert(True)
hashes = sha512, sha384, sha256, sha224, sha1
server_fingerprints = [hash(server_cert).hexdigest() for hash in hashes]
# compare fingerprints
fingerprint = sha1(self.sock.getpeercert(True)).hexdigest()
if fingerprint not in self._fingerprint:
raise OfflineImapError("Server SSL fingerprint '%s' "
matches = [(server_fingerprint in self._fingerprint) for server_fingerprint in server_fingerprints]
if not any(matches):
raise OfflineImapError("Server SSL fingerprint(s) '%s' "
"for hostname '%s' "
"does not match configured fingerprint(s) %s. "
"Please verify and set 'cert_fingerprint' accordingly "
"if not set yet."%
(fingerprint, host, self._fingerprint),
(zip([hash.__name__ for hash in hashes], server_fingerprints), host, self._fingerprint),
OfflineImapError.ERROR.REPO)

View File

@ -1,5 +1,5 @@
# IMAP server support
# Copyright (C) 2002-2016 John Goerzen & contributors.
# Copyright (C) 2002-2018 John Goerzen & contributors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,9 +15,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import datetime
import hmac
import socket
import base64
import json
import urllib
import time
@ -36,13 +36,10 @@ from offlineimap.ui import getglobalui
try:
# do we have a recent pykerberos?
have_gss = False
import kerberos
if 'authGSSClientWrap' in dir(kerberos):
have_gss = True
import gssapi
have_gss = True
except ImportError:
pass
have_gss = False
class IMAPServer(object):
@ -55,9 +52,6 @@ class IMAPServer(object):
delim The server's folder delimiter. Only valid after acquireconnection()
"""
GSS_STATE_STEP = 0
GSS_STATE_WRAP = 1
def __init__(self, repos):
""":repos: a IMAPRepository instance."""
@ -92,7 +86,7 @@ class IMAPServer(object):
self.af = socket.AF_INET
else:
self.af = socket.AF_UNSPEC
self.hostname = None if self.preauth_tunnel else repos.gethost()
self.hostname = None if self.transport_tunnel or self.preauth_tunnel else repos.gethost()
self.port = repos.getport()
if self.port is None:
self.port = 993 if self.usessl else 143
@ -107,8 +101,10 @@ class IMAPServer(object):
self.sslversion = repos.getsslversion()
self.starttls = repos.getstarttls()
if self.tlslevel is not "tls_compat" and self.sslversion is None:
raise Exception("When 'tls_version' is not 'tls_compat' "
if self.usessl \
and self.tlslevel != "tls_compat" \
and self.sslversion is None:
raise Exception("When 'tls_level' is not 'tls_compat' "
"the 'ssl_version' must be set explicitly.")
self.oauth2_refresh_token = repos.getoauth2_refresh_token()
@ -116,6 +112,7 @@ class IMAPServer(object):
self.oauth2_client_id = repos.getoauth2_client_id()
self.oauth2_client_secret = repos.getoauth2_client_secret()
self.oauth2_request_url = repos.getoauth2_request_url()
self.oauth2_access_token_expires_at = None
self.delim = None
self.root = None
@ -127,7 +124,6 @@ class IMAPServer(object):
self.connectionlock = Lock()
self.reference = repos.getreference()
self.idlefolders = repos.getidlefolders()
self.gss_step = self.GSS_STATE_STEP
self.gss_vc = None
self.gssapi = False
@ -183,8 +179,7 @@ class IMAPServer(object):
# get 1) configured password first 2) fall back to asking via UI
self.password = self.repos.getpassword() or \
self.ui.getpass(self.repos.getname(), self.config,
self.passworderror)
self.ui.getpass(self.username, self.config, self.passworderror)
self.passworderror = None
return self.password
@ -208,6 +203,11 @@ class IMAPServer(object):
http://tools.ietf.org/html/rfc4616"""
authc = self.username
if not authc:
raise OfflineImapError("No username provided for '%s'"
% self.repos.getname(),
OfflineImapError.ERROR.REPO)
passwd = self.__getpassword()
authz = b''
if self.user_identity != None:
@ -221,6 +221,12 @@ class IMAPServer(object):
return retval
def __xoauth2handler(self, response):
now = datetime.datetime.now()
if self.oauth2_access_token_expires_at \
and self.oauth2_access_token_expires_at < now:
self.oauth2_access_token = None
self.ui.debug('imap', 'xoauth2handler: oauth2_access_token expired')
if self.oauth2_access_token is None:
if self.oauth2_request_url is None:
raise OfflineImapError("No remote oauth2_request_url for "
@ -258,41 +264,65 @@ class IMAPServer(object):
raise OfflineImapError("xoauth2handler got: %s"% resp,
OfflineImapError.ERROR.REPO)
self.oauth2_access_token = resp['access_token']
if u'expires_in' in resp:
self.oauth2_access_token_expires_at = now + datetime.timedelta(
seconds=resp['expires_in']/2
)
self.ui.debug('imap', 'xoauth2handler: access_token "%s"'%
self.oauth2_access_token)
self.ui.debug('imap', 'xoauth2handler: access_token "%s expires %s"'% (
self.oauth2_access_token, self.oauth2_access_token_expires_at))
auth_string = 'user=%s\1auth=Bearer %s\1\1'% (
self.username, self.oauth2_access_token)
#auth_string = base64.b64encode(auth_string)
self.ui.debug('imap', 'xoauth2handler: returning "%s"'% auth_string)
return auth_string
def __gssauth(self, response):
data = base64.b64encode(response)
# Perform the next step handling a GSSAPI connection.
# Client sends first, so token will be ignored if there is no context.
def __gsshandler(self, token):
if token == "":
token = None
try:
if self.gss_step == self.GSS_STATE_STEP:
if not self.gss_vc:
rc, self.gss_vc = kerberos.authGSSClientInit(
'imap@' + self.hostname)
response = kerberos.authGSSClientResponse(self.gss_vc)
rc = kerberos.authGSSClientStep(self.gss_vc, data)
if rc != kerberos.AUTH_GSS_CONTINUE:
self.gss_step = self.GSS_STATE_WRAP
elif self.gss_step == self.GSS_STATE_WRAP:
rc = kerberos.authGSSClientUnwrap(self.gss_vc, data)
response = kerberos.authGSSClientResponse(self.gss_vc)
rc = kerberos.authGSSClientWrap(
self.gss_vc, response, self.username)
response = kerberos.authGSSClientResponse(self.gss_vc)
except kerberos.GSSError as err:
# Kerberos errored out on us, respond with None to cancel the
# authentication
self.ui.debug('imap', '%s: %s'% (err[0][0], err[1][0]))
return None
if not self.gss_vc:
name = gssapi.Name('imap@' + self.hostname,
gssapi.NameType.hostbased_service)
self.gss_vc = gssapi.SecurityContext(usage="initiate",
name=name)
if not response:
response = ''
return base64.b64decode(response)
if not self.gss_vc.complete:
response = self.gss_vc.step(token)
return response if response else ""
elif token is None:
# uh... context is complete, so there's no negotiation we can
# do. But we also don't have a token, so we can't send any
# kind of response. Empirically, some (but not all) servers
# seem to put us in this state, and seem fine with getting no
# GSSAPI content in response, so give it to them.
return ""
# Don't bother checking qop because we're over a TLS channel
# already. But hey, if some server started encrypting tomorrow,
# we'd be ready since krb5 always requests integrity and
# confidentiality support.
response = self.gss_vc.unwrap(token)
# This is a behavior we got from pykerberos. First byte is one,
# first four bytes are preserved (pykerberos calls this a length).
# Any additional bytes are username.
reply = []
reply[0:4] = response.message[0:4]
reply[0] = '\x01'
if self.username:
reply[5:] = self.username
reply = ''.join(reply)
response = self.gss_vc.wrap(reply, response.encrypted)
return response.message if response.message else ""
except gssapi.exceptions.GSSError as err:
# GSSAPI errored out on us; respond with None to cancel the
# authentication
self.ui.debug('imap', err.gen_message())
return None
def __start_tls(self, imapobj):
if 'STARTTLS' in imapobj.capabilities and not self.usessl:
@ -326,18 +356,13 @@ class IMAPServer(object):
return False
self.connectionlock.acquire()
self.gssapi = False
try:
imapobj.authenticate('GSSAPI', self.__gssauth)
return True
except imapobj.error as e:
self.gssapi = False
raise
else:
imapobj.authenticate('GSSAPI', self.__gsshandler)
self.gssapi = True
kerberos.authGSSClientClean(self.gss_vc)
self.gss_vc = None
self.gss_step = self.GSS_STATE_STEP
return True
finally:
self.gss_vc = None
self.connectionlock.release()
def __authn_cram_md5(self, imapobj):
@ -619,7 +644,8 @@ class IMAPServer(object):
if self.port != 993:
reason = "Could not connect via SSL to host '%s' and non-s"\
"tandard ssl port %d configured. Make sure you connect"\
" to the correct port."% (self.hostname, self.port)
" to the correct port. Got: %s"% (
self.hostname, self.port, e)
else:
reason = "Unknown SSL protocol connecting to host '%s' for "\
"repository '%s'. OpenSSL responded:\n%s"\
@ -679,8 +705,7 @@ class IMAPServer(object):
self.assignedconnections = []
self.availableconnections = []
self.lastowner = {}
# reset kerberos state
self.gss_step = self.GSS_STATE_STEP
# reset GSSAPI state
self.gss_vc = None
self.gssapi = False
@ -796,13 +821,14 @@ class IdleThread(object):
localrepos = account.localrepos
remoterepos = account.remoterepos
statusrepos = account.statusrepos
remotefolder = remoterepos.getfolder(self.folder)
remotefolder = remoterepos.getfolder(self.folder, decode=False)
hook = account.getconf('presynchook', '')
account.callhook(hook)
hook_env = {
'OIMAP_ACCOUNT_NAME': account.getname(),
}
account.callhook('presynchook', hook_env)
offlineimap.accounts.syncfolder(account, remotefolder, quick=False)
hook = account.getconf('postsynchook', '')
account.callhook(hook)
account.callhook('postsynchook', hook_env)
ui = getglobalui()
ui.unregisterthread(currentThread()) #syncfolder registered the thread

View File

@ -17,6 +17,8 @@
import re
import string
import binascii
import codecs
from offlineimap.ui import getglobalui
@ -370,3 +372,85 @@ def decode_mailbox_name(name):
return ret.decode('utf-7').encode('utf-8')
except (UnicodeDecodeError, UnicodeEncodeError):
return name
# Functionality to convert folder names encoded in IMAP_utf_7 to utf_8.
# This is achieved by defining 'imap4_utf_7' as a proper encoding scheme.
# Public API, to be used in repository definitions
def IMAP_utf8(foldername):
"""Convert IMAP4_utf_7 encoded string to utf-8"""
return foldername.decode('imap4-utf-7').encode('utf-8')
def utf8_IMAP(foldername):
"""Convert utf-8 encoded string to IMAP4_utf_7"""
return foldername.decode('utf-8').encode('imap4-utf-7')
# Codec definition
def modified_base64(s):
s = s.encode('utf-16be')
return binascii.b2a_base64(s).rstrip('\n=').replace('/', ',')
def doB64(_in, r):
if _in:
r.append('&%s-' % modified_base64(''.join(_in)))
del _in[:]
def encoder(s):
r = []
_in = []
for c in s:
ordC = ord(c)
if 0x20 <= ordC <= 0x25 or 0x27 <= ordC <= 0x7e:
doB64(_in, r)
r.append(c)
elif c == '&':
doB64(_in, r)
r.append('&-')
else:
_in.append(c)
doB64(_in, r)
return (str(''.join(r)), len(s))
# decoding
def modified_unbase64(s):
b = binascii.a2b_base64(s.replace(',', '/') + '===')
return unicode(b, 'utf-16be')
def decoder(s):
r = []
decode = []
for c in s:
if c == '&' and not decode:
decode.append('&')
elif c == '-' and decode:
if len(decode) == 1:
r.append('&')
else:
r.append(modified_unbase64(''.join(decode[1:])))
decode = []
elif decode:
decode.append(c)
else:
r.append(c)
if decode:
r.append(modified_unbase64(''.join(decode[1:])))
bin_str = ''.join(r)
return (bin_str, len(s))
class StreamReader(codecs.StreamReader):
def decode(self, s, errors='strict'):
return decoder(s)
class StreamWriter(codecs.StreamWriter):
def decode(self, s, errors='strict'):
return encoder(s)
def imap4_utf_7(name):
if name == 'imap4-utf-7':
return (encoder, decoder, StreamReader, StreamWriter)
codecs.register(imap4_utf_7)

View File

@ -433,8 +433,9 @@ class OfflineImap(object):
accounts.Account.set_abort_event(self.config, 2)
elif sig in (signal.SIGTERM, signal.SIGINT, signal.SIGHUP):
# tell each account to ABORT ASAP (ctrl-c)
getglobalui().warn("Terminating NOW (this may "\
"take a few seconds)...")
getglobalui().warn("Preparing to shutdown after sync (this may "\
"take some time), press CTRL-C three "\
"times to shutdown immediately")
accounts.Account.set_abort_event(self.config, 3)
if 'thread' in self.ui.debuglist:
self.__dumpstacks(5)

View File

@ -1,6 +1,6 @@
""" Base repository support """
# Copyright (C) 2002-2016 John Goerzen & contributors
# Copyright (C) 2002-2017 John Goerzen & contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -165,7 +165,13 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
def deletefolder(self, foldername):
raise NotImplementedError
def getfolder(self, foldername):
def getfolder(self, foldername, decode=True):
"""Get the folder for this repo.
WARNING: the signature changes whether it's remote or local:
- remote types have the decode arg
- local types don't have the decode arg
"""
raise NotImplementedError
def sync_folder_structure(self, local_repo, status_repo):
@ -242,7 +248,7 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
# Get IMAPFolder and see if the reverse nametrans works fine.
# TODO: getfolder() works only because we succeed in getting
# inexisting folders which I would like to change. Take care!
tmp_remotefolder = remote_repo.getfolder(remote_name)
tmp_remotefolder = remote_repo.getfolder(remote_name, decode=False)
loop_name = tmp_remotefolder.getvisiblename().replace(
remote_repo.getsep(), local_repo.getsep())
if local_name != loop_name:

View File

@ -19,6 +19,7 @@
from offlineimap.repository.IMAP import IMAPRepository
from offlineimap import folder, OfflineImapError
class GmailRepository(IMAPRepository):
"""Gmail IMAP repository.
@ -87,9 +88,9 @@ class GmailRepository(IMAPRepository):
def getpreauthtunnel(self):
return None
def getfolder(self, foldername):
def getfolder(self, foldername, decode=True):
return self.getfoldertype()(self.imapserver, foldername,
self)
self, decode)
def getfoldertype(self):
return folder.Gmail.GmailFolder

View File

@ -1,6 +1,6 @@
""" IMAP repository support """
# Copyright (C) 2002-2016 John Goerzen & contributors
# Copyright (C) 2002-2019 John Goerzen & contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -293,7 +293,7 @@ class IMAPRepository(BaseRepository):
comma-separated fingerprints in hex form."""
value = self.getconf('cert_fingerprint', "")
return [f.strip().lower() for f in value.split(',') if f]
return [f.strip().lower().replace(":", "") for f in value.split(',') if f]
def setoauth2_request_url(self, url):
self.oauth2_request_url = url
@ -428,10 +428,10 @@ class IMAPRepository(BaseRepository):
# No strategy yielded a password!
return None
def getfolder(self, foldername):
def getfolder(self, foldername, decode=True):
"""Return instance of OfflineIMAP representative folder."""
return self.getfoldertype()(self.imapserver, foldername, self)
return self.getfoldertype()(self.imapserver, foldername, self, decode)
def getfoldertype(self):
return folder.IMAP.IMAPFolder
@ -480,8 +480,7 @@ class IMAPRepository(BaseRepository):
flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
if '\\noselect' in flaglist:
continue
foldername = imaputil.dequote(name)
retval.append(self.getfoldertype()(self.imapserver, foldername,
retval.append(self.getfoldertype()(self.imapserver, name,
self))
# Add all folderincludes
if len(self.folderincludes):
@ -489,7 +488,7 @@ class IMAPRepository(BaseRepository):
try:
for foldername in self.folderincludes:
try:
imapobj.select(foldername, readonly=True)
imapobj.select(imaputil.utf8_IMAP(foldername), readonly=True)
except OfflineImapError as e:
# couldn't select this folderinclude, so ignore folder.
if e.severity > OfflineImapError.ERROR.FOLDER:
@ -498,7 +497,7 @@ class IMAPRepository(BaseRepository):
'Invalid folderinclude:')
continue
retval.append(self.getfoldertype()(
self.imapserver, foldername, self))
self.imapserver, foldername, self, decode=False))
finally:
self.imapserver.releaseconnection(imapobj)
@ -525,6 +524,8 @@ class IMAPRepository(BaseRepository):
def deletefolder(self, foldername):
"""Delete a folder on the IMAP server."""
if self.account.utf_8_support:
foldername = imaputil.utf8_IMAP(foldername)
imapobj = self.imapserver.acquireconnection()
try:
result = imapobj.delete(foldername)
@ -544,18 +545,35 @@ class IMAPRepository(BaseRepository):
:param foldername: Full path of the folder to be created."""
if foldername is '':
if foldername == '':
return
if self.getreference():
foldername = self.getreference() + self.getsep() + foldername
if not foldername: # Create top level folder as folder separator.
foldername = self.getsep()
self.makefolder_single(foldername)
return
parts = foldername.split(self.getsep())
folder_paths = [self.getsep().join(parts[:n + 1]) for n in range(len(parts))]
for folder_path in folder_paths:
try:
self.makefolder_single(folder_path)
except OfflineImapError as e:
reasonLower = e.reason.lower() # Handle reasons '[ALREADYEXISTS]' and 'Mailbox already exists!' @chris001
if not ('already' in reasonLower and 'exists' in reasonLower):
raise
def makefolder_single(self, foldername):
self.ui.makefolder(self, foldername)
if self.account.dryrun:
return
imapobj = self.imapserver.acquireconnection()
try:
if self.account.utf_8_support:
foldername = imaputil.utf8_IMAP(foldername)
result = imapobj.create(foldername)
if result[0] != 'OK':
raise OfflineImapError("Folder '%s'[%s] could not be created. "

View File

@ -15,13 +15,12 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from threading import Lock, Thread, BoundedSemaphore, currentThread
from threading import Lock, Thread, BoundedSemaphore
try:
from Queue import Queue, Empty
except ImportError: # python3
from queue import Queue, Empty
import traceback
import os.path
from offlineimap.ui import getglobalui

View File

@ -1,5 +1,5 @@
# Curses-based interfaces
# Copyright (C) 2003-2016 John Goerzen & contributors.
# Copyright (C) 2003-2018 John Goerzen & contributors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -137,7 +137,13 @@ class CursesAccountFrame:
sleepstr = '%3d:%02d'% (secs // 60, secs % 60) if secs else 'active'
accstr = '%s: [%s] %12.12s: '% (self.acc_num, sleepstr, self.account)
self.ui.exec_locked(self.window.addstr, 0, 0, accstr)
def addstr():
try:
self.window.addstr(0, 0, accstr)
except curses.error as e: # Occurs when the terminal is very small
pass
self.ui.exec_locked(addstr);
self.location = len(accstr)
def setwindow(self, curses_win, acc_num):
@ -211,7 +217,10 @@ class CursesThreadFrame:
def display(self):
def locked_display():
self.window.addch(self.y, self.x, '@', self.curses_color)
try:
self.window.addch(self.y, self.x, '@', self.curses_color)
except curses.error: # Occurs when the terminal is very small
pass
self.window.refresh()
# lock the curses IO while fudging stuff
self.ui.exec_locked(locked_display)
@ -549,7 +558,7 @@ class Blinkenlights(UIBase, CursesUtil):
def mainException(self):
UIBase.mainException(self)
def getpass(self, accountname, config, errmsg=None):
def getpass(self, username, config, errmsg=None):
# disable the hotkeys inputhandler
self.inputhandler.input_acquire()
@ -558,8 +567,8 @@ class Blinkenlights(UIBase, CursesUtil):
try:
#s.gettf().setcolor('white')
self.warn(" *** Input Required")
self.warn(" *** Please enter password for account %s: " % \
accountname)
self.warn(" *** Please enter password for user '%s': " % \
username)
self.logwin.refresh()
password = self.logwin.getstr()
finally:

View File

@ -1,4 +1,4 @@
# Copyright (C) 2007-2016 John Goerzen & contributors.
# Copyright (C) 2007-2018 John Goerzen & contributors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -182,15 +182,15 @@ class MachineUI(UIBase):
return 0
def getpass(s, accountname, config, errmsg=None):
def getpass(s, username, config, errmsg=None):
if errmsg:
s._printData(s.logger.warning,
'getpasserror', "%s\n%s"% (accountname, errmsg),
'getpasserror', "%s\n%s"% (username, errmsg),
False)
s._log_con_handler.acquire() # lock the console output
try:
s._printData(s.logger.info, 'getpass', accountname)
s._printData(s.logger.info, 'getpass', username)
return (sys.stdin.readline()[:-1])
finally:
s._log_con_handler.release()

View File

@ -1,5 +1,5 @@
# TTY UI
# Copyright (C) 2002-2015 John Goerzen & contributors
# Copyright (C) 2002-2018 John Goerzen & contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -76,14 +76,14 @@ class TTYUI(UIBase):
return sys.stdout.isatty() and sys.stdin.isatty()
def getpass(self, accountname, config, errmsg=None):
def getpass(self, username, config, errmsg=None):
"""TTYUI backend is capable of querying the password."""
if errmsg:
self.warn("%s: %s"% (accountname, errmsg))
self.warn("%s: %s"% (username, errmsg))
self._log_con_handler.acquire() # lock the console output
try:
return getpass("Enter password for account '%s': " % accountname)
return getpass("Enter password for user '%s': " % username)
finally:
self._log_con_handler.release()

View File

@ -1,5 +1,5 @@
# UI base class
# Copyright (C) 2002-2016 John Goerzen & contributors.
# Copyright (C) 2002-2018 John Goerzen & contributors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -96,8 +96,13 @@ class UIBase(object):
def setup_sysloghandler(self):
"""Backend specific syslog handler."""
if sys.platform == 'darwin':
address = '/var/run/syslog'
else:
address = '/dev/log'
# create syslog handler
ch = logging.handlers.SysLogHandler('/dev/log')
ch = logging.handlers.SysLogHandler(address)
# create formatter and add it to the handlers
self.formatter = logging.Formatter("%(message)s")
ch.setFormatter(self.formatter)
@ -257,7 +262,7 @@ class UIBase(object):
################################################## INPUT
def getpass(self, accountname, config, errmsg = None):
def getpass(self, username, config, errmsg = None):
raise NotImplementedError("Prompting for a password is not supported"
" in this UI backend.")
@ -396,9 +401,9 @@ class UIBase(object):
def copyingmessage(self, uid, num, num_to_copy, src, destfolder):
"""Output a log line stating which message we copy."""
self.logger.info("Copy message UID %s (%d/%d) %s:%s -> %s"% (
self.logger.info("Copy message UID %s (%d/%d) %s:%s -> %s:%s"% (
uid, num, num_to_copy, src.repository, src,
destfolder.repository))
destfolder.repository, destfolder))
def deletingmessages(self, uidlist, destlist):
ds = self.folderlist(destlist)

View File

@ -24,7 +24,7 @@ UI_LIST = {'ttyui': TTY.TTYUI,
'syslog': Noninteractive.Syslog,
'machineui': Machine.MachineUI}
#add Blinkenlights UI if it imports correctly (curses installed)
# add Blinkenlights UI if it imports correctly (curses installed)
try:
from offlineimap.ui import Curses
UI_LIST['blinkenlights'] = Curses.Blinkenlights

View File

@ -1,4 +1,4 @@
# Copyright 2014-2006 Eygene A. Ryabinkin & contributors.
# Copyright 2006-2018 Eygene A. Ryabinkin & contributors.
#
# Module that supports distribution-specific functions.
@ -18,6 +18,8 @@ __DEF_OS_LOCATIONS = {
'darwin': [
# MacPorts, port curl-ca-bundle
'/opt/local/share/curl/curl-ca-bundle.crt',
# homebrew, package openssl
'/usr/local/etc/openssl/cert.pem',
],
'linux-ubuntu': '/etc/ssl/certs/ca-certificates.crt',
'linux-debian': '/etc/ssl/certs/ca-certificates.crt',
@ -26,6 +28,7 @@ __DEF_OS_LOCATIONS = {
'linux-redhat': '/etc/pki/tls/certs/ca-bundle.crt',
'linux-suse': '/etc/ssl/ca-bundle.pem',
'linux-opensuse': '/etc/ssl/ca-bundle.pem',
'linux-arch': '/etc/ssl/certs/ca-certificates.crt',
}
@ -45,7 +48,9 @@ def get_os_name():
if OS.startswith('linux'):
DISTRO = platform.linux_distribution()[0]
if DISTRO:
OS = OS + "-%s" % DISTRO.split()[0].lower()
OS = OS + "-%s" % DISTRO.split()[0].lower()
if os.path.exists('/etc/arch-release'):
OS = "linux-arch"
return OS

13
offlineimap/version.py Normal file
View File

@ -0,0 +1,13 @@
__productname__ = 'OfflineIMAP'
# Expecting trailing "-rcN" or "" for stable releases.
__version__ = "7.3.4"
__copyright__ = "Copyright 2002-2021 John Goerzen & contributors"
__author__ = "John Goerzen"
__author_email__= "offlineimap-project@lists.alioth.debian.org"
__description__ = "Disconnected Universal IMAP Mail Synchronization/Reader Support"
__license__ = "Licensed under the GNU GPL v2 or any later version (with an OpenSSL exception)"
__bigcopyright__ = """%(__productname__)s %(__version__)s
%(__license__)s""" % locals()
__homepage__ = "http://www.offlineimap.org"
banner = __bigcopyright__

View File

@ -25,7 +25,7 @@ this virtual_imaplib2 or we might go into troubles.
DESC = None
_SUPPORTED_RELEASE = 2
_SUPPORTED_REVISION = 55
_SUPPORTED_REVISION = 57
try:
# Try any imaplib2 in PYTHONPATH first. This allows both maintainers of

View File

@ -1,2 +1,5 @@
# Requirements
six
# Minimal requirements defined in setup.py
-e .
# Extra "optional" requirements
gssapi[kerberos]
portalocker[cygwin]

View File

@ -5,7 +5,7 @@
# IMAP synchronization
# Module: installer
# COPYRIGHT #
# Copyright (C) 2002 - 2006 John Goerzen
# Copyright (C) 2002 - 2020 John Goerzen & contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -19,13 +19,17 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
from distutils.core import setup, Command
import offlineimap
import logging
from test.OLItest import TextTestRunner, TestLoader, OLITestLib
from os import path
here = path.abspath(path.dirname(__file__))
# load __version__, __doc__, __author_, ...
exec(open(path.join(here, 'offlineimap', 'version.py')).read())
class TestCommand(Command):
"""runs the OLI testsuite"""
@ -42,6 +46,11 @@ class TestCommand(Command):
pass
def run(self):
# Import the test classes here instead of at the begin of the module
# to avoid an implicit dependency of the 'offlineimap' module
# in the setup.py (which may run *before* offlineimap is installed)
from test.OLItest import TextTestRunner, TestLoader, OLITestLib
logging.basicConfig(format='%(message)s')
# set credentials and OfflineImap command to be executed:
OLITestLib(cred_file='./test/credentials.conf', cmd='./offlineimap.py')
@ -49,19 +58,25 @@ class TestCommand(Command):
#TODO: failfast does not seem to exist in python2.6?
TextTestRunner(verbosity=2,failfast=True).run(suite)
reqs = [
'six',
'rfc6555'
]
setup(name = "offlineimap",
version = offlineimap.__version__,
description = offlineimap.__description__,
author = offlineimap.__author__,
author_email = offlineimap.__author_email__,
url = offlineimap.__homepage__,
version = __version__,
description = __description__,
long_description = __description__,
author = __author__,
author_email = __author_email__,
url = __homepage__,
packages = ['offlineimap', 'offlineimap.folder',
'offlineimap.repository', 'offlineimap.ui',
'offlineimap.utils'],
scripts = ['bin/offlineimap'],
license = offlineimap.__copyright__ + \
license = __copyright__ + \
", Licensed under the GPL version 2",
cmdclass = { 'test': TestCommand}
cmdclass = { 'test': TestCommand},
install_requires = reqs
)

19
snapcraft.yaml Normal file
View File

@ -0,0 +1,19 @@
name: offlineimap
version: git
summary: OfflineIMAP
description: |
OfflineIMAP is software that downloads your email mailbox(es) as local
Maildirs. OfflineIMAP will synchronize both sides via IMAP.
grade: devel
confinement: devmode
apps:
offlineimap:
command: bin/offlineimap
parts:
offlineimap:
plugin: python
python-version: python2
source: .

5
test/.gitignore vendored
View File

@ -1,2 +1,5 @@
credentials.conf
tmp_*
tmp_*
*.pyc
OLItest/*.pyc
tests/*.pyc

View File

@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import imaplib
import offlineimap.virtual_imaplib2 as imaplib
import unittest
import logging
import os

2
tests/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.pyc

82
tests/create_conf_file.py Executable file
View File

@ -0,0 +1,82 @@
#!/bin/env python
# Copyright 2018 Espace LLC/espacenetworks.com. Written by @chris001.
# This must be run from the main directory of the offlineimap project.
# Typically this script will be run by Travis to create the config files needed for running the automated tests.
# python ./tests/create_conf_file.py
# Input: Seven shell environment variables.
# Output: it writes the config settings to "filename" (./oli-travis.conf) and "additionalfilename" (./test/credentials.conf).
# "filename" is used by normal run of ./offlineimap -c ./oli-travis.conf , "additionalfilename" is used by "pytest".
# They are the same conf file, copie to two different locations for convenience.
import os
import shutil
try:
import ConfigParser
Config = ConfigParser.ConfigParser()
except ImportError:
import configparser
Config = configparser.ConfigParser()
filename = "./oli-travis.conf"
additionalfilename = "./test/credentials.conf" # for the 'pytest' which automatically finds and runs the unittests.
#TODO: detect OS we running on now, and set sslcacertfile location accordingly.
sslcacertfile = "/etc/pki/tls/cert.pem" # CentOS 7
sslcacertfile = "" # TODO: https://gist.github.com/1stvamp/2158128 Current Mac OSX now must download the cacertfile.
sslcacertfile = "/etc/ssl/certs/ca-certificates.crt" # Ubuntu Trusty 14.04 (Travis linux test container 2018.)
if os.environ["TRAVIS_OS_NAME"] == "osx":
sslcacertfile = os.environ["OSX_BREW_SSLCACERTFILE"]
# lets create that config file.
cfgfile = open(filename,'w')
# add the settings to the structure of the file, and lets write it out.
sect = 'general'
Config.add_section(sect)
Config.set(sect,'accounts','Test')
Config.set(sect,'maxsyncaccounts', '1')
sect = 'Account Test'
Config.add_section(sect)
Config.set(sect,'localrepository','IMAP') # Outlook.
Config.set(sect,'remoterepository', 'Gmail')
### "Repository IMAP" is hardcoded in test/OLItest/TestRunner.py it should dynamically get the Repository names but it doesn't.
sect = 'Repository IMAP' # Outlook.
Config.add_section(sect)
Config.set(sect,'type','IMAP')
Config.set(sect,'remotehost', 'imap-mail.outlook.com')
Config.set(sect,'remoteport', '993')
Config.set(sect,'auth_mechanisms', os.environ["OUTLOOK_AUTH"])
Config.set(sect,'ssl', 'True')
#Config.set(sect,'tls_level', 'tls_compat') #Default is 'tls_compat'.
#Config.set(sect,'ssl_version', 'tls1_2') # Leave this unset. Will auto select between tls1_1 and tls1_2 for tls_secure.
Config.set(sect,'sslcacertfile', sslcacertfile)
Config.set(sect,'remoteuser', os.environ["secure_outlook_email_address"])
Config.set(sect,'remotepass', os.environ["secure_outlook_email_pw"])
Config.set(sect,'createfolders', 'True')
Config.set(sect,'folderfilter', 'lambda f: f not in ["Inbox", "[Gmail]/All Mail"]') #Capitalization of Inbox INBOX was causing runtime failure.
#Config.set(sect,'folderfilter', 'lambda f: f not in ["[Gmail]/All Mail"]')
### "Repository Gmail" is also hardcoded into test/OLItest/TestRunner.py
sect = 'Repository Gmail'
Config.add_section(sect)
Config.set(sect,'type', 'Gmail')
Config.set(sect,'remoteport', '993')
Config.set(sect,'auth_mechanisms', os.environ["GMAIL_AUTH"])
Config.set(sect,'oauth2_client_id', os.environ["secure_gmail_oauth2_client_id"])
Config.set(sect,'oauth2_client_secret', os.environ["secure_gmail_oauth2_client_secret"])
Config.set(sect,'oauth2_refresh_token', os.environ["secure_gmail_oauth2_refresh_token"])
Config.set(sect,'remoteuser', os.environ["secure_gmail_email_address"])
Config.set(sect,'ssl', 'True')
#Config.set(sect,'tls_level', 'tls_compat')
#Config.set(sect,'ssl_version', 'tls1_2')
Config.set(sect,'sslcacertfile', sslcacertfile)
Config.set(sect,'createfolders', 'True')
Config.set(sect,'folderfilter', 'lambda f: f not in ["INBOX", "[Gmail]/All Mail"]')
Config.write(cfgfile)
cfgfile.close()
shutil.copy(filename, additionalfilename)

4
tests/requirements.txt Normal file
View File

@ -0,0 +1,4 @@
pytest
pytest-cov
coverage
codecov