yagshi's website
mu4eを MS の OAuth2に対応させる

mu4eを MS の OAuth2に対応させる

タイトルが正確じゃないかも知れませんが、やりたいことはそうなので そういう内容です。大学のe-mailを提供している MS の Office365 のOutlook が とうとう LOGIN 認証を認めなくなってしまったので重い腰を上げた次第。 何とか mu4e (isync(mbsync), msmtp) でOAuth2認証できるようになりましたが、 なんかとても面倒くさかった。

mu4eって何?という方はこばやしの別記事をどうぞ。

0. prerequisites

OAuth 認証をやってくれる外部ツールが必要で、いくつかあるようなのですが 3つほど試して次の2つが何とか使えたのでこれらで行きます。MSだけで 良いならoauth2msの方が設定項目が少なく、運用時もひと手間省けて楽です。

  • oauth2ms
    まさにMSのO365専用に作られたPythonツールです。

  • oauth2token
    こっちはGoogleなどもいける汎用ツールです。pip でインストールできるのは楽だけど、認証を行うコマンドと、トークンを出力するコマンドとに分けられているのが(使用時に)面倒くさい。

あとはOSにもよると思いますが、Debian の場合は以下のライブラリをインストールする必要がありました。

  • libsasl2-modules-kdexoauth2
    sudo apt install libsasl2-modules-kdexoauth2 でok。

1. Azureの設定

Azure側でアプリを登録します。 このアプリが oauth2ms であり oauth2token です。 詳しくはoauth2msの解説ページ を参照してください。

  1. azure portalを開きます。

  2. Azure active directory へと進み、「+追加」→「アプリの登録」

  3. 名前は適当に mail とかつけます。サポートされているアカウントはデフォルトのままでいいと思います。リダイレクトURIはあとから入力することにしてとりあえず省略。

  4. 「アプリケーション(クライアント)ID」と「ディレクトリ(テナント)ID」を控えておきます。

  5. 「証明書とシークレット」へと進み、「+新しいクライアントシークレット」をクリックします。 作られたシークレットの「値」の方を控えます。この値は今後参照できなくなるのでしっかり 控えましょう。

  6. 「APIのアクセス許可」へと進み、「+アクセス許可の追加」をクリックし、「Microsoft Graph」 →「委任されたアクセス許可」から以下を追加します。 - IMAP.AccessAsUser.All - User.ReadBasic.All - SMTP.Send

  7. 「認証」へと進み、「+プラットフォームを追加」をクリックし、「Web」を選択し、リダイレクトURIに……

    • oauth2ms (次節)を使う場合はhttp://localhost:5000/getToken
    • oauth2token (次々節)を使う場合はhttp://localhost

と入力します。そして「サポートされているアカウントの種類」をマルチテナントにし、さらに「パブリッククライアントフローを許可する」を「はい」にします。

2-a. oauth2ms の設定(次節のoauth2tokenとどちらか一方でok)

  1. 公式ページを見てインストールします。だいたいこんな流れです。
$ git clone https://github.com/harishkrupo/oauth2ms
(クローンされる)
$ cd oauth2ms
$ pip install -r requirements.txt --user
(必要なモジュールがインストールされる)
$ sudo cp oauth2ms /usr/local/bin    #←これはお好みで。
  1. ~/.config/oauth2ms/というディレクトリを作って、以下のconfig.jsonを作ります。
// config.json
{
  "tenant_id": "上で控えたテナントID",
  "client_id": "上で控えた「アプリケーション(クライアント)ID」",
  "client_secret": "上で控えたシークレットの「値」",
  "redirect_host": "localhost",
  "redirect_port": "5000",
  "redirect_path": "/getToken/",
  "scopes": ["https://outlook.office.com/IMAP.AccessAsUser.All"]
}
  1. ターミナル上でoauth2msを実行します。ブラウザの認証画面が開き、認証が終わったらターミナルにトークンが出力されればok。

2-b. oauth2token の設定(前節のoauth2msとどちらか一方でok)

  1. pip install oauth2token --user でインストールします。
  2. ~/.config/oauth2token/microsoft/(microsoftの部分は自分で決める識別名)という ディレクトリを作って、以下のconfig.jsonscopes.jsonを作ります。 oauth2tokenの公式ページ参照。
// config.json
{
  "web": {
    "client_id": "上で控えた「アプリケーション(クライアント)ID」",
    "client_secret": "上で控えたシークレットの「値」",
    "auth_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
    "token_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/token"
  }
}
// scopes.json
["https://outlook.office.com/IMAP.AccessAsUser.All", "https://outlook.office.com
/SMTP.Send"]
  1. とりあえずここまでやって万事うまくできていれば oauth2create microsoft <アカウト> としていつものweb認証ができるはず。 その後、エラーなく終了すればok。さらにoauth2get microsoft <アカウント> で トークンが出力されるはず。

3. .mbsyncrc の修正

現在 LOGIN 認証で動いているのであれば AuthMechs と PassCmd を修正するだけで大丈夫なはずです。

AuthMechs XOAUTH2
PassCmd "oauth2ms"                           # oauth2msの場合
PassCmd "oauth2get microsoft <アカウント>"   # oauth2tokenの場合

4. .msmtprc の修正

現在 LOGIN 認証で動いているのであれば auth と passwordeval を修正するだけで大丈夫なはずです。

auth xoauth2
passwordeval "oauth2ms"                         # oauth2msの場合
passwordeval "oauth2get microsoft <アカウント>" # oauth2tokenの場合

で、これで ok な人は ok です。ところが、環境によっては メイル送信時に oauth2ms で permission denied のエラーが出てしまう場合があります。これはAppArmorの 仕業かもしれません。その場合は、/etc/apparmor.d/usr.bin.msmtp を変数してホワイトリストに oauth2ms を 加えます。以下のような感じです。

(前略)
    owner /tmp/*     rw,

    /usr/local/bin/oauth2ms PUx,  # この行を追加
    /usr/bin/secret-tool PUx,
(以下略)

5. macOSの問題 (2024/2追記)

と、これで万事ok、のはずなのですが、なんと macOSでは動きません、たぶん。 sasl というライブラリの問題らしくmbsyncでエラーが出てしまいます。

yagshi@mac ~ % mbsync oit
Error performing SASL authentication step: SASL(-1): generic failure: Unable to find a callback: 18948
C: 3/3  B: 0/3  F: +0/0 *0/0 #0/0  N: +0/0 *0/0 #0/0

こんな感じです。ぼくは結局綺麗な解決方法が思いつかなかったので Docker のlinux で mbsync しました。Dockerfile はこんなんです。

FROM debian
RUN apt update
RUN apt -y install isync openssl ca-certificates libsasl2-modules-kdexoauth2 git python3 python3-pip python3-msal python3-xdg python3-gnupg
RUN git clone https://github.com/harishkrupo/oauth2ms
RUN cp oauth2ms/oauth2ms /usr/local/bin

docker build -t mbsync としてイメージを作ります。 で、emacsの init.el あたりで macOS のときのみ docker の linux 環境で mbsync します。 OSによって切り替えるのが面倒なら linux でも docker 使っても良いとは 思いますが。どちらがより美しいと思うかは人によるかな。

(when (eq system-type 'darwin)
  (setq mu4e-get-mail-command "mbsync srv1 srv2; docker run -v /Users/yagshi:/root --rm mbsync mbsync oit")
  )

ここでわざわざ oauth2 が必要なサーバ(上記例だとoit) だけ dockerでやって、その他の srv1, srv2 はネイティブ環境でやっています。これは認証の問題を 回避するためです。oauth2 じゃないサーバの方は mu4eを使おうの記事で書いた通りパスワードを gpg で暗号化していて、 gpg-agentの類が (いわゆる pinentry で) パスワードを求めます。 その際 macOS 側の agent が docker 側にクレデンシャルを渡すような方法があれば良いのでしょうが (ていうか、きっとあるんでしょうが)、 とてもめんどくさそうだったので単純にサーバによって動作環境を分けました。