Mail 環境の設定


Related Index Debian

始めに

メールに関するアレコレ. ネットワーク接続を意識したくないので, 送受信用のサーバを手元に置いて MUA はなるべくここを叩くようにしている.

送信設定: Exim4

smarthost での送信だけなら Exim4 が軽量で楽。 他にも幾つか選択肢はある。気になるなら Debian Wiki: DefaultMTA あたり参照のこと。 欲しい機能は

  • 上流の MTA への smarthost 送信。
  • offline 時の queue
  • localhost:25 での待受

なので、軽いしとりあえず Exim4 をそのまま使っている。

$ sudo -s
# dpkg-reconfigure -plow exim4-config
  メール設定の一般的なタイプ: 2. スマートホストでメール送信; SMTP または fetchmail で受信する
  システムメール名: localhost
  入力側 SMTP 接続をリスンする IP アドレス: 127.0.0.1
  メールを受け取るその他の宛先: [空白]
  メールをリレーするマシン: [空白]
  送出スマートホストの IP アドレスまたはホスト名: スマートホスト用の送信サーバ::587
  送出するメールでローカルメール名を隠しますか? no
  DNS クエリの数を最小限に留めますか (ダイヤルオンデマンド)? no
  ローカルメールの配送方式: 1. /var/mail/ 内の mbox 形式
  設定を小さなファイルに分割しますか?: no
  root と postmaster のメール受信者: 適当に設定

そのあと, /etc/exim4/passwd.client 内で, スマートホストの設定

スマートホスト送信サーバ:[アカウント]:[パスワード]

そして必要なら /etc/exim4/email-addresses に置換するリストを適当に記述. 最低限 root と ユーザ宛(uid=1000) のメール送信先は設定しておく.

$ sudo -s
# upate-exim4.conf
# sudo /etc/init.d/exim4 restart

あとは適当にテストとかしてみると良い.

(2020/04/27) このままだと message-id が "乱数列@hostname" になるので /etc/exim4/exim4.conf.localmacros

message_id_header_domain = ドメイン名
message_id_header_text   = ホスト名

を追加して update-exim4.conf を走らせておく.

ローカル配送先を適宜設定したい場合には

MAILDIR_HOME_MAILDIR_LOCATION = $home/Maildir/INBOX

などとしておく.

送信設定: msmtp

Wanderlust からメールを送る時には, msmtp を使っている.

sendmail コマンド互換かつ enverope-from に応じて送信に利用する SMTP サーバを切り替えられる, というのが良い. 本当は Gmail に SMTP サーバを登録しておいて, exim4 の smarthost で Gmail に丸投げしたかったのだけれど, Gmail のメールサーバは Reply-To を上書きしたりして行儀が良くない.

msmtp 自体の設定はいたって普通だが, queue が面倒? 結局 Emacs からは sendmail として

#!/bin/sh
ARGS="$*"
msmtp-enqueue.sh $ARGS 1>/dev/null
if [ x"$(LANG=C nmcli n connectivity)" = x"full" ]; then
    msmtp-runqueue.sh 2>&1 1>/dev/null
fi
exit 0

を叩くことで, ネットワーク接続がある場合には即座に送信, 無い場合には queuing, という風に動作を切り替えている.

あとは NetworkManager の dispatcher として msmtp-runqueue を走らせれば良い.

受信

受信/閲覧は

  • isync で、リモートの IMAP サーバのメールをローカルに Maildir 形式で保存
  • ローカルで dovecot-imapd を起動してメールを閲覧

としている.

最近の MUA は多かれ少なかれ IMAP 接続でも手元にメールを持ってくるので, このメールの重複をなんとかしたいのだけれども, 例えば Wanderlust なんかで Maildir を直接見にいくと Emacs の反応があまり芳しくない.

以前 第247回 Offlineimap+Dovecotによる快適メール環境:Ubuntu Weekly Recipe なんて記事を書いた。 その時は offlineimap を使っていたのだけれども

  • 遅い。特に 2014 年あたりから Gmail の同期が体感できるレベルで遅くなった
  • 偶に block するし、imap の接続数が酷い事になる。

ということで、2015 年から isync: free IMAP and MailDir mailbox synchronizer に移行した。

TODO mbsync/isync

