iPhoneのメールで表示されるタイムスタンプはDateヘッダではなくエンベロープの到着日
理由は知りませんが、iPhoneのApple純正のメールアプリ(※)に表示されるタイムスタンプは、IMAP4を使ってる場合、メールヘッダのDate:ヘッダの中身では無く所謂エンベロープの到着日を表示しているようです。
※インストール時点で入っているApple公式の青いアイコンのメールアプリのことです。
エンベロープの到着日というのはIMAP4のINTERNALDATEに相当するタイムスタンプです。IMAPサーバにloginコマンドでログイン後、SELECTコマンドでメールボックスを選択して「fetch <メール番号> INTERNALDATE」みたいなコマンドを送りつけると返ってくるタイムスタンプです。
a FETCH 1 INTERNALDATE
* 1 FETCH (INTERNALDATE "18-Mar-2010 01:36:12 +0900")
a OK Fetch completed (0.013 + 0.000 + 0.012 secs).
a FETCH 1 SAVEDATE
* 1 FETCH (SAVEDATE "21-Oct-2022 05:38:12 +0900")
a OK Fetch completed (0.031 + 0.000 + 0.030 secs).
このエンベロープのタイムスタンプは私のメールサーバ(※)の場合、下記に示すようにファイルシステム上で、上記のメールに該当するメールファイルの属性値であるBirthかModified属性のいずれかのタイムスタンプがそのまま入っていました(※※)。ちなみにSAVEDATEコマンドの方はChange属性が入っているみたいです。
$ stat -x 1268843772.*****.mail.yuaiho.net:2,S
File: "1268843772.*****.mail.yuaiho.net:2,S"
Size: 654 FileType: Regular File
Mode: (0600/-rw-------) Uid: (yuaiho/515) Gid: (yuaiho/515)
Device: 0,112 Inode: 19423*** Links: 1
Access: Fri Oct 21 05:07:10 2022
Modify: Thu Mar 18 01:36:12 2010
Change: Fri Oct 21 05:38:12 2022
Birth: Thu Mar 18 01:36:12 2010
※ファイル名の一部(FQDN部分)やInode番号、UID/GIDは架空のものですが、それ以外はオリジナルのデータです。タイムスタンプ変更もこのファイルに関しては変更していません。
※FreeBSD13.1+Postfix3.7.3+Dovecot2.3.19.1。投稿日時点の安定版の最新Versionです。互換モードは無効化。
※※BirthかModified属性のいずれか: BirthとModifiedのどっちを採用してるかは不明です。freebsd-ufs上ではModifiedもBirthも同じタイムスタンプになるため、どちらを見てるのかわかりません。
まぁ要するにMTA(Mail Transfer Agent)やMRA(Mail Retrieval Agent)の独自DBだったりMaildirだったりのどこかに、エンベロープのタイムスタンプが書かれているファイルが別途存在する、というわけでは無いと言うことです。
他のMTAやMRAの組み合わせまでは存じませんが、少なくともPostfix+Dovecotでは前述の通りです。
MRAはMUAからIMAPコマンドでINTERNALDATE相当の問い合わせを受けると、それに該当するメールファイルのファイルシステムのModified or BirthのタイムスタンプをOSに問い合わせて、その結果をエンベロープのタイムスタンプと(いうことに)して送って来てるだけと言うことです。
iPhoneの純正のメールアプリはこのエンベロープのタイムスタンプ、つまりメールファイルがサーバのファイルシステムに書き込まれた時点のタイムスタンプを、メール受信日として取り扱っています。
iPhoneのメールのタイムスタンプがおかしい理由
と、いうわけで、ファイルシステム上のメールファイルのModified属性のタイムスタンプが狂うと、iPhone上のメールアプリの受信日もその狂ったタイムスタンプになります。
他のメールソフトで見るとタイムスタンプがそれっぽいものがちゃんと表示されているのに、同じメールをiPhoneで見るとタイムスタンプがおかしい場合、たぶんこれが原因です。他のメールソフトはDate:ヘッダをParseして表示していたりするんですが、iPhoneのメールアプリはDate:ヘッダを使って無いっぽいです。
メールファイルのタイムスタンプが狂う起因ですが、例えばメールサーバのシェルなどでメールファイルを直接操作するときに、tar cfpやcp -pとかの属性保存オプションの使用を忘れて雑にcpやtarをしたりすると、コピー先でメールファイルのタイムスタンプがファイル操作実行時点のものに書き変わってしまいます。
エンベロープのタイムスタンプの実態はさっき説明したように、実態はメールファイルの置いてあるディスク(ファイルシステム)に記録されたModified属性です。
そのため、メールファイルのModifiedのタイムスタンプが変更されるような操作をすると、本当のエンベロープのタイムスタンプが虚空に消えると言うわけです。
そして、そんな正しいModifiedのデータが虚空に消え、”管理者が雑にファイルを操作した日”とかいう利用者からするとわけのわからんタイムスタンプが入ったメールファイルを含むメールボックスをiPhoneのメールアプリから見ると、その謎の日付が表示されることになります。
修正方法
さて、リストアする方法ですが、きちんとタイムスタンプが保存されているバックアップが無いなら基本的にはありません。
しかし、メールヘッダ内にあるタイムスタンプが信用できるか、(信用できるかどうかに関わらず)信用することにするなら、メールヘッダに入ってるタイムスタンプを元にしてModified属性の日付時刻に修正することは出来ます。
使用出来るタイムスタンプが入ったヘッダは2つあり、Received:ヘッダとDate:ヘッダです。
具体的には、Received:ヘッダのうち最も行頭に近いところにあるReceived:ヘッダの末尾に付いてるタイムスタンプと、Date:ヘッダに含まれるタイムスタンプのいずれかです。1つのReceived:ヘッダが複数行渡って書かれている場合、その1つのReceived:ヘッダ全体の末尾に書かれたタイムスタンプを使用します。
後述しますが、どちらのヘッダを使っても信頼性はケースバイケースです。特に昔の牧歌的な時代のタイムスタンプはReceived:ヘッダであっても正直怪しいです。
そしてReceived:ヘッダを使う方法は1つだけ制限があって、MUA(Mail User Agent/メーラ)が送信時に直接IMAPフォルダに書き込んだメール、つまりSentフォルダや送信済フォルダの中身には使用できません。これらのメールはSMTPサーバを経由しておらず、Received:ヘッダがヘッダに付与されていないので、Date:ヘッダしか使用できません。
Received:ヘッダ
もっとも行頭に近い部分にあるReceived:ヘッダは、メールリレーの終着点である自分のサーバが付けるので、送信者側から詐称が難しく、適切な時刻管理がされていればかなり正確なのが期待できます。この「適切な時刻管理がされていれば」と言うのがミソで、本当に正確かは終着点のサーバの管理次第です。
自営SMTPサーバを持ってて、そのSMTPがメールリレーの終点であれば1番上のReceived:ヘッダは自分のサーバが付与したヘッダになります。
エンベロープのタイムスタンプは終点のメールサーバにファイルが書き込まれた時刻です。MTA内部でメールスプールがふん詰まったりしない限り、通常はSMTPでメールを受信したら即座にMaildirのあるディスクに書き込まれますので、ヘッダ上の自分のサーバへの到着日をエンベロープの到着日とみなすのは一定の合理性があります。
まぁ、実際のところファイル属性値はミリ秒まで書かれているらしいんでが、SMTPのヘッダは1秒未満(=ミリ秒)の情報が無いのでミリ秒の情報はヘッダから復元出来ません。
そのため時刻管理が正しく行われているサーバのReceivedヘッダを使って修正したとしても実は若干不正確なんですが、それでも±1秒レベルぐらいの精度では合うので、秒以前に年月日すら合ってないようなわけわからんタイムスタンプに比べれば全然マシです。
仮に終点が自分のサーバでなくても、読み込んで時差補正さえかければまぁそれっぽい時間にはなります。
ただ時刻管理が杜撰なメールサーバでは、下手するとDate:ヘッダより不正確です。私の場合、過去のメール調べてみると2000年代前半のものはDate:ヘッダや先頭のReceived:ヘッダとその1つ前のReceived:ヘッダに乖離があるものが結構ありました。
まぁ2000年代前半(5号機時代)の私のサーバは時刻同期(NTP)の管理が杜撰そのものでした。当時はメインで使ってるマシンぐらいしかまとも時刻同期してませんでしたし、合わせ方もNTPではなくSNTPで気が向いた時にたまに合わせるぐらいでした(※)。
※今のモダンなOSは特に設定してなくても明確に止めない限り、w32tmなりntpdなりchronydなりsystem-timesyncdなりのNTPが動きます。
Real Time Clock Due to oddities of the Macintosh hardware interrupt priority scheme, NetBSD/mac68k keeps very poor time. Under a high interrupt load (e.g. SCSI or serial port activity), a machine can lose several minutes per hour. A consequence of this problem is that attempting to run ntpd is generally rather pointless.
https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.3/mac68k/INSTALL.html#Known%20hardware%20issues%20with%20this%20release
ちなみに私の場合、杜撰な時刻同期管理に加えて、5号機で使ってたNetBSD/mac68kは上記のようにIO処理が重いと時計が狂う仕様らしいので正確な要素が全く見当たりませんね。実際、SNTP (ntpdate)で合わせると毎回1〜2時間ぐらいズレてて修正かかってました。
しっかりしたISPなら大丈夫だと思いますが、時刻管理が杜撰なサーバの古いメールだとReceivedヘッダの時刻は怪しいかもしれません。
たた、終点が自分のサーバなら、仮にそのサーバの時刻同期が杜撰で多少ズレていたとしても、1番上のReceived:ヘッダは「サーバに到着した時のサーバの時刻」が書かれてます。その時にサーバはその時刻だったのは間違いはないので、目を瞑れる程度のズレならスルーするしてしまうのがいいと思います。
前述したように、Received:ヘッダを採用する方式の弱点は、MUAから直接IMAPサーバに書き込まれたメール、つまり「Sentフォルダ」や「送信済メール」の中身には適用出来ません。
これらのメールは基本的にReceivedヘッダが1つも無いことが多いのでReceived:ヘッダの時刻を適用する方法では修正出来ません。これらのメールに対してはDate:ヘッダを使う必要があります。
Date:ヘッダ
Date:ヘッダはReceived:ヘッダと異なり、メールファイル内で付いていないということはまず無いというメリットがあります。
ただ、Date:ヘッダは送信者の自己申告も容易に出来ます。つまり詐称しやすいヘッダです。よって、必ずしも信用できる値が入ってるわけではありません。これが最大のデメリットです。
ただ、これは私の主観にはなりますが、明らかにおかしいタイムスタンプが付いているケースはゼロではありませんが、実際のところはほとんど見かけません(※)。
また、Date:ヘッダを受信時刻として扱うMUAの実装もあるので、「管理者がファイル操作した時刻」というメールの中身と無関係なタイムスタンプよりは、Dateヘッダのタイムスタンプを使う方がまだマシとは言えます。
※たぶんですが、Date:ヘッダは明らかに詐称してるものがあるのでiPhoneは Date:ヘッダでなくエンベロープを見てるのだと思います。
基本はReceived:ヘッダから時刻を引っ張ってきて、なんらかの理由でReceived:ヘッダのタイムスタンプが明らかにおかしいか、SentフォルダなどでReceived:ヘッダがない場合はDateから復旧すれば良いと思われます。
修正の流れ
具体的な流れはメールファイルのヘッダから読み込んだReceived:なりDate:なりにあるタイムスタンプをParseして、整形して、時差を修正して、touch -dコマンドの引数として利用可能なYYYY-MM-DDThh:mm:ss形式(例:2000-05-15T21:15:09)にします。Yuaihoの場合はtouch -dの引数に入れる時刻はローカルタイム、つまり時差修整を含む日本時間を指定しています。
ちなみに、Date:ヘッダのタイムスタンプはYuaihoの持ってるメールだけでも下記のように割とバラエティがあってParseするが割と面倒でした。もし自分でコードを書くのであればReceived:ヘッダを使う方が楽だと思います。Receivedヘッダの方がバラエティが少ないです。
1. Fri, 28 Apr 2006 15:44:57 +0900 (JST) ※PostfixのReceivedヘッダ
2. Fri, 18 Jul 2003 17:58:36 +0900 ※よくわからんMTAのReceivedヘッダ
3. Thu, 18 Sep 2003 08:00:00 +0900 (JST) ※Dateヘッダ/Receivedヘッダと同じ
4. 18 Jan 2006 15:24:58 +0900 ※Dateヘッダ/曜日がないパターン
5. Wed, 5 Apr 2017 18:02:17 +0900 (JST) ※Dateヘッダ/日の0フィル無し(こっちが一般的)
6. Mon, 03 Mar 2008 12:01:34 +0900 (JST) ※Dateヘッダ/日の0フィル有り
7. Wed, 25 Jan 2006 01:52:30 +0900 ※Dateヘッダ/タイムゾーン名なし
上記は私の古いメールを漁って、タイムスタンプがどんな表記をしているのか確かめた実物です。上記の1と2はReceived:ヘッダのパターンで、3〜7はDate:ヘッダのパターンです。
具体的には1がPostfix、2が自宅サーバで受信していないメールから持ってきた詳細のわからないMTAです。3〜7はDate:ヘッダから目に付くものを持ってきたものです。
Received:ヘッダはJSTなどのTZ名の有無ぐらいしか差がありませんが、Date:ヘッダは曜日がなかったり、TZ名がなかったり、日にゼロフィルがあるなど意外とバリーションがあるのがわかります。
上記をうまくParseして、YYYY-MM-DDThh:mm:ss形式のタイムスタンプ文字列が出来たら、touchコマンドでメールファイルのModifiedを下記のように書き換えます。コマンドを実行するとメールファイルのファイルシステム上のタイムスタンプが変更されます。
[yuaiho@yuaiho.net cur]$ touch -d 2000-05-15T21:15:09 <メールファイル>
こうするとエンベロープのタイムスタンプ、つまりINTERNAL DATEをメールヘッダから読み込んだタイムスタンプに修正できます。
ファイルシステムのタイムスタンプを書き換え終わったら、シェルで書き換えたcurやnewフォルダの上位にあるのdovecotのキャッシュファイルを消します。具体的には dovecot-uidlist dovecot.index.log dovecot-keywords dovecot.index.cache の4ファイルです。curやnewなどのディレクトリを消さないようにしてください。削除後のDovecotの再起動は不要です。
-rw------- 1 yuaiho yuaiho 15 Jan 14 2021 dovecot-keywords -rw------- 1 yuaiho yuaiho 2109 Sep 26 12:11 dovecot-uidlist -rw------- 1 yuaiho yuaiho 13332 Sep 29 11:56 dovecot.index.cache -rw------- 1 yuaiho yuaiho 3484 Sep 29 11:56 dovecot.index.log
ここまで終わったら、iPhone上でそのフォルダを読み直せばさっき修整した日付になる筈です。全ファイル読み直すので時間はかかります。
iPhoneのメールのメールボックスのリスト上では修整されたのに、メールを開いた際に出てくる日付が修整されてない場合はiPhoneの「設定」→「メール」→アカウントでそのメールボックスを含むアカウントを消してから、iPhoneを再起動し、アカウントをもう一度登録すると直ります。