OfflineIMAP

出自 ArchWiki

OfflineIMAP 是一个 Python 实用程序,用于同步来自 IMAP 服务器的邮件。它不适用于 POP3 协议或 mbox,通常与 MUA(例如 Mutt)配对使用。

安装

安装 offlineimap 软件包。

配置

Offlineimap 发行版附带两个默认配置文件,都位于 /usr/share/offlineimap/offlineimap.conf 包含所有设置并有详细文档。或者,offlineimap.conf.minimal 没有注释,仅包含少量设置;请参阅:#Minimal

将其中一个默认配置文件复制到 ~/.offlineimaprc$XDG_CONFIG_HOME/offlineimap/config

注意: 在同一行上的选项/值后写入注释是无效的语法,因此请注意将注释放在单独的行上。

极简

以下文件是 offlineimap.conf.minimal 的注释版本。

~/.offlineimaprc
[general]
# List of accounts to be synced, separated by a comma.
accounts = main

[Account main]
# Identifier for the local repository; e.g. the maildir to be synced via IMAP.
localrepository = main-local
# Identifier for the remote repository; i.e. the actual IMAP, usually non-local.
remoterepository = main-remote

[Repository main-local]
# OfflineIMAP supports Maildir, GmailMaildir, and IMAP for local repositories.
type = Maildir
# Where should the mail be placed?
localfolders = ~/mail

[Repository main-remote]
# Remote repos can be IMAP or Gmail, the latter being a preconfigured IMAP.
# SSL and STARTTLS are enabled by default.
type = IMAP
remotehost = host.domain.tld
remoteuser = username
# Necessary for SSL connections, if using offlineimap version > 6.5.4
sslcacertfile = /etc/ssl/certs/ca-certificates.crt

选择性文件夹同步

要仅同步某些文件夹,您可以在 ~/.offlineimaprc 中帐户的 remote 部分使用 folderfilter。例如,以下配置将仅同步 InboxSent 文件夹

~/.offlineimaprc
[Repository main-remote]
# Synchronize only the folders Inbox and Sent:
folderfilter = lambda foldername: foldername in ["Inbox", "Sent"]
...

有关更多选项,请参阅 官方文档

自定义端口

某些 IMAP 服务器可能要求您连接到自定义端口,而不是默认的 993 端口。为此,请在 remote 部分的 ~/.offlineimaprc 中添加 remoteport 选项

~/.offlineimaprc
[Repository main-remote]
remoteport=1234

用法

在运行 offlineimap 之前,创建分配给本地存储库的任何父目录

$ mkdir ~/mail

现在,运行程序

$ offlineimap

邮件帐户现在将被同步。如果出现任何问题,请仔细查看错误消息。OfflineIMAP 通常会非常详细地说明问题;部分原因是开发人员懒得从最终产品中删除回溯信息。

技巧与窍门

在后台运行 offlineimap

本文或章节需要语言、wiki 语法或风格改进。请参阅 Help:Style 以获取参考。

原因: 也许不要使用带有注释的配置文件作为文档,而是在文本中描述它们?(在 Talk:OfflineIMAP 中讨论)

大多数其他邮件传输代理都假设用户将该工具用作 守护程序,默认情况下使程序定期同步。在 offlineimap 中,有一些设置可以控制后台任务。

令人困惑的是,它们分散在整个配置文件中

~/.offlineimaprc
# In the general section
[general]
# Controls how many accounts may be synced simultaneously
maxsyncaccounts = 1

# In the account identifier
[Account main]
# Minutes between syncs
autorefresh = 0.5
# Quick-syncs do not update if the only changes were to IMAP flags.
# autorefresh=0.5 together with quick=10 yields
# 10 quick refreshes between each full refresh, with 0.5 minutes between every 
# refresh, regardless of type.
quick = 10

# In the remote repository identifier
[Repository main-remote]
# Instead of closing the connection once a sync is complete, offlineimap will
# send empty data to the server to hold the connection open. A value of 60
# attempts to hold the connection for a minute between syncs (both quick and
# autorefresh).This setting has no effect if autorefresh and holdconnectionopen
# are not both set.
keepalive = 60
# OfflineIMAP normally closes IMAP server connections between refreshes if
# the global option autorefresh is specified.  If you wish it to keep the
# connection open, set this to true. This setting has no effect if autorefresh
# is not set.
holdconnectionopen = yes

要在登录时自动启动守护程序,请使用 --user 标志启动/启用 systemd/User 服务 offlineimap.service

如果您配置了多个帐户,建议使用 offlineimap@.service 而不是增加 maxsyncaccounts 参数[1]。只需 启动/启用 offlineimap@youraccountname.service 即可。

systemd 计时器

或者,可以使用 systemd-user 计时器 完全管理 OfflineIMAP,使用 --user 标志启动/启用 offlineimap-oneshot.timer

此计时器默认每 15 分钟运行 OfflineIMAP 一次。可以通过创建 drop-in 片段 轻松更改此设置。例如,以下内容将计时器修改为每 5 分钟检查一次

~/.config/systemd/user/offlineimap-oneshot.timer.d/timer.conf
[Timer]
OnUnitInactiveSec=5m

本文或章节的事实准确性存在争议。

原因: 这可能会导致 OfflineIMAP 本地数据库中的不一致。(在 Talk:OfflineIMAP 中讨论)

为了更强大的解决方案,可以设置一个 watchdog,在 OfflineIMAP 冻结的情况下将其杀死

~/.config/systemd/user/offlineimap-oneshot.service.d/service.conf
[Service]
WatchdogSec=300

为 mutt 自动生成邮箱

Mutt 不能简单地指向 IMAP 或 maildir 目录,并期望它猜测哪些子目录恰好是邮箱,但 offlineimap 可以生成一个 muttrc 片段,其中包含它同步的邮箱。

~/.offlineimaprc
[mbnames]
enabled = yes
filename = ~/.mutt/mailboxes
header = "mailboxes "
peritem = "+%(accountname)s/%(foldername)s"
sep = " "
footer = "\n"

然后将以下行添加到 ~/.mutt/muttrc

~/.mutt/muttrc
# IMAP: offlineimap
set folder = "~/mail"
source ~/.mutt/mailboxes
set spoolfile = "+account/INBOX"
set record = "+account/Sent\ Items"
set postponed = "+account/Drafts"

account 是您在 ~/.offlineimaprc 中为您的 IMAP 帐户指定的名称。

Gmail 配置

此远程存储库专门为 Gmail 支持而配置,将文件夹名称中的大写字母替换为小写字母,以及其他一些小的添加。请记住,此配置不会同步所有邮件文件夹,因为它通常是不必要的,跳过它可以节省带宽成本

~/.offlineimaprc
[Repository gmail-remote]
type = Gmail
remoteuser = user@gmail.com
remotepass = password
nametrans = lambda foldername: re.sub ('^\[gmail\]', 'bak',
                               re.sub ('sent_mail', 'sent',
                               re.sub ('starred', 'flagged',
                               re.sub (' ', '_', foldername.lower()))))
folderfilter = lambda foldername: foldername not in ['[Gmail]/All Mail']
# Necessary as of OfflineIMAP 6.5.4
sslcacertfile = /etc/ssl/certs/ca-certificates.crt
# Necessary to work around https://github.com/OfflineIMAP/offlineimap/issues/573 (versions 7.0.12, 7.2.1)
ssl_version = tls1_2
注意
  • 如果您已将 Gmail 设置为另一种语言,则文件夹名称也可能显示为已翻译,例如“verzonden_berichten”而不是“sent_mail”。
  • 在 6.3.5 版本之后,offlineimap 还会创建远程文件夹以匹配您的本地文件夹。因此,您可能还需要为本地存储库设置 nametrans 规则,以反转此 nametrans 规则的效果。如果您不想制定反向 nametrans 规则,则可以通过在远程配置中放置以下内容来禁用远程文件夹创建:createfolders = False
  • 截至 2012 年 10 月 1 日,gmail SSL 证书指纹并不总是相同的。这阻止了使用 cert_fingerprint,并使 sslcacertfile 成为 SSL 验证的更好解决方案(请参阅 #SSL 指纹不匹配)。

通过 oama 获取 OAuth2 访问令牌

oama (oama-binAUR) 是一个实用程序,为 IMAP/SMTP 客户端提供 OAuth2 凭据的续订功能和授权。

OfflineIMAP 可以从其配置中调用 Python 代码。因此,在开头的 [general] 部分中,添加以下行

~/.offlineimaprc
[general]
pythonfile = ~/.offlineimap.py

在 Python 文件中,添加以下代码以通过 oama 检索 OAuth2 访问令牌

~/.offlineimap.py
import subprocess

def get_token(email_address):
    return subprocess.run(["oama", "access", email_address], capture_output=True, text=True).stdout

现在回到配置文件,在 Gmail 帐户的存储库部分,添加以下内容

~/.offlineimaprc
auth_mechanisms = XOAUTH2
oauth2_client_id = YOUR_OAUTH2_CLIENT_ID
oauth2_client_secret = YOUR_OAUTH2_CLIENT_SECRET
oauth2_request_url = https://127.0.0.1/o/oauth2/token
oauth2_access_token_eval = get_token("YOUR_EMAIL_ADDRESS_FOR_THIS_ACCOUNT")

密码管理

.netrc

将以下行添加到您的 ~/.netrc

machine hostname.tld
    login [your username]
    password [your password]

不要忘记给文件适当的权限,如 600 或 700

$ chmod 600 ~/.netrc
注意:.netrc 文件中存储了多个帐户时,OfflineIMAP 不知道 如何检索密码。

使用 GPG

GNU Privacy Guard 可用于将密码存储在加密文件中。首先设置 GnuPG,然后按照本节中的步骤操作。假设您可以一直使用您的 GPG 私钥 而无需输入密码

首先在纯文本文件中键入电子邮件帐户的密码。在位于 tmpfs 上的具有 700 权限的安全目录中执行此操作,以避免将未加密的密码写入磁盘。然后使用 GnuPG 加密文件,并将自己设置为接收者。

删除不再需要的纯文本文件。将加密文件移动到最终位置,例如 ~/.offlineimappass.gpg

现在创建一个 python 函数,该函数将解密密码

~/.offlineimap.py
#! /usr/bin/env python
from subprocess import check_output

def get_pass():
    return check_output("gpg -dq ~/.offlineimappass.gpg", shell=True).rstrip(b"\n")

~/.offlineimaprc 加载此文件并指定定义的函数

~/.offlineimaprc
[general]
# Path to file with arbitrary Python code to be loaded
pythonfile = ~/.offlineimap.py
...

[Repository example]
# Decrypt and read the encrypted password
remotepasseval = get_pass()
...

使用 pass

pass 是一个基于 GPG 的简单命令行密码管理器。

首先为您的电子邮件帐户创建密码

$ pass insert Mail/account

现在创建一个 python 函数,该函数将解密密码

~/.offlineimap.py
#! /usr/bin/env python
from subprocess import check_output

def get_pass(account):
    return check_output("pass Mail/" + account, shell=True).splitlines()[0]

这是多帐户设置的示例。您可以自定义 pass 的参数,如先前定义的那样。

~/.offlineimaprc 加载此文件并指定定义的函数

~/.offlineimaprc
[general]
# Path to file with arbitrary Python code to be loaded
pythonfile = ~/.offlineimap.py
...

[Repository Gmail]
# Decrypt and read the encrypted password
remotepasseval = get_pass("Gmail")
...

Gnome 密钥环

在远程存储库的配置中,remoteusereval/remotepasseval 字段可以设置为自定义 python 代码,该代码评估为用户名/密码。该代码可以是调用在 'pythonfile' 配置字段指向的 Python 脚本中定义的函数。根据以下小节创建 ~/.offlineimap.py 并在配置中使用它

[general]
pythonfile = ~/.offlineimap.py

[Repository examplerepo]
type = IMAP
remotehost = mail.example.com
remoteusereval = get_username("examplerepo")
remotepasseval = get_password("examplerepo")
gkgetsecret.py

本文或章节需要语言、wiki 语法或风格改进。请参阅 Help:Style 以获取参考。

原因: 由于这现在是一个单独的小节,因此应将其与上面的介绍合并。(在 Talk:OfflineIMAP 中讨论)

确保已安装 gnome-keyringpython2AURpython2-gobjectAURlibsecret。然后使用以下内容创建 ~/.offlineimap.pygkgetsecret.py,并在 ~/.offlineimaprc 中设置 pythonfile = ~/.offlineimap.py,如上所述。

如果您使用 seahorse 创建了密码,则可以从其描述中检索它。例如,存储在 gnome-keyring 中,描述为 _Password for me@myworkemail.com_ 的存储库 _Work_ 的密码可以通过将以下内容添加到 ~/.offlineimaprc 来检索

[Repository Work]
...
remotepasseval = get_pw_from_desc("Password for me@myworkemail.com")

对于您希望也存储用户名的配置,最好使用 secret-tool 创建密码,因为这可以用于设置属性,例如用户名和存储库名称。考虑使用以下命令创建的密码

$ secret-tool store --label "Password for Work Email" username me@myworkemail.com repo Work

可以通过将以下内容添加到 ~/.offlineimaprc 来检索此帐户的用户名和密码

[Repository Work]
...
remoteusereval = get_val_from_attrs("username", "repo", "Work")
remotepasseval = get_pw_from_attrs("repo", "Work")

python-keyring

有一个通用解决方案,应该适用于任何密钥环。安装 python-keyring,然后更改您的 ~/.offlineimaprc 以说类似以下内容

[general]
pythonfile = /home/user/offlineimap.py
...
[Repository RemoteEmail]
remoteuser = username@host.net
remotepasseval = keyring.get_password("host","username")
...

并在 ~/offlineimap.py 中的某个位置添加 import keyring

现在您要做的就是设置您的密码,可以使用 python 脚本,如下所示

$ python
>>> import keyring
>>> keyring.set_password("host","username", "MYPASSWORD")

或者您可以使用 python-keyring 软件包提供的 keyring 命令

$ keyring --help
$ keyring set host username
Password for 'username' in 'host':
$ keyring get host username
password

它将从您的 (kwallet/gnome-) 密钥环中获取密码,而无需将其保留为纯文本或每次都输入。

Emacs EasyPG

请参阅 https://www.emacswiki.org/emacs/OfflineIMAP#toc2

KeePassXC 与 Freedesktop.org secret-service

安装 libsecret,在 KeepassXC 设置中启用 Freedesktop.org secret-service 集成,在 _数据库设置 > Secret Service 集成_ 中公开条目,并在 _编辑条目 > 高级_ 设置中添加带有 account@name.org 的附加属性 _Title_。现在命令 secret-tool lookup Title account@name.org 应该在控制台中打印密码。接下来创建一个 python 脚本

 ~/.script.py
#! /usr/bin/env python
import os
from subprocess import check_output

def get_pass(account):
    return check_output("secret-tool lookup Title " + account, shell=True).splitlines()[0].decode("UTF-8")

一个等效的脚本依赖于 python-secretstorage,它是

 ~/.script.py
#! /usr/bin/env python
import secretstorage
from contextlib import closing
def get_pass(title):
    with closing(secretstorage.dbus_init()) as conn:
        assert(secretstorage.check_service_availability(conn))
        collection = secretstorage.get_default_collection(conn)
        if collection.is_locked():
            collection.unlock()
        matches = collection.search_items({"Title": title})
        entry = next(matches)
        if entry.is_locked():
            entry.unlock()
        return(entry.get_secret())

~/.offlineimaprc 加载此文件并指定定义的函数

~/.offlineimaprc
[general]
# Path to file with arbitrary Python code to be loaded
pythonfile = ~/.script.py
...
[Repository Gmail]
# Decrypt and read the encrypted password
remotepasseval = get_pass("account@name.org")
...

故障排除

覆盖 UI 和自动刷新设置

为了方便故障排除,有时可以启动 offlineimap,使用更详细的 UI,不进行后台同步,甚至使用调试级别

$ offlineimap [ -o ] [ -d <debug_type> ] [ -u <ui> ]
-o
禁用自动刷新、保持连接等。
-d <debug_type>
其中 <debug_type>imapmaildirthread 之一。调试 imap 和 maildir 是目前为止最有用的。
-u <ui>
其中 <ui>CURSES.BLINKENLIGHTSTTY.TTYUINONINTERACTIVE.BASICNONINTERACTIVE.QUIETMACHINE.MACHINEUI 之一。TTY.TTYUI 对于调试目的来说已经足够了。
注意: 更新的版本使用以下 <ui>: blinkenlightsttyuibasicquietmachineui

文件夹无法创建

在 6.5.3 版本中,offlineimap 获得了在远程仓库中创建文件夹的能力,如此处所述。

当在远程仓库上使用 nametrans 时,这可能会导致以下形式的错误

ERROR: Creating folder bar on repository foo-remote
  Folder 'bar'[foo-remote] could not be created. Server responded: ('NO', ['[ALREADYEXISTS] Duplicate folder name bar (Failure)'])

解决方案是为本地仓库提供一个反向的 nametrans lambda,例如

~/.offlineimaprc
[Repository foo-local]
nametrans = lambda foldername: foldername.replace('bar', 'BAR')

[Repository foo-remote]
nametrans = lambda foldername: foldername.replace('BAR', 'bar')
  • 为了找出正确的反向映射,offlineimap --info 的输出应该有所帮助。
  • 更新映射后,可能需要删除受影响帐户的 $HOME/.offlineimap/ 下的所有文件夹。

SSL 指纹不匹配

ERROR: Server SSL fingerprint 'keykeykey' for hostname 'example.com' does not match configured fingerprint. Please verify and set 'cert_fingerprint' accordingly if not set yet.

要解决此问题,请添加到 ~/.offlineimaprc (在与 ssl = yes 相同的节中) 以下内容之一

  • 或者添加 cert_fingerprint,以及远程服务器的证书指纹。这将检查远程服务器证书是否与给定的指纹匹配。
    cert_fingerprint = keykeykey
  • 或者添加 sslcacertfile,以及系统 CA 证书文件的路径。需要安装 ca-certificates
    sslcacertfile = /etc/ssl/certs/ca-certificates.crt

复制消息,连接已关闭

ERROR: Copying message -2 [acc: email] connection closed
Folder sent [acc: email]: ERROR: while syncing sent [account email] connection closed

造成这种情况的原因可能是本地和服务器上都创建了相同的消息。如果您的电子邮件提供商自动将已发送邮件保存到与本地客户端相同的文件夹,则会发生这种情况。如果遇到这种情况,请在本地客户端中禁用保存已发送邮件的功能。

另请参阅