電子メールの仕組み

そもそも電子メールとは?

素朴な疑問、すなわち「そもそも電子メール(以下、メール)とは何か?」と いうことを考えてみます。誤解を恐れず単純に言うと、文字データ(の集 まりというか、列)の受渡しです。つまり所詮は0と1の並びの転 送です。

そのデータが、決められたルールと指示にしたがって移動(コピー)される、つ まり、いろいろなプログラムを通じて、ある時は(有線・無線を問わず)ネット ワーク上を流れたり、ファイルとしてコンピュータの中にたまってたりしている、 ということです。

この「データがバケツリレーのように転々と移され(流れて) どこかに溜る」というイメージをまずは是非もってください。

電子メールの配送の仕組み

Mail Transfer Agent

上の図は、簡略化されたメール配送フローの仕組みです。たとえば、左のPC (パソコン)のユーザが、右のPCのユーザーにメールを送ることを考えてみま す。

  1. まず、メール自体をなんらかのツールを使ってかきます。大抵 は次の配送プログラムと一緒になって Web メールなどのメールクライアントツー ルを使って書きます。自分のアドレスは事前設定されてるとしても、宛先、件 名、本文くらいは最低限自分で書くわけです。
  2. その書かれたメールを(大抵は書くのに利用したプログラム の機能を使って)「送信」します。これは内部的には、上で書かれた情報を加工 したり、設定されている情報などを加えて、サーバーに送ります。このプログラ ムの機能を MUA (Mail User Agent) と呼ぶことがあります。また、このサーバ と情報をやり取りする時にはSMTP (Simple Mail Transfer Protocol) というプ ロトコルを用いることになってます。
  3. 受け取ったサーバは、その宛先をみて、その宛先のサーバに転送します。 この時にも上述の SMTP を使います。場合によってはサーバーを複数経由しな いといけないかもしれませんが、それはサーバープログラム MTA (Mail Transfer Agent)のお仕事です。宛先をどのように知るか、というのは DNS の MX (Mail eXchenger) を見て配送する云々、ということなの ですが、詳細は割愛します。
    ここでは、インターネットでメールをやりとりするコンピュータ(サーバ)は、 誰かどこかでが事前に設定していることで、メールの交換が出来る、というこ とを確認してください。
  4. 最終的に宛先に対応するサーバに届くと、それをメールスプールというディ スク領域 にファイルとして保存します。
  5. 宛先のユーザさんは自分のPCのクライアントソフト(上述の MUA です) を使ってメールを取り込みます。この時には POP3 (あるいは IMAP4) という SMTP とは別のプロトコルを用います。何故かというと、メールを取り込むため にはそのユーザさんが確かに宛先のユーザさんであるという確認(認証)が必要 なわけで、そのためにユーザ名とパスワードをまずサーバに与えて、その上で何 通かのメールを区別しながら取り込まなくてはいけないわけで、単にメールを配 送するのとは全然役割が違うからです。
  6. とりこまれたメールは適宜加工され、見やすい(?)状態でユーザに提示されま す。

メールの形式

メールは転送する やり取りの手順 だけでなく、その 中味 (データ)の形式 についても決まりがないと、情報交換できません。例 えば日本人が漢字と仮名で「問い合わせ」と書いても、英米人は「問い合わせ」 が何かなんて分からない(そもそも読めない)わけです。 メールはインターネット上でのアプリケーションの中でもなり初期から使われ ていることから、英語をベースとした世界統一フォーマットに変換した上で送 ることに決まっています。これがまさにプロトコルなわけです。

さらにヘッダに関してはフィールド(ヘッダの要素)として; くらいを把握しておけばいいでしょう。これらもあくまでもデータの列としてネッ トワークを流れたりファイルに保存される対象である、ということを再確認して ください。

ここで大切なことは、受取人とかを実際に決めているのは、ヘッダではな くエンベロープである、ということです。実際From: や To: には、フォー マットにしたがってる限り架空のアドレスでもなんでも書けます。メーリング リストなどはこれを利用してるメールの流れを制御しています。

但し、近年は迷惑メール対策が厳しくなっいてて、あまり自由に書き換えると 迷惑メールと判定されることもあるので注意が必要です(後述)

SMTP を語る(直打ち編)

MUA を使わずに自分自身が MUA になったつもりでSMTP を語り、データとして RFC 5321,5322 にそったデータを流してみると理解が深まると思います。具体 的には liweb に unix (& CUI) 入門 に従ってログインしたのち、
telnet [serverのホスト名] smtp
とすることで試せます。例えば私が自分あてにメールを出す場合、以下のよう になります。 赤字が人(私)の入力した ところで、 緑はサーバが返して来たメッセージ、 青はコメントです。
$  telnet ma.e-chan.jp 25 
Trying 52.37.234.165...
Connected to ma.e-chan.jp.
Escape character is '^]'.
220 ma.e-chan.jp ESMTP
helo hannan-u.ac.jp   挨拶(?)
250 ma.e-chan.jp
mail from:<maechan@hannan-u.ac.jp>  エンベロープその1=送り主
250 2.1.0 Ok
rcpt to:<m@e-chan.jp>  エンベロープその2 =宛先(最重要!
250 2.1.5 Ok
data   今からデータです、という意味
354 End data with <CR><LF>.<CR><LF>
From: maechan@hannan-u.ac.jp ヘッダを打ち込む。ヘッダのフィールドの頭文字は必ず大文字で
To: m@e-chan.jp 「:」(コロン)のあとの空白も忘れずに
Subject: test via SMTP

This is a test. Please ignore?! 空行はヘッダとボディの切れ目!その後でボディを打ち込む。
. 最後は「.」(ピリオド)だけの行
250 2.0.0 Ok: queued as 4397760FC8
quit 対話の終わり
221 2.0.0 Bye
Connection closed by foreign host.

MIME (Multipurpose Internet Mail Extension)

MIME の基礎 なんかがよくまとまってます。ここでは簡単に説明しておきます。

MIME でのマルチパート

1つのメールとして、複数の画像や音声などを1まとめにするための規約で、メー ルにASCII 文字以外のデータを格納するための MIME (Multipurpose Internet Mail Extension) 規格の拡張仕様で、RFC 2112として規格化されています。

以下は、マルチパートの例として、画像を添付したメールです(一部不要なヘッ ダを切り取ってます)。

Return-Path: <maechan@hannan-u.ac.jp>
Date: Wed, 29 Oct 2003 17:18:12 +0900 (JST)
Message-Id: <20031029.171812.32173164.maechan@hannan-u.ac.jp>
To: maechan@ieee.org
Subject: attached image sample
From: Toshiyuki MAEDA <maechan@hannan-u.ac.jp>
X-Mailer: Mew version 3.2 on Emacs 20.7 / Mule 4.1
MIME-Version: 1.0
Content-Type: Multipart/Mixed;
    boundary="--Next_Part(Wed_Oct_29_17:18:12_2003_783)--"
Content-Transfer-Encoding: 7bit
X-UIDL: 8+6"!99""!?Bk!!bjp"!



----Next_Part(Wed_Oct_29_17:18:12_2003_783)--
Content-Type: Text/Plain; charset=iso-2022-jp
Content-Transfer-Encoding: 7bit

添付画像のサンプルです。

----Next_Part(Wed_Oct_29_17:18:12_2003_783)--
Content-Type: Image/Gif
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename="dum.gif"

R0lGODdhOwA2AIAAAAAAAP///ywAAAAAOwA2AAACtIyPqcvg/5icVMKLq94H+8yFy0dC4hmUqolq
6+u0FEzLFv3aIw7rCI/zGYA1HzGoOxZtyh6z6WxBo6hpTmZdPbOkLdfj/V7C4gi2jE4DUmQx+6x+
w8vDZLpud+Pz2Z9Qv8cHlbAmlDKlUGh4SNSw2NE48vjjpEg4mfhxKYl5YunXKfLZESrqWMoxGog6
c8paoSr3OhEbOwuaeNvKqeu62cv7CyyMO1x8bCyLbFybTAyphqRQAAA7

----Next_Part(Wed_Oct_29_17:18:12_2003_783)----

また次は、マルチパートの別の有用な例である、PGP 署名したメールです(一 部不要なヘッダを切り取ってます)。

Return-Path: <maechan@hannan-u.ac.jp>
Date: Wed, 29 Oct 2003 17:11:28 +0900 (JST)
Message-Id: <20031029.171128.119854785.maechan@hannan-u.ac.jp>
To: maechan@ieee.org
Subject: PGP signature sample
From: Toshiyuki MAEDA <maechan@hannan-u.ac.jp>
X-Mailer: Mew version 3.2 on Emacs 20.7 / Mule 4.1
MIME-Version: 1.0
Content-Type: Multipart/Signed;
    protocol="application/pgp-signature";
    micalg=pgp-sha1;
    boundary="--Security_Multipart(Wed_Oct_29_17:11:28_2003_659)--"
Content-Transfer-Encoding: 7bit
X-UIDL: E\A"!om)!!:VJ"!QBQ!!



----Security_Multipart(Wed_Oct_29_17:11:28_2003_659)--
Content-Type: Text/Plain; charset=iso-2022-jp
Content-Transfer-Encoding: 7bit

PGP の署名の例です。

----Security_Multipart(Wed_Oct_29_17:11:28_2003_659)--
Content-Type: application/pgp-signature
Content-Transfer-Encoding: 7bit

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (GNU/Linux)

iD8DBQA/n3Y5R1DOJaOzii0RAh+9AJ9ZLBwyqzzPcFx7CLwaL4QJRhcQJgCfb5SG
NuGDQzmivytG8nv7HTC/VNg=
=utS/
-----END PGP SIGNATURE-----

----Security_Multipart(Wed_Oct_29_17:11:28_2003_659)----

迷惑メール対策:OB25B, SPF, DKIM

近年、ボットなどによる迷惑メール発信が量的に無視出来ない状況で、メール サーバはその正当性を保証する必要にせまられています。それらの一部につい て、キーワードだけ紹介します。 SPF, DKIM, DMARC は DNS の txt レコードに必要な情報を登録することで実 現します。DNS レコードを書き換えられるのはそのドメインのオーナー 以外ありえないので、そこで正当性を担保しているというわけ です。ちなみに e-chan.jp は SPF も DKIM も対策済です(設定は結構大変だっ た)。

応用(発展):メールをフィルタする、という考え方

以上、メールの仕組みを知ると、いろんなことが出来ます。

本講座の小テスト・課題提出システム lect@e-chan.jp もそうですし、他にも 例えば、「XXXX site」「Get Viagra!」「未承諾広告」のような、読みたくも 無いメールを自動的に捨てるとか、"From:" が music-store-news@amazon.co.jp だったらこれは広告なのでとりあえず置いて おくが、転送はしない、などのことが、比較的簡単なプログラムによって実現 できます。

もちろん、本格的に SPAM のフィルタリングをしようとすると、 込み入った処理が必要になります。興味ある人は、 スパムへの対策 ---A Plan for Spam 等を読んでみてください。さらに言 うと、近年では Deep Learningによる振分けも行われていると思われます。
以下はその一例(拙作)で、メールサーバ上のホームディレクトリ直下の .forward という名前のファイルに、
"|/home/maechan/bin/sel-forw /home/maechan/lib/flist maesub"
として指定しているプログラムです。指示は "" で囲み、"|" (縦棒?パイプの 意味)は次に指定されてるのがプログラムであり、そのプログラムの標準入力へ データが流れていく、という印です。
多くの UNIX 系のサーバでは、各ユーザのホームディレクトリ に .forward というファイルをつくっておくと、スプールする時にこ のファイルをチェックして、その指示にしたがいます。.forward では転送先の アドレスを書くことでそのサーバからさらに別のところにメールを転送させた り、プログラムを指定することで、そのプログラムにメールを読みこませて処 理させることが出来ます。
説明は…略させてもらいますので、perl の勉強がてら読んでみてください (?!) #! /usr/bin/perl # (C) Toshiyuki MAEDA MCMXCIX,MM,MMI # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # a tiny tool for selected forwarding by denoting KILL from # Updated on 22/Dec/1999 by maechan # Updated on 14/Feb/2000 by maechan@heisei-u.ac.jp # Updated on 23/Jan/2001 by maechan@heisei-u.ac.jp # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # PREDEFINED Var. # # $SENDMAIL_COMMAND = "/usr/sbin/sendmail.8.11.6 -oi " ; # send only $SENDMAIL_COMMAND = "/usr/sbin/sendmail " ; # send only $MaxLine = 150; # Max. lines to be snipped # # pre-check # die "Usage: $0 <kill-file> <forward addr.> " if ($#ARGV != 1) ; open (KILLFILE,$ARGV[0]) || die "Cannot open kill file" ; # # get kill list # Line format: [FT] <(part of) address> # F = From, T = To/Cc # while (<KILLFILE>) { chop; next if /^#/ ; if (/^F /) { split; push @FKL, $_[1]; } if (/^T /) { split; push @TKL, $_[1]; } } # foreach $i (@FKL) { print $i." deshu\n" ; } # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # # get mail headers # $Received = ""; $ExistTo=0; $/ = "\n\n"; $* = 1; $_ = <STDIN>; # get all headers by 3 lines above $Headers = $_ ; # save headers chop; s/\n(\s)/\r$1/g; if (/^Sender: (.*)\n/i) { $Sender = $1; $Sender =~ s/\r/\n/g; } if (/^Received: (.*)\n/i) { $Received .= $1; $Received =~ s/\r/ /g; } if (/^To: (.*)\n/i) { $To = $1; $To =~ s/\r/\n/g; $ExistTo=1; } if (/^Cc: (.*)\n/i) { $Cc = $1; $Cc =~ s/\r/\n/g; } if (/^From: (.*)\n/i) { $From = $1; $From =~ s/\r/\n/g; } if (/^Date: (.*)\n/i) { $Date = $1; $Date =~ s/\r/\n/g; } if (/^Subject: (.*)\n/i) { $Subject = $1; $Subject =~ s/\r/\n/g; } if (/^Message-id: (.*)\n/i) { $Mesid = $1; $Mesid =~ s/\r/\n/g; } if (/^Content-Type: (.*)\n/i) { $CType = $1; $CType =~ s/\r/\n/g; } $/ = "\n"; # # AGAINST SPAMs ... # exit if ($ExistTo = 0) ; # no "To" may be a spam. exit if ($Subject =~ /NON-MEMBER/) ; # ukml-specific # # cut spam-like mails # exit if ($CType =~ /[Gg][Bg]2312/); exit if ($CType =~ /\-[Kk][Rr]/); exit if ($CType =~ /[Kk][Ss]_[Cc]_5601\-1987/); exit if ($Subject =~ /=\?[Ii][Ss][Oo]\-2022\-[Jj][Pp]\?B\?GyRCTCQ+NUcnOS05cCIoGyhC?=/); exit if ($Subject =~ /=\?[Ii][Ss][Oo]\-2022\-[Jj][Pp]\?B\?GyRCJDckOCRfJEgkYiRiJE4lMyVpJVwlbCE8JTclZyVzGyhK\?\=/); exit if ($Subject =~ /=\?[Ii][Ss][Oo]\-2022\-[Jj][Pp]\?B\?GyRCTCQ\+NU/); exit if ($Subject =~ /\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ /); # # check To,Cc # for $i ($To,$Cc,$Received) { # just modified for 2-line From (;^_^) $i =~ s/\n//g ; foreach $j (@TKL) { exit if ($i =~ /$j/i) ; } } for $i ($From) { # just modified for 2-line From (;^_^) $i =~ s/\n//g ; foreach $j (@FKL) { exit if ($i =~ /$j/i) ; } } # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # # do the rest # open(SM, "| $SENDMAIL_COMMAND ".$ARGV[1]) || die; print SM "Message-id: $Mesid\n" if ("x$Mesid" ne "x") ; print SM "Date: $Date\n" if ("x$Date" ne "x") ; print SM "To: $To\n" if ("x$To" ne "x") ; print SM "Cc: $Cc\n" if ("x$Cc" ne "x") ; print SM "From: $From\n" if ("x$From" ne "x") ; print SM "Subject: $Subject\n\n" if ("x$Subject" ne "x") ; $count = 0; while (<STDIN>) { print SM; $count++ unless (/^$/) ; if ($count > $MaxLine) { print SM "<!-- -- rest snipped... -- -->\n" ; exit; } }
前田としゆき / maechan@hannan-u.ac.jp

講義用スタイル
印刷用スタイル (開いてから、ページを再度更新してください)