isync/mbsync はリモートの IMAP サーバとローカルの Maildir を同期するソフトウェア。 gmail の XOAUTH2 に対応するために libsasl2-module-xoauth2 なんてものを パッケージ化していたりする. ←あとで書く.

インストール: mbsync/isync

実行バイナリは isync もしくは mbsync 。以前の名前が isync なので、Debian のパッケージ名はそのままである。

$ sudo apt-get install isync

apt 万歳

設定: mbsync/isync

設定ファイルは ~/.mbsyncrc. 設定例は以下:

  • ~/Maildir/ 以下にサーバのメールを同期する
  • ~/Maildir/INBOX.mobile.XXX に imap.mobile.com のメールを同期.
    • 明示的に Pattern を指定することで、指定ディレクトリ以外を同期しないことにする
  • ~/Maildir/INBOX.Gmail.XXX に Gmail のメールを同期
    • 明示的に Pattern を指定することで、指定ディレクトリ以外を同期しないことにする
    • [Gmail]/ というラベルを上手く扱えないので、同期チャンネルを複数設定することで対応
      • 今のところ SPAM 堕ちしたメールの確認のためだけに ~/Maildir/INBOX.Gmail.Junk のみ確認している
  • ~/Maildir/INBOXwork.example.com のメールを同期
    • work.example.com メールサーバのディレクトリが work/2014 等と "/" 区切りになっているので Flatten: .INBOX.work.2014 等に変換する。そのために MaildirStore を複数設定している
    • Pattern: * !*mobile* !*Gmail* で同期から除外する Maildir を指定する
  • PassCmd で gpg 暗号化した netrc 形式のファイルからパスワードを取得している.
    • これ、User なんかにも使えないかなぁ、とか思ったり

今のところ特に使用に際して困った事はおきていない。

IMAPAccount mobile
Host mobile.example.com
User TheMobileUserAccount
PassCmd "echo ${PASSWORD:-$(gpg --no-tty -qd ~/.authinfo.gpg | sed -n 's,^machine mobile.example.com .*password \\\([^ ]*\\) port.*,\\1,p')}"
Port 993
UseIMAPS yes
RequireSSL yes
UseTLSv1.2 yes
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPAccount work
Host work.example.com
User TheWorkUserAccount
PassCmd "echo ${PASSWORD:-$(gpg --no-tty -qd ~/.authinfo.gpg | sed -n 's,^machine work.example.com .*password \\\([^ ]*\\) port.*,\\1,p')}"
Port 993
UseIMAPS yes
RequireSSL yes
UseTLSv1.2 yes
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPAccount gmail
Host imap.gmail.com
User GmailMailAddress
PassCmd "echo ${PASSWORD:-$(gpg --no-tty -qd ~/.authinfo.gpg | sed -n 's,^machine imap.gmail.com .*password \\\([^ ]*\\) port.*,\\1,p')}"
Port 993
UseIMAPS yes
RequireSSL yes
UseTLSv1.2 yes
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore work-remote
Account work
UseNamespace no

IMAPStore work-remote
Account work

IMAPStore gmail-remote
Account gmail
UseNamespace yes

MaildirStore local
Path ~/Maildir/
Inbox ~/Maildir/INBOX

MaildirStore work-local
Path ~/Maildir/
Inbox ~/Maildir/INBOX
Flatten .

Channel mobile
Master :mobile-remote:
Slave :local:INBOX.mobile.
Patterns INBOX Junk Archives Sent Drafts Trash
Create Both
Expunge Both
Sync all
SyncState *

Channel gmail
Master :gmail-remote:
Slave :local:INBOX.Gmail.
Patterns INBOX
Create Both
Expunge Both
Sync all
SyncState *

Channel gmail-junk
Master ":gmail-remote:[Gmail]/&j,dg0TDhMPww6w-"
Slave :local:INBOX.Gmail.Junk
Patterns *
Create Both
Expunge Both
Sync all
SyncState *

Channel work
Master :work-remote:
Slave :work-local:
Patterns * !*mobile* !*Gmail*
Create Both
Expunge Both
Sync all
SyncState *

上手く設定できているなら

mbsync -l mobile

