★ The Tsuchinoko News 2 (つちのこ通信2) ★

重要な話から、どうでもいいことまで。ほとんど役に立たないことを書き連ねています。

【PC】Android で Socketを使ったメール送信(1)

f:id:tsuchinoko118:20120917162432j:image:left:w260Android でメールを送信というと、ユーザーは様々なメールソフトを使えばOK。アプリを作る方は多くの場合は Intent を使って(Intent って非常に便利ですねぇ)メール画面を出してあげればOK。ただし、この手法は「メールを送信する一歩手前の画面」を用意できるだけなので、メールを送るところは操作しないといけない。
自動でメールを送信したい場合や、メールアプリを作る場合などには、これだとちょっと困る。そこでよく出てくるのが JavaMail API を使う方法だが、筆者の場合、いままでずっと Visual Studio でメール送信処理は作ってきたので( Windows だけど ) メール送信は Socket で出来るだろうと思い込んでいたりする。
長い間、放置していた古い Visual Studio のプログラムソースを開いて、やっぱり Socket だけでやってます、ということで Android でも Socket だけでメール送信は書けるはず、と挑んでみた。
ほとんど単なる移植だけでも・・・。

メール送信の基本は、インターネット上にある特定のメール送信サーバー(SMTPサーバー)に、お願いしてメールを送ってもらえばよい。メールは、「自分のメールアドレス」「相手先のメールアドレス」「件名」「本文」があれば送れるはずで、SMTPサーバーさんとお話しながら、この4項目を渡してあげればよい。はず。
それでも手順は少し面倒くさいのだが、それだけでよい。はず。

しかし実際には、ここ数年のインターネットの普及と、そのダークサイド=セキュリティのお話、なりすまし、ありとあらゆる悪意によって、ものすごくややこしい手順を踏まねばならなくなってしまった。まず、メールの受信をする。受信なのでもちろん認証あり、だ。そうすることによって、はじめてメール送信サーバーにアクセスできる。次に、メール送信サーバーにアクセスする。最近は多くの場合、認証付きだ。2回も認証をして、やっと差出人と宛先を送ると、次は、件名。この件名に日本語が使えないので、BASE64というエンコードをしてあげる。で、ようやく本文。むかしは受信がとてもややこしくて送信は送るだけ、だったのだが、昨今はこれ全部を自前で処理をしないと、メール送信アプリは作れない。たいへん面倒くさい。

ともかく膨大な量のプロシージャーというかクラスが必要になるのだが、ちょっとづつ、小出しに(笑)書き殴っていこうと思う。

1回目は最も単純なメール送信プログラムのサンプル。
実用性ゼロ(笑)

package com.example.blog;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import android.os.Bundle;
import android.app.Activity;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

		if( sendMail("smtp.server.xxx.zzz", 
					 587, 
					 "送信者の名前", 
					 "soushinsya@mail.xx.zz", 
					 "atesaki@xx.zz", 
					 "件名", 
					 "本文") == true ){

			//
			// メール送信成功
			//
			
		}else{

			//
			// メール送信失敗( orz )
			//
		}

    }

    //==============================
	//
	// Mail送信
	//
    
    public Boolean sendMail( String smtphost,
    						 int smtpport,
    						 String fromName,
    						 String fromMailAddress,
    						 String toMailAddress,
    						 String subject,
    						 String bodymsg ){

    	Boolean ret = true;

		Socket connection = null;
		BufferedReader reader = null;
		BufferedWriter writer = null;

		try {
			
			// サーバーに接続
			connection = new Socket( smtphost,smtpport );
			
			reader = new BufferedReader(
				new InputStreamReader(
					connection.getInputStream()
				)
			);

			writer = new BufferedWriter(
				new OutputStreamWriter(
					connection.getOutputStream(),"ISO2022JP"
				)
			);

			String message = reader.readLine();
			if ( !message.startsWith("220") ) 
				return false;
				
			// HELOの送信
			sendData( writer, "EHLO " + smtphost + "\r\n" );
			message = reader.readLine();
			if ( !message.startsWith("250") )
				return false;

			// MAILFROMの送信
			sendData( writer, "MAIL FROM: <" +
                                 fromMailAddress + ">\r\n" );
			message = reader.readLine();
			if ( !message.startsWith("250") ) 
				return false;

			
			// MAILTOの送信
			sendData( writer, "RCPT TO: <" + toMailAddress + ">\r\n" );
			message = reader.readLine();
			if ( !(message.startsWith("250")) && 
                             !(message.startsWith("251")) )
				return false;

			// DATA(メッセージ本体) の送信
			sendData( writer, "DATA\r\n" );
			message = reader.readLine();
			if ( !(message.startsWith("250")) && 
                             !(message.startsWith("354")) )
				return false;

				
			String header = "MIME-Version: 1.0\r\n" +
				        "Content-type: text/plain;"+
					"charset=\"iso-2022-jp\"\r\n" +
					"Content-Transfer-Encoding: 7bit\r\n" +
					"To: " + toMailAddress + "\r\n" +
					"From: =?ISO-2022-JP?B?" + 
                                base64Encode( fromName ) + "?=" +
					"<"+ fromMailAddress +">\r\n";
			
			String sndMsg = "Subject: =?ISO-2022-JP?B?" + 
                                base64Encode( subject ) +"?=\r\n" +
					header + "\r\n\r\n" 
                                + bodymsg + "\r\n" + "." + "\r\n";
								
			sendData( writer, sndMsg );
			message = reader.readLine();
			if ( !(message.startsWith("250")) && 
                             !(message.startsWith("354")) )
				return false;

			// QUITの送信
			sendData( writer, "QUIT\r\n" );
			message = reader.readLine();
			if ( !message.startsWith("221") )
				return false;

		
		} catch (UnknownHostException e) {
			//
			// サーバー 不明エラー
			//
			ret = false;
		} catch (IOException e) {
			//
			// 接続エラー
			//
			ret = false;
		} catch (Exception e) {
			//
			// その他のエラー
			//
			ret = false;
		} finally {
		
			//
			// 接続終了処理
			//
			try {
		
				reader.close();
				writer.close();
				connection.close();
				
			} catch (IOException e2) {
				ret = false;
			} catch (Exception e2) {
				ret = false;
			}

		}

		return ret;
    }

	private Boolean sendData( BufferedWriter writer,
			 String msg ) throws IOException{
		//
		// 送信する
		//

		Boolean ret = true;

		writer.write( msg );
		writer.flush();

		return ret;
	}
	
	private String base64Encode( String s ){
		//
		// BASE64エンコードを行う予定
		//
		return s;
	}

}

次回はまずこの基本を解説することにしようかな、と考え中。

f:id:tsuchinoko118:20120917162455j:image:right:w400 それでもうまくいくと、なんとなく送れたりする。複雑なセキュリティなんだけど、エラーについては無頓着のようで・・・