POP (Post Office Protocol)

POPはIETFにより標準化されている電子メールの受信プロトコルです。 現在はPOPのバージョン3、通称POP3が広く利用されています。ここではこの POP3の動作を説明した後に、POP3を利用したメール受信プログラムについて 解説します。

電子メールはメールサーバ間をSMTPを使って転送されます。従って、メールの 受け取り側もSMTPを使って受信すれば簡単です。しかし、受け取り側は常時接 続されているは限りません。例えば電話回線により接続されている場合もあり ます。従って、届いたメールはメールサーバ上で一時的に保持してもらい、必 要に応じて、そのメールを取り出す機構が必要となりました。それがPOPです。

POPによるメール受け取りでは、POPに対応したサーバとクライアントのあ いだで行われます。両者はTCPを利用して通信を行い、その際のポート番号は 110を利用するのが一般的です。クライアントはサーバにメールの到着を問い 合わせます。そして、メールが届いているときはそのメールを読み出します。 POPとはこのときのクライアントとサーバのあいだの通信手順を定めたもので す。

POPの手順はクライアントからサーバにメッセージを送り、サーバへのログ インを行います。そして、サーバにメールが届いているかを調べます。そして、 必要に応じてメールを取り出します。このとき、クライアントからサーバへの メッセージはUSERPASSSTATLISTRETRなどを含む40文字以下の文字列であり、それに対するサーバの 返答は成功、不成功に応じて+OK+ERRから始める文字列 となります。どちらも文字列の終わりは<CRLF>となります。

POP3の動作

POP3の動作はメール呼び出しが正当なものかどうかを調べる認証と、 その後に行われるメール呼び出しの二つステップから構成されています。

POP3の認証

メールは他人に読まれては困ります。このため、メールサーバはメール呼 び出す要求があったときに、そのメールの受取人からの呼び出しであるかを調 べる必要があります。POP3ではログインと同じで秘密のパスワードを使って判 定します。つまり、メールを呼び出す前にPOPクライアントはユーザ名とパス ワードをPOPサーバに伝えます。そして、ユーザ名とパスワードが一致したら POPサーバは、POPクライアントによるメール呼び出し要求に従いメールをPOP クライアントに送ります。

このPOP3の認証方法として、 USERPASSコマンドを使う方法で説明していきます。

POPクライアントはTCPによりPOPサーバに接続します。そして、POPクライ アントはUSERコマンドによりメールアカウント名(ログイン名)をPOP サーバにおくります。

USER メールアカウント名<CRLF>

POPサーバは返答として+OK <CRLF>というメッセージを送り返 します。なお、メールサーバ(POPサーバ)にはメールボックスがあり、POPサー バ届いたメールはこのメールボックスがためられています。しかし、メールア カウント名に対応したメールボックスがなければPOPサーバはメールを返しよ うがありません。この場合は+OK <CRLF>ではなく+ERR <CRLF>を送り返し、USERを受け取る前の状態に戻ります。

POPクライアントは+OK <CRLF>を受け取るとパスワードを送ります。

PASS パスワード<CRLF>

POPサーバは、パスワードが正しくないときは+ERR <CRLF>を 送り返し、USERを受け取る前の状態に戻ります。正しいときは +OK <CRLF>を返します。これでPOPサーバはPOPクライアントからの メール呼び出し要求を受理するようになります。

クライアント USER name
サーバ +OK
クライアント PASS password
サーバ +OK

もう一つの認証方式

USERPASSコマンドを利用した認証はパスワードがそのま まネットワークを通ることになります。従って、第3者に読まれてしまう危険 性をもっています。特に、新着メールの確認は所定時間毎に自動で行うことが 多いですが、その場合はパスワードが盗まれる可能性は高くなります。

従って、安全な認証方法が必要になり、それを実現するのがAPOPコ マンドです。これは暗号技術を利用したものであり、POPサーバとPOPクライア ント双方が共有している暗号鍵をもとに、使い捨てのパスワードを作り、それ をPOPサーバに送るという方法です。

コラム:暗号とは

コラム:UNIXの暗号技術

コラム:共通鍵暗号系と公開鍵暗号系

メールの呼び出し

POPクライアントはSTATLISTRETRDELEコマンドをPOPサーバに送ってメールを呼び出しを行います。こ れらのコマンドは認証されたPOPクライアントから以外から送っても、POPサー バはコマンドを実行してくれません。