などとして、同期する Maildir を確認すると良い。 リモートとローカルでどういうマッピングがされているかわかるだろう。 25万通(6.5G)の同期にだいたい 3 時間ぐらい。 回線速度に依存するだろうけれど、offlineimap より目に見えて速い(気がする)。 また、IMAP Pipeline で処理されているので、サーバ上の IMAP connection の数は 1 つのみだった。

cron での動作: mbsync/isync

認証に GPG を使っており、認証に Gnome Keyring を用いているので 必要な環境変数を読み込んでから実行するようにする. 先ず ~/bin/export_x_info.sh を以下の内容で用意:

#!/bin/bash
sleep 5
# Export the dbus session address on startup so it can be used by cron
touch $HOME/.Xdbus
chmod 600 $HOME/.Xdbus
env | grep DBUS_SESSION_BUS_ADDRESS > $HOME/.Xdbus
env | grep GPG_AGENT_INFO >> $HOME/.Xdbus
echo 'export DBUS_SESSION_BUS_ADDRESS' >> $HOME/.Xdbus
echo 'export GPG_AGENT_INFO' >> $HOME/.Xdbus

~/.config/autostart 以下に適当な名前で

[Desktop Entry]
Type=Application
Exec=/home/uwabami/.mua/export_x_info.sh
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name=Xdbus infomation exporter
Comment=Xdbus infomation exporter

を用意しておく.

あとは cron 実行時に ~/.Xdbus を include するようにしておけば, 必要に応じて GnomeKeyring による認証が行なわれる. cron で実行されるスクリプトは, 例えば

#!/bin/sh
# -*- mode:sh; coding: utf-8-unix; indent-tabs-mode: nil -*-
################################################################################
# for personal settings
PID=~/Maildir/mbsync.pid
CONF=~/.mbsyncrc
# for gnome-keyring:
. ${HOME}/.Xdbus
# check network is avaliable
nm-online -x  2>/dev/null || exit 0
# check another offlineimap
timelimit -T 300 -t 300 mbsync -c $CONF -a 2>&1 || exit 0

とか。mbsync 自体の timeout や PID 管理ができないので、 timelinit コマンドで処理をするようにしている。 実際は、二回目以降の同期は(回線速度に依存するだろうけれど) 20 秒ぐらい。

dovecot

インストール: dovecot

最低限 IMAP での接続ができれば良いので

% sudo apt-get install dovecot-imapd

apt 万歳

設定: dovecot

最低限の設定として、

  • localhost で起動. 認証は PAM に任せる(login パスワードと同じ)
  • Maildir 形式で mbsync(もしくは offlineimap)で同期したMaildirを読む.
  • IMAP もしくは IMAP over SSL のみ

ための設定を行なう

以下、設定ファイル中のコメント行を除いた部分だけを記載しているので注意 (コメント消すなんてとんでもない!)。

先ず待受サーバ。 どの IP アドレスで待ち受けるかは /etc/dovecot/dovecot.conf にあるので

...
listen = 127.0.0.1
...

あたりを修正しておく。

認証は /etc/dovecot/conf.d/10-auth.conf 内で

disable_plain_text_auth = no
auth_mechanisms = plain
!include auth-system.conf.ext

を有効に。include されている auth-system.conf.ext の中身は

passdb {
  driver = pam
}
userdb {
  driver = passwd
}

だけが有効。これで pam 経由でパスワードログインするようになる.

次に Maildir の設定。 /etc/dovecot/conf.d/10-mail.conf 内で

mail_location = maildir:%h/Maildir:INBOX=%h/Maildir/INBOX:LAYOUT=fs
namespace inbox {
  inbox = yes
}

あたりを修正しておく。

最後に IMAP over SSL の設定。 /etc/dovecot/conf.d/10-ssl.conf で証明書の設定をする:

ssl_cert = </etc/ssl/private/ssl-cert-dovecot.pem
ssl_key = </etc/ssl/private/ssl-cert-dovecot.key
ssl_protocols = !SSLv2 !SSLv3

その後に /etc/dovecot/conf.d/10-master.conf 内の imap-login 部分を修正

service imap-login {
  inet_listener imap {
    port = 0
    ssl = no
  }
  inet_listener imaps {
    port = 993
    ssl = yes
  }
}

