OfflineIMAP
OfflineIMAP 是一个用于从 IMAP 服务器同步邮件的 Python 工具。它不支持 POP3 协议或 mbox 格式,通常与 Mutt 等邮件用户代理(MUA)配合使用。
安装
安装 offlineimap 软件包。
配置
Offlineimap 随附两个默认配置文件,均位于 /usr/share/offlineimap/。offlineimap.conf 包含所有设置并有详尽的注释。或者,offlineimap.conf.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。例如,以下配置将仅同步 Inbox 和 Sent 文件夹。
~/.offlineimaprc
[Repository main-remote] # Synchronize only the folders Inbox and Sent: folderfilter = lambda foldername: foldername in ["Inbox", "Sent"] ...
有关更多选项,请参阅官方文档。
自定义端口
某些 IMAP 服务器可能要求您连接到非默认 993 端口的自定义端口。为此,请在 ~/.offlineimaprc 的 remote 部分添加 remoteport 选项。
~/.offlineimaprc
[Repository main-remote] remoteport=1234
用法
运行 offlineimap 之前,请创建分配给本地仓库的任何父目录。
$ mkdir ~/mail
现在,运行程序。
$ offlineimap
邮件账户现已同步。如果出现问题,请仔细查看错误消息。OfflineIMAP 通常会详细报告问题;部分原因是开发者并未从最终产品中移除回溯信息。
技巧与提示
在后台运行 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 timer
或者,可以使用 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 卡死时将其杀死。
~/.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 支持而配置,将文件夹名称的大小写进行转换,并包含其他一些微小的修改。请注意,此配置不同步 All Mail 文件夹,因为它通常是不必要的,跳过它可以节省带宽费用。
~/.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 设置为其他语言,文件夹名称也可能是翻译过的,例如“sent_mail”可能会显示为“verzonden_berichten”。
- 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://#/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 文件中存储多个账户时检索密码。使用 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
确保安装了 gnome-keyring、python2AUR、python2-gobjectAUR 和 libsecret。然后创建包含以下内容的 ~/.offlineimap.py:gkgetsecret.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
带有 Freedesktop.org secret-service 的 KeePassXC
安装 libsecret,在 KeepassXC 设置中启用 Freedesktop.org secret-service 集成,在 数据库设置 > Secret Service 集成 中暴露条目,并在 编辑条目 > 高级 设置中添加属性 Title 为 account@name.org。现在,命令 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 和自动刷新设置
为了排查故障,有时方便以更详细的 UI、无后台同步甚至调试级别启动 offlineimap:
$ offlineimap [ -o ] [ -d <debug_type> ] [ -u <ui> ]
- -o
- 禁用自动刷新、保持连接(keepalive)等。
- -d <debug_type>
- 其中 <debug_type> 是
imap、maildir或thread之一。调试 imap 和 maildir 是最有用的。
- -u <ui>
- 其中 <ui> 是
CURSES.BLINKENLIGHTS、TTY.TTYUI、NONINTERACTIVE.BASIC、NONINTERACTIVE.QUIET或MACHINE.MACHINEUI之一。TTY.TTYUI 对于调试目的已足够。
blinkenlights、ttyui、basic、quiet 或 machineui。文件夹无法创建
在 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。这会验证远程 SSL 证书链是否符合文件中的认证机构。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
其原因是本地和服务器上同时创建了同一封邮件。如果您的邮件提供商自动将已发送邮件保存到与本地客户端相同的文件夹中,就会发生这种情况。如果您遇到此问题,请在本地客户端中禁用已发送邮件的保存。
参见
- 官方 OfflineIMAP 邮件列表
- Mutt + Gmail + Offlineimap - brisbin 使用 cron 保持 offlineimap 同步的简单 gmail/mutt 设置大纲。