STAT
メールボックス内のメール数と全体データ量(バイト数)の問い合わせ
LIST
メールボックス内の各メールのデータ量(バイト数)の問い合わせ
RETR
メールボックスから指定したメールを読み出す。
DELE
メールボックスから指定したメールを消去

STATコマンド

メールボックス内のメール数と全体データ量(バイト数)の 問い合わせるコマンドです。

STAT<CRLF>

POPサーバからの返答メッセージは以下のようになります。+OKnnmmは一個のスペースで区切られ、nnはメー ルボックスにたまっているメールの数、mmはそれらのメールの合計 データ量(バイト数)となります。mmの後に付加情報を追加すること もできますが、その返答メッセージの終わりは<CRLF>となる 必要があります。

+OK nn mm<CRLF>

例をあげます。これはメールボックスに2つのメールがあり、 それらの合計バイト数が320の場合です。

クライアント STAT<CRLF>
サーバ +OK 2 320<CRLF>

LISTコマンド

STATコマンドと同様に認証が完了していないと使えないコマンドで す。このLISTコマンドはメールボックス中の各メールのデータサイ ズを調べるコマンドです。

LIST<CRLF>

特定のメールのデータサイズを調べるときは、以下のようにLISTコ マンドのつぎに一個のスペースをあけて、そのメールの番号を引数として与え ます。

LIST nn<CRLF>

POPサーバは認証済みのPOPクライアントから、この コマンドを受け取ると、複数行の返答メッセージを返します。一行目は +OK<CRLF>となります。二行目以降は各メールのデータサイズ をnn mm<CRLF>の形式で返します。ここでnnはメー ルの番号、mmはそのメールのデータサイズ(バイト数)です。 そして、最後の行はメール情報の終わりを示すため.<CRLF>で 終わります。なお、メールボックスにメールがないときは +OK<CRLF>のあとに.<CRLF>を返します。

+OK<CRLF>
nn mm<CRLF> (メール数分)
.<CRLF>

例をあげます。これはメールボックスに2つのメールがあり、 それらの合計バイト数が320の場合です。

クライアント LIST<CRLF>
サーバ +OK 2 messages<CRLF>
サーバ 1 120<CRLF>
サーバ 2 200<CRLF>
サーバ .<CRLF>

特定のメールを調べるときは以下のようになります。

クライアント LIST 2<CRLF>
サーバ +OK 2 200<CRLF>
クライアント LIST 3<CRLF>
サーバ -ERR no such message, only 2 messages in maildrop<CRLF>

RETRコマンド

RETRコマンドはメールを呼び出すコマンドです。ただし、認証済み のPOPクライアントからのRETRコマンドだけが有効です。 以下のようにRETRコマンドのつぎに一個のスペースをあけて、 読み出したいメールの番号nnを引数として与えます。

RETR nn<CRLF>

POPサーバは認証済みのPOPクライアントから、RETRコマンドを受け 取ると、複数行の返答メッセージを返します。最初の行は +OK<CRLF>となります。二行目以降はメールの中身となります が、最後の行にはメール情報の終わりを示すため.<CRLF>とな ります。なお、メールボックスに該当のメールがないときは +OK<CRLF>のあとに.<CRLF>を返します。

例をみていきましょう。

クライアント RETR 1<CRLF>
サーバ +OK 120<CRLF>
<メールの中身>
.<CRLF>

DELEコマンド

未完

メールの呼び出し例

ここではメール呼び出しにおける、POPクライアントとPOPサーバの メッセージのやりとりを時間順に見てみます。

サーバ <wait for connection on TCP port 110>
クライアント <open connection>
サーバ +OK POP3 server ready <1896.697170952@dbc.mtview.ca.us>
クライアント APOP mrose c4c9334bac560ecc979e58001b3e22fb
サーバ +OK mrose's maildrop has 2 messages (320 octets)
クライアント STAT
サーバ +OK 2 320
クライアント LIST
サーバ +OK 2 messages (320 octets)
サーバ 1 120
サーバ 2 200
サーバ .
クライアント RETR 1
サーバ +OK 120 octets
サーバ <the POP3 server sends message 1>
サーバ .
クライアント DELE 1
サーバ +OK message 1 deleted
クライアント RETR 2
サーバ +OK 200 octets
サーバ <the POP3 server sends message 2>
サーバ .
クライアント DELE 2
サーバ +OK message 2 deleted
クライアント QUIT
サーバ +OK dewey POP3 server signing off (maildrop empty)
クライアント <close connection>
サーバ <wait for next connection>

SMTPクライアントプログラム例

POPの例題として、POPクライアントプログラムを示します。これは第一引 数として与えたPOPサーバから、第二引数のメールアカウント名と第三引数の パスワードにより認証を行い、そのメールアカウント宛に届いたメールをすべ て呼び出して画面に表示するものです。

通信接続部分は他のTCPクラ イアントプログラムと同じであり、重要なのはPOPによるメッセージ手順の部 分です。そのプログラム例を下記にあげます(下記のプログラムはJava言語版 です。これをC言語に直すだけです。あと、最初のメールしか呼び出してくれ ません)。

import java.io.*;
import java.lang.*;
import java.net.*;
import java.util.*;

public class POP3 {
  static final int DEFAULT_PORT = 110;
  static final String CRLF = "\r\n";
  String user;
  String password;
  Socket sock = null;
  BufferedReader input = null;
  DataOutputStream output = null;
  
  public static void main(String[] args) {
    if( args.length != 3 ) {
      System.out.println(
	 "Usage: java POP3 pop-server user password");
    }
    else {
      new POP3(args[0], args[1], args[2]);
    }
  }

  public POP3(String popserver, String user, String password) {
    this.user = user;
    this.password = password;
    try {
      connect(popserver, DEFAULT_PORT);
      login();
      retract();
      close();
    }
    catch (ProtocolException e) {
    }
  }
  
  public void connect(String popserver, int port) throws ProtocolException {
    try {
      sock = new Socket(popserver, port);
      input = new BufferedReader(new InputStreamReader(sock.getInputStream()));
      output = new DataOutputStream(sock.getOutputStream());
    } 
    catch (IOException e) {
      throw new ProtocolException("Can't connect POP3-server");
    }
  }
  
  public void login() throws ProtocolException {
    try{
      output.writeBytes("USER " + user + CRLF);
      if(responseError(input.readLine())) {
	throw new ProtocolException("Login denied");
      }
      output.writeBytes("PASS " + password + CRLF);
      if(responseError(input.readLine())) {
	throw new ProtocolException("Login denied");
      }
    } 
    catch(IOException e) {
      throw new ProtocolException("Can't communicate POP3-server") ;
    }
  }
  
  public void retract() throws ProtocolException {
    try{
      output.writeBytes("STAT" + CRLF) ;
      output.flush();
      String line = input.readLine();
      if(!responseError(line)) {
	StringTokenizer st = new StringTokenizer(line);
	st.nextToken();
	String number = null;
	output.writeBytes("RETR 1" + CRLF) ;
	output.flush();
	String buf = null;
	while((buf = input.readLine()) != null) {
	  System.out.println(buf);
	}
      } 
    } 
    catch(IOException e) {}
    throw new ProtocolException("Can't communicate POP3-server");
  }
  
  public void close() throws ProtocolException {
    try {
      output.writeBytes("QUIT" + CRLF) ;
      output.flush();
      sock.close();
    } 
    catch(IOException e) {
      throw new ProtocolException("Can't communicate POP3-server");
    }
  }
  
  private boolean responseError(String s) throws IOException {
    StringTokenizer st = new StringTokenizer(s) ;
    String stf = st.nextToken() ;
    return stf.equals("-ERR") ;
  }
}

プログラム中で重要な部分をみていきます。

下記の部分は認証を行う部分です。userにはメールアカウント名、 passwordにはパスワードが格納されています。

  public void login() throws ProtocolException {
    try{
      output.writeBytes("USER " + user + CRLF);
      if(responseError(input.readLine())) {
	throw new ProtocolException("Login denied");
      }
      output.writeBytes("PASS " + password + CRLF);
      if(responseError(input.readLine())) {
	throw new ProtocolException("Login denied");
      }
    } 
    catch(IOException e) {
      e.printStackTrace();
    }
  }

下記はメールの呼び出し部分(未完)

  public void retract() throws ProtocolException {
    try{
      output.writeBytes("STAT" + CRLF) ;
      output.flush();
      String line = input.readLine();
      if(!responseError(line)) {
	StringTokenizer st = new StringTokenizer(line);
	st.nextToken();
	String number = null;
	output.writeBytes("RETR 1" + CRLF) ;
	output.flush();
	String buf = null;
	while((buf = input.readLine()) != null) {
	  System.out.println(buf);
	}
      } 
    } 
    catch(IOException e) {}
    throw new ProtocolException("Can't communicate POP3-server");
  }