これで再起動すると /etc/ssl/private に置いた SSL 証明書を使って 127.0.1.1:993 で IMAP サーバが起動する。Listen を 127.0.0.1 に絞るなら SSL でのアクセスは不要かもしれない。

以下, obsolete

検索は mu (maildir-utils) を使うようになったし, メールの同期には mbsync を使うようになったので, 以下は昔のお話.

検索: Solr

Dovecot には FTS(Full Text Search) 用の plugin が幾つかある。 ここでは Solr を試してみる

とりあえず jetty で solar を動かすことに:

% sudo aptitude -R solr-jetty dovecot-solr

Jetty の起動設定は /etc/default/jetty8 にあるので

NO_START=0
JETTY_HOST=localhost
JETTY_PORT=8089

としておく。dovecot 用の scheme.xml は dovecot-solr をインストールすると /usr/share/dovecot/solr-scheme.xml にインストールされるので、これを /etc/solr/conf/scheme.xml にコピー

% cd /etc/solr/conf
% sudo cp scheme.xml scheme.xml.bak
% sudo cp /usr/share/dovecot/solr-schemex.ml scheme.xml

その後で /etc/dovecot/conf.d/10-imap.conf 内の mailplugins 行に

mail_plugins = fts fts_solr

を追加しておく。 IMAP でしか使わないのであれば 20-imap.conf に指定すれば良いのだが、この場合は doveadm fts rescan -u USERNAME ができない。 最後に /etc/dovecot/conf.d/90-plugin.conf

plugin {
  fts_autoindex = yes
  fts = solr
  fts_solr = break-imap-search url=http://localhost:8089/solr/
}

としておく。これで jetty8 および dovecot を再起動すると、検索が効く様になる…?

検索: Solr で日本語

scheme.xml の修正が必要。 とりあえず

  • /etc/solr/conf/scheme.xml より text_cjk および text_ja の部分を抜き出して, dovecot の提供していた scheme.xml に追加
  • hdr,body,subjecttext_cjk で解析するように

してみた.

diff は以下:

--- solr-schema.xml 2015-05-10 20:52:33.375358006 +0900
+++ schema.xml  2015-05-10 20:37:37.358195292 +0900
@@ -13,6 +13,62 @@
     <fieldType name="long" class="solr.TrieLongField" />
     <fieldType name="boolean" class="solr.BoolField" />

+    <!-- CJK bigram (see text_ja for a Japanese configuration using morphological analysis) -->
+    <fieldType name="text_cjk" class="solr.TextField" positionIncrementGap="100">
+      <analyzer>
+        <tokenizer class="solr.StandardTokenizerFactory"/>
+        <!-- normalize width before bigram, as e.g. half-width dakuten combine  -->
+        <filter class="solr.CJKWidthFilterFactory"/>
+        <!-- for any non-CJK -->
+        <filter class="solr.LowerCaseFilterFactory"/>
+        <filter class="solr.CJKBigramFilterFactory"/>
+      </analyzer>
+    </fieldType>
+    <fieldType name="text_ja" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false">
+      <analyzer>
+      <!-- Kuromoji Japanese morphological analyzer/tokenizer (JapaneseTokenizer)
+
+           Kuromoji has a search mode (default) that does segmentation useful for search.  A heuristic
+           is used to segment compounds into its parts and the compound itself is kept as synonym.
+
+           Valid values for attribute mode are:
+              normal: regular segmentation
+              search: segmentation useful for search with synonyms compounds (default)
+            extended: same as search mode, but unigrams unknown words (experimental)
+
+           For some applications it might be good to use search mode for indexing and normal mode for
+           queries to reduce recall and prevent parts of compounds from being matched and highlighted.
+           Use <analyzer type="index"> and <analyzer type="query"> for this and mode normal in query.
+
+           Kuromoji also has a convenient user dictionary feature that allows overriding the statistical
+           model with your own entries for segmentation, part-of-speech tags and readings without a need
+           to specify weights.  Notice that user dictionaries have not been subject to extensive testing.
+
+           User dictionary attributes are:
+                     userDictionary: user dictionary filename
+             userDictionaryEncoding: user dictionary encoding (default is UTF-8)
+
+           See lang/userdict_ja.txt for a sample user dictionary file.
+
+           See http://wiki.apache.org/solr/JapaneseLanguageSupport for more on Japanese language support.
+        -->
+        <tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/>
+        <!--<tokenizer class="solr.JapaneseTokenizerFactory" mode="search" userDictionary="lang/userdict_ja.txt"/>-->
+        <!-- Reduces inflected verbs and adjectives to their base/dictionary forms (辞書形) -->
+        <filter class="solr.JapaneseBaseFormFilterFactory"/>
+        <!-- Removes tokens with certain part-of-speech tags -->
+        <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" enablePositionIncrements="true"/>
+        <!-- Normalizes full-width romaji to half-width and half-width kana to full-width (Unicode NFKC subset) -->
+        <filter class="solr.CJKWidthFilterFactory"/>
+        <!-- Removes common tokens typically not useful for search, but have a negative effect on ranking -->
+        <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" enablePositionIncrements="true" />
+        <!-- Normalizes common katakana spelling variations by removing any last long sound character (U+30FC) -->
+        <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
+        <!-- Lower-cases romaji characters -->
+        <filter class="solr.LowerCaseFilterFactory"/>
+      </analyzer>
+    </fieldType>
+
     <fieldType name="text" class="solr.TextField" positionIncrementGap="100">
       <analyzer type="index">
         <tokenizer class="solr.StandardTokenizerFactory"/>
@@ -43,14 +99,14 @@
    <field name="box" type="string" indexed="true" stored="true" required="true" />
    <field name="user" type="string" indexed="true" stored="true" required="true" />

-   <field name="hdr" type="text" indexed="true" stored="false" />
-   <field name="body" type="text" indexed="true" stored="false" />
+   <field name="hdr" type="text_cjk" indexed="true" stored="false" />
+   <field name="body" type="text_cjk" indexed="true" stored="false" />

    <field name="from" type="text" indexed="true" stored="false" />
    <field name="to" type="text" indexed="true" stored="false" />
    <field name="cc" type="text" indexed="true" stored="false" />
    <field name="bcc" type="text" indexed="true" stored="false" />
-   <field name="subject" type="text" indexed="true" stored="false" />
+   <field name="subject" type="text_cjk" indexed="true" stored="false" />

    <!-- Used by Solr internally: -->
    <field name="_version_" type="long" indexed="true" stored="true"/>

offlineimap

(2015/05/10) 以前使っていた時のメモ。もう使わないけれど、たまに検索でココを見に来ている人がいるみたいなので、残しておくことに。 OfflineIMAP は,ネットワーク上にあるIMAPサーバと手元のMaildir/IMAPサーバの同期を取るためのソフトウェア.

  • インストール: offlineimap

    apt万歳, ってことで.

    $ sudo apt-get install offlineimap
    
  • 設定: offlineimap

    現状, 以下の通り:

    • OfflineIMAP のメタ情報は ~/Mail/offlineimap 以下に格納
    • 同期するサーバは二箇所:
      • Main サーバのメールは ~/Mail/imap 以下に格納.
        • この際, IMAP Namespace である INBOX. を除外して Maildir を作成
        • ~/Mail/imap/[Gmail] は同期しない
      • Gmail のメールは ~/Mail/imap/[Gmail]/ 以下に格納
    # -*- mode: conf; coding: utf-8-unix; indent-tabs-mode: nil -*-
    # offlineimaprc
    #
    # Copyright(C) 2011 Youhei SASAKI All rights reserved.
    # $Lastupdate: 2014-07-26 18:25:07$
    #
    # Author: Youhei SASAKI <uwabami@gfd-dennou.org>
    # License: WTFPL
    #
    # Code:
    [general]
    # メタデータの格納先
    metadata = ~/Mail/offlineimap
    # 補助関数が定義されたファイルの置き場所
    pythonfile = ~/.mua/offlineimap_utils.py
    # 同期するサーバの設定
    accounts = Main, Gmail
    # 同期するアカウントの数
    maxsyncaccounts = 2
    # UI の設定. cron で同期する場合には quiet の方が良い
    ui = basic
    socktimeout = 600
    # 高速化(?)
    fsync = false
    
    # Gmail 用の設定: ここで設定する名前は accounts に揃える
    [Account Gmail]
    localrepository = LocalGmail
    remoterepository = RemoteGmail
    maxsize = 2000000000
    status_backend = sqlite
    # quick = 1 だとフラグは同期されない
    # '[Gmail]/全てのメール' を同期するなら quick = 1 の方が良い?
    quick = 0
    
    [Repository LocalGmail]
    # Gmail の手元の設定
    type = Maildir
    # Maildir の格納場所
    localfolders = ~/Mail/imap/[Gmail]
    restoreatime = no
    # IMAP のセパレータの変換: Mailbox の "." が "/" となり,
    # ディレクトリが作成される
    sep = /
    
    [Repository RemoteGmail]
    # Gmail との接続設定: type = IMAP でも良い?
    type = Gmail
    # remote{pass,user}eval は offlineimap_utils.py で定義した関数
    remoteusereval = get_username("imap.gmail.com")
    remotepasseval = get_password("imap.gmail.com")
    maxsize = 2000000000
    realdelete = no
    # 同時接続数. 並列実行可能なので適宜
    # サーバに怒られない程度に
    maxconnections = 5
    # remote -> local 時のフォルダ名変換(正規表現)
    nametrans = lambda foldername: re.sub('^INBOX','', (re.sub('^\[Gmail\]/','', foldername)))
    # 同期するフォルダの設定: Gmail 側で IMAP で表示するラベル設定しておく, でも良いかも.
    folderfilter = lambda foldername: foldername in [
                 'INBOX',                   # -> '~/Mail/imap/[Gmail]' になる
                 '[Gmail]/&j,dg0TDhMPww6w-' # -> '~/Mail/imap/[Gmail]/&j,dg0TDhMPww6w-' になる
                 ]
    sslcacertfile = /etc/ssl/certs/ca-certificates.crt
    
    # Main サーバの設定: ここで設定する名前は [general] accounts に揃える
    [Account Main]
    localrepository = LocalMain
    remoterepository = RemoteMain
    status_backend = sqlite
    maxsize = 2000000000
    quick = 0
    
    [Repository LocalMain]
    # 手元の設定
    type = Maildir
    # Maildir の格納場所
    localfolders = ~/Mail/imap
    restoreatime = no
    # local -> remote での名前変換
    # LocaltoRemoteTransnameINBOX は offlineimap_utils.py で定義した関数
    nametrans = LocalToRemoteTransnameINBOX
    # 'Maildir/imap/[Gmail]' は同期しない
    folderfilter = lambda folder: not folder.startswith('[Gmail]')
    sep = /
    
    [Repository RemoteMain]
    type = IMAP
    ssl = yes
    # certificates がデフォルトで探せない?
    sslcacertfile = /etc/ssl/certs/ca-certificates.crt
    remotehost = [main server]
    # remote{pass,user}eval は offlineimap_utils.py で定義した関数
    remoteusereval = get_username("[main server]")
    remotepasseval = get_password("[main server]")
    maxsize = 2000000000
    maxconnections = 5
    # remote -> local での名前の変換
    # RemotetoLocalTransnameINBOX は offlineimap_utils.py で定義した関数
    nametrans = RemoteToLocalTransnameINBOX
    # 同期 *しない* フォルダの選択
    folderfilter = lambda foldername: foldername not in [
                'INBOX.Drafts',
                'INBOX.Trash',
                'INBOX.archive.zz2006',
                'INBOX.archive.zz2007',
                'INBOX.archive.zz2008',
                'INBOX.archive.zz2009',
                'INBOX.archive.zz2010',
                'INBOX.archive.zz2011',
                'INBOX.archive.zz2012',
                'INBOX.archive.zz2013'
                ]

    offlineimap_utils.py の中身は以下の通り:

    #!/usr/bin/python
    # -*- coding: utf-8
    import sys
    import re
    import os
    # # Auth via GnomeKeyring
    import gtk
    from getpass import getpass
    import gnomekeyring as gkey
    # Ad hoc fix for Striptime behavior
    import locale
    locale.setlocale(locale.LC_TIME, 'C')
    
    ##################
    # Gnome keyring  #
    ##################
    
    class Keyring(object):
        def __init__(self, name, server, protocol):
            self._name = name
            self._server = server
            self._protocol = protocol
            self._keyring = gkey.get_default_keyring_sync()
    
        def has_credentials(self):
            try:
                attrs = {"server": self._server, "protocol": self._protocol}
                items = gkey.find_items_sync(gkey.ITEM_NETWORK_PASSWORD, attrs)
                return len(items) > 0
            except gkey.DeniedError:
                return False
    
        def get_credentials(self):
            attrs = {"server": self._server, "protocol": self._protocol}
            items = gkey.find_items_sync(gkey.ITEM_NETWORK_PASSWORD, attrs)
            return (items[0].attributes["user"], items[0].secret)
    
        def set_credentials(self, (user, pw)):
            attrs = {
                    "user": user,
                    "server": self._server,
                    "protocol": self._protocol,
                }
            gkey.item_create_sync(gkey.get_default_keyring_sync(),
                    gkey.ITEM_NETWORK_PASSWORD, self._name, attrs, pw, True)
    
    def get_username(server):
        keyring = Keyring("offlineimap", server, "imap")
        (username, password) = keyring.get_credentials()
        return username
    
    def get_password(server):
        keyring = Keyring("offlineimap", server, "imap")
        (username, password) = keyring.get_credentials()
        return password
    
    if __name__ == '__main__':
            server = raw_input("server: ")
            user = raw_input("username: ")
            password = getpass("password: ")
            confirm = getpass("confirm password: ")
            if password != confirm:
                print "password doesn't match confirmation!"
                sys.exit(1)
            keyring = Keyring("offlineimap", server, "imap")
            keyring.set_credentials((user, password))
    
    ############################
    # NameTrans 'INBOX' suffix #
    ############################
    # add 'INBOX' suffix
    def LocalToRemoteTransnameINBOX(foldername):
        if (foldername == ""):
            retval = "INBOX"
        else:
            retval = "INBOX." + foldername
        return retval
    
    # remove 'INBOX' suffix
    def RemoteToLocalTransnameINBOX(foldername):
        if (foldername == "INBOX"):
            retval = ""
        else:
            retval = re.sub("^INBOX\.", "", foldername)
        return retval

    IMAP の名前空間が複雑な(?)場合には, 適宜正規表現を追加すると良い.

  • cron での動作: offlineimap

    認証に GnomeKeyring を使っているので DBUS_SESSION_BUS_ADDRESS を設定してから 実行するようにする. 先ず ~/bin/export_x_info.sh を以下の内容で用意

    #!/bin/bash
    sleep 5
    # Export the dbus session address on startup so it can be used by cron
    touch $HOME/.Xdbus
    chmod 600 $HOME/.Xdbus
    env | grep DBUS_SESSION_BUS_ADDRESS > $HOME/.Xdbus
    echo 'export DBUS_SESSION_BUS_ADDRESS' >> $HOME/.Xdbus

    ~/.config/autostart 以下に適当な名前で

    [Desktop Entry]
    Type=Application
    Exec=/home/uwabami/.mua/export_x_info.sh
    Hidden=false
    NoDisplay=false
    X-GNOME-Autostart-enabled=true
    Name=Xdbus infomation exporter
    Comment=Xdbus infomation exporter

    を用意しておく.

    あとは cron 実行時に ~/.Xdbus を include するようにしておけば, 必要に応じて GnomeKeyring による認証が行なわれる. cron で実行されるスクリプトは, 例えば

    #!/bin/sh
    # -*- mode:sh; coding: utf-8-unix; indent-tabs-mode: nil -*-
    # $Lastupdate: 2014-07-15 11:52:22$
    # Author: Youhei SASAKI <uwabami@gfd-dennou.org>
    # License: WTFPL
    ################################################################################
    # for personal settings
    OFFLINEIMAP_PID=~/Mail/offlineimap/pid
    OFFLINEIMAP_CONF=~/.offlineimaprc
    UI=quiet
    echo "******************************************************************"
    echo `LANG=C date`
    echo "******************************************************************"
    # for gnome-keyring:
    . ${HOME}/.Xdbus
    # check network is avaliable
    nm-online -x || exit 0
    # check another offlineimap
    [ $(ps -p `cat $OFFLINEIMAP_PID` -o pid --no-heading) ] \
      || nice -n 19 offlineimap -c $OFFLINEIMAP_CONF -u $UI -o  2>&1

    とか.