前回の記事で C# を使用して SMTP サーバーに接続する方法をまとめました。今回は、SMTP 認証と SSL 接続に挑戦したいと思います。
目次
目次
やりたいこと
SMTP サーバーに SSL 接続し、SMTP 認証を行うことを目指します。今回は勉強のために、SMTP コマンドを使用してサーバーと送受信を行うところから作成します。複数メールの送信、複数宛への送信、CC、BCC については今回も実装はしません。
事前準備
1.前回の記事で作成したコードを用意する
前回の記事で作成したコードを今回は使用します。導入がまだでしたら「C# で SMTP サーバーに接続したい(入門編)!!!」を参照してください。
2.SMTP サーバーの認証情報を用意する
SMTP 認証接続したい SMTP サーバーの下記情報を新たに用意してください。
・認証ユーザー名
・認証パスワード
・認証の種類(Plain, Login, CRAM-MD5)
手順概要
手順1.SMTP サーバーへ SMTP 認証をする
手順2.SMTP サーバーに SSL 接続をする
手順3.SMTP サーバーに SSL 接続と SMTP 認証を使ってメールを送信する
手順1.SMTP サーバーへ SMTP 認証をする
SMTP 認証をするクラス を実装する
新たに SMTP 認証をする Auth クラスを実装します。
public class Auth
{
/// <summary>
/// 認証方法
/// </summary>
public enum Type
{
/// <summary>
/// 認証なし
/// </summary>
None,
/// <summary>
/// Plain 認証
/// </summary>
Plain,
/// <summary>
/// Login 認証
/// </summary>
Login,
/// <summary>
/// CRAM-MD5 認証
/// </summary>
CRAM_MD5,
}
/// <summary>
/// 認証で用いるユーザー名
/// </summary>
public string userName { get; set; }
/// <summary>
/// 認証で用いるパスワード
/// </summary>
public string password { get; set; }
/// <summary>
/// 認証方法
/// </summary>
public Type type { get; set; }
/// <summary>
/// サーバー応答メッセージの開始位置
/// </summary>
const int _messageStartPos = 4;
// 認証
protected void Authenticate(Message message)
{
if (type != Type.None)
{
if (userName == "")
throw new InvalidOperationException("UserName is empty.");
if (password == "")
throw new InvalidOperationException("Password is empty.");
}
string rstr = "";
string sstr = "";
switch (type)
{
case Type.Plain:
sstr = "AUTH PLAIN";
message.SendReceive(sstr, new List<UInt16> { 334, 502 }, false, out rstr, "[S]");
// 502:Command not implemented
if (rstr.StartsWith("502")) return;
sstr = Common.GetBase64(userName + '\0' + userName + '\0' + password);
message.SendReceive(sstr, new List<UInt16> { 235 }, false, out rstr, "[C]base64");
break;
case Type.Login:
sstr = "AUTH LOGIN";
message.SendReceive(sstr, new List<UInt16> { 334, 502 }, false, out rstr, "[C]");
// 502:Command not implemented
if (rstr.StartsWith("502")) return;
sstr = Common.GetBase64(userName);
message.SendReceive(sstr, new List<UInt16> { 334 }, false, out rstr, "[C]" + userName + "; base64: ");
sstr = Common.GetBase64(password);
message.SendReceive(sstr, new List<UInt16> { 235 }, false, out rstr, "[C]" + password + "; base64: ");
break;
case Type.CRAM_MD5:
sstr = "AUTH CRAM-MD5";
message.SendReceive(sstr, new List<UInt16> { 334, 502 }, false, out rstr, "[C]");
// 502:Command not implemented
if (rstr.StartsWith("502")) return;
// AUTH CRAM-MD5
sstr = Common.GetBase64(GetMD5(rstr.Substring(_messageStartPos), userName, password));
message.SendReceive(sstr, new List<UInt16> { 235 }, false, out rstr, "[C]base64: ");
break;
case Type.None:
default:
break;
}
}
// MD5 値取得
private string GetMD5(string challenge, string userName, string password)
{
var key = (new System.Security.Cryptography.HMACMD5(Encoding.UTF8.GetBytes(password)))
.ComputeHash(System.Convert.FromBase64String(challenge));
var digest = "";
foreach (var octet in key)
{
digest += octet.ToString("x02");
}
return string.Format("{0} {1}", userName, digest);
}
}
前回の記事で実装した Smtp クラスを変更し、Auth クラスを継承させます。
/// <summary>
/// SMTP サーバーとの交信を行う
/// </summary>
public class Smtp : Auth
{
/// <summary>
/// SMTP サーバーの名前
/// </summary>
public string serverName { get; set; }
・・・以下省略・・・
}
前回の記事で実装した SMTPConnect 関数を変更し、SMTP 認証を実装します。
// SMTP 接続
private void SMTPConnect(System.IO.Stream stream, Mail mail)
{
string rstr = "";
string sstr = "";
Message message = new Message(stream, encode);
// SMTP 認証
Authenticate(message);
try
{
sstr = "MAIL FROM:<" + mail.From.Address + ">";
message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
}
catch (ApplicationException e)
{
sstr = "MAIL FROM:" + mail.From.Address;
message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
}
try
{
sstr = "RCPT TO:<" + mail.To.Address + ">";
message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
}
catch (ApplicationException e)
{
sstr = "RCPT TO:" + mail.To.Address;
message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
}
sstr = "DATA";
message.SendReceive(sstr, new List { 354 }, false, out rstr, "[C]");
// メールデータ作成
sstr = message.Create(mail).ToString();
message.SendReceive(sstr, new List { 250 }, false, out rstr, "[C]mail DATA:\r\n");
sstr = "QUIT";
message.SendReceive(sstr, new List { 221 }, false, out rstr, "[C]");
}
手順2.SMTP サーバーに SSL 接続をする
SSL 接続をするクラスを実装する
手順1で実装した Smtp クラスを変更し SSL 接続を実装します。
SSLConnect 関数を新たに作成し enableSSL を追加します。Send 関数で enableSSL の値によって SMTPConnect と SSLConnect を切り替えるようにします。SMTPConnect 関数の変更はありません。
/// <summary>
/// SMTP サーバーとの交信を行う
/// </summary>
public class Smtp : Auth
{
/// <summary>
/// SMTP サーバーの名前
/// </summary>
public string serverName { get; set; }
/// <summary>
/// SMTP サーバーの受付ポート番号
/// </summary>
public int serverPort { get; set; }
/// <summary>
/// 本文のエンコーディングに使用する IANA に登録されている名前
/// </summary>
public string encode { get; set; }
/// <summary>
/// SMTP サーバーとの接続に SSL を使用するかどうかを指定
/// </summary>
public bool enableSSL { get; set; }
/// <summary>
/// 受信タイムアウト
/// </summary>
public int readTimeout { get; set; }
/// <summary>
/// メールを送信する
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown when... プロパティ不足
/// </exception>
/// <exception cref="SocketException">
/// Thrown when... SMTPサーバー接続失敗
/// </exception>
/// <exception cref="System.Security.Authentication.AuthenticationException">
/// Thrown when... SSL 接続失敗
/// </exception>
/// <exception cref="ApplicationException">
/// Thrown when... SMTPサーバー通信エラー
/// </exception>
/// <param name="mail">送信するメール</param>
/// <returns></returns>
public void Send(Mail mail)
{
if (serverName == "")
throw new InvalidOperationException("ServerName is empty.");
if (Encoding.GetEncodings().Where(c => c.Name.ToUpper() == encode.ToUpper()).Count() == 0)
throw new InvalidOperationException("Encoding name not found(encoding: " + encode + ")");
// サーバー接続
Debug.WriteLine("Connecting...");
using (var sock = new TcpClient())
{
try
{
sock.Connect(serverName, serverPort);
Debug.WriteLine("Connected!");
Debug.WriteLine("Socket open!");
using (var stream = sock.GetStream())
{
stream.ReadTimeout = readTimeout;
try
{
string rstr = "";
string sstr = "";
Message message = new Message(stream, encode);
if (!message.Receive(new List<UInt16> { 220 }, false, out rstr))
throw new ApplicationException("Server Not Ready");
sstr = "EHLO " + System.Net.Dns.GetHostName();
message.SendReceive(sstr, new List<UInt16> { 250 }, true, out rstr, "[S]"); //無応答となるまで受信し続ける
if (enableSSL)
{
sstr = "STARTTLS";
message.SendReceive(sstr, new List<UInt16> { 220 }, false, out rstr, "[S]");
// SMTP over SSL
SSLConnect(serverName, stream, mail);
}
else
{
// SMTP
SMTPConnect(stream, mail);
}
}
finally
{
Debug.WriteLine("Socket close!");
stream.Close();
}
}
}
catch (System.Net.Sockets.SocketException e)
{
Debug.WriteLine("Socket error! code: " + e.ErrorCode.ToString() + "; " + e.Message);
throw;
}
finally
{
// サーバー接続のクローズ
Debug.WriteLine("Connection close!");
sock.Close();
}
}
}
// SSL 接続を確立する
private void SSLConnect(string serverName,
System.IO.Stream sockStream, Mail mail)
{
using (var stream = new SslStream(sockStream))
{
try
{
Debug.WriteLine("SSL authenticating...");
stream.AuthenticateAsClient(serverName);
Debug.WriteLine("SSL authenticated!");
SMTPConnect(stream, mail);
}
catch (System.Security.Authentication.AuthenticationException e)
{
Debug.WriteLine("Authentication error: " + e.Message);
if (e.InnerException != null)
{
Debug.WriteLine("Inner exception: " + e.InnerException.Message);
}
throw;
}
finally
{
Debug.WriteLine("SSL close!");
stream.Close();
}
}
}
// SMTP 接続
private void SMTPConnect(System.IO.Stream stream, Mail mail)
{
string rstr = "";
string sstr = "";
Message message = new Message(stream, encode);
// SMTP 認証
Authenticate(message);
try
{
sstr = "MAIL FROM:<" + mail.From.Address + ">";
message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
}
catch (ApplicationException e)
{
sstr = "MAIL FROM:" + mail.From.Address;
message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
}
try
{
sstr = "RCPT TO:<" + mail.To.Address + ">";
message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
}
catch (ApplicationException e)
{
sstr = "RCPT TO:" + mail.To.Address;
message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
}
sstr = "DATA";
message.SendReceive(sstr, new List { 354 }, false, out rstr, "[C]");
// メールデータ作成
sstr = message.Create(mail).ToString();
message.SendReceive(sstr, new List { 250 }, false, out rstr, "[C]mail DATA:\r\n");
sstr = "QUIT";
message.SendReceive(sstr, new List { 221 }, false, out rstr, "[C]");
}
}
手順3.SMTP サーバーに SSL 接続と SMTP 認証を使ってメールを送信する
SMTP サーバーへメールを送信してみましょう
前回の記事の手順2で作成した smtp を変更しましょう。事前準備で用意した、認証ユーザー名、認証パスワード、認証の種類(Plain, Login, CRAM-MD5)を使用します。
var smtp = new Smtp
{
serverName = "SMTP サーバー名",
serverPort = 25,
encode = "ISO-2022-JP",
readTimeout = 500,
/*----------ここから下を追加-----------*/
enableSSL = true,
userName = "認証ユーザー名",
password = "認証パスワード",
type = Auth.Type.Plain //認証の種類(Plain, Login, CRAM-MD5)
};
まとめ
C#で SMTP サーバーに対して、SSL 接続と SMTP 認証を使用して、メールを送信する関数とクラスを作成してみました。是非お試しください。
関連するページ
その他の備忘録も以下のページでまとめていますので、よろしければアクセスしてください。
以上!!