DOBON.NET

Socketを使ってSMTPでメールを送信する

注意:ここで紹介しているコードを実際に使用する場合は、SMTPサーバー、送信者、送信先などを必ず変更してください。

SMTPでメールを送信する場合、「SMTPでメールを送信する」で紹介したように、SmtpClientクラスSmtpMailクラスを使えば簡単です。しかしここでは、あえてSocketを使用して直接SMTPサーバーとやり取りをしてメールを送信することに挑戦します。

この場合はもちろん、SMTPやMIMEに関する知識が必要です。SMTPについてRFCではRFC821(SIMPLE MAIL TRANSFER PROTOCOL)で、さらにESMTPについてはRFC1869(SMTP Service Extensions)などで定義されています。

ここでは、ソケットを使ってSMTPメール送信するサンプルのみを示します。このサンプルは、コンソールアプリケーションとして実行できるようになっています。使い方は、ProgramクラスのMainメソッドをご覧ください。

このサンプルは、SMTPとプログラミングの知識がすでにある程度ある方(あるいは、ご自分で勉強できる方)の参考になればと思い作成したものです。よって、実用には耐えませんし、申し訳ありませんが、コード内のコメント以外に詳しい説明もしません。

AUTH PLAIN、AUTH LOGIN、CRAM-MD5での認証に対応しています。.NET Framework 2.0以上で動作すると思いますが、CRAM-MD5での認証が必要なければ、その部分(HMACMD5クラスを使っている箇所)を削除すれば、すべてのバージョンの.NET Frameworkで動作すると思います。

VB.NET
コードを隠すコードを選択
Imports System.Net.Sockets
Imports System.Text

Namespace Dobon.Mail
    Public Class Program
        ''' <summary>
        ''' テスト用のエントリポイント
        ''' </summary>
        Public Shared Sub Main()
            'Debug.Writeでコンソールに出力するようにする
            System.Diagnostics.Debug.Listeners.Clear()
            System.Diagnostics.Debug.Listeners.Add( _
                New System.Diagnostics.ConsoleTraceListener())

            'SmtpMailMessageオブジェクトを作成する
            Dim mail As New SmtpMailMessage()
            'From
            mail.From = "xxx@xxx.xxx"
            'To
            mail.[To] = "xxx@xxx.xxx"
            'Subject
            mail.Subject = "テスト"
            '本文
            mail.Body = "これはテストです。"

            'Smtpオブジェクトを作成する
            Dim smtp As New Smtp()
            'SMTPサーバー名
            smtp.SmtpServer = "localhost"
            'ポート番号
            smtp.SmtpPort = 25
            '認証が必要な時は、認証方法を設定する
            smtp.AuthMethod = SmtpAuthMethod.CramMd5
            smtp.AuthUserName = "user"
            smtp.AuthPassword = "pass"
            'メールを送信する
            smtp.Send(mail)

            Console.ReadLine()
        End Sub
    End Class

    ''' <summary>
    ''' 送信用のメールメッセージを表すクラス
    ''' </summary>
    Public Class SmtpMailMessage
        Private _from As String = ""
        ''' <summary>
        ''' 送信者のメールアドレス
        ''' </summary>
        Public Property From() As String
            Get
                Return _from
            End Get
            Set(value As String)
                _from = value
            End Set
        End Property

        Private _to As String = ""
        ''' <summary>
        ''' 送信先のメールアドレス
        ''' </summary>
        Public Property [To]() As String
            Get
                Return _to
            End Get
            Set(value As String)
                _to = value
            End Set
        End Property

        Private _subject As String = ""
        ''' <summary>
        ''' メールの件名
        ''' </summary>
        Public Property Subject() As String
            Get
                Return _subject
            End Get
            Set(value As String)
                _subject = value
            End Set
        End Property

        Private _body As String = ""
        ''' <summary>
        ''' メールの本文
        ''' </summary>
        Public Property Body() As String
            Get
                Return _body
            End Get
            Set(value As String)
                _body = value
            End Set
        End Property

        Private _encoding As Encoding = Encoding.GetEncoding(50220)
        ''' <summary>
        ''' 使用するエンコーディング
        ''' </summary>
        Public Property Encoding() As Encoding
            Get
                Return _encoding
            End Get
            Set(value As Encoding)
                _encoding = value
            End Set
        End Property
    End Class

    ''' <summary>
    ''' Smtpクラスで発生するエラーを表したクラス
    ''' </summary>
    Public Class SmtpException
        Inherits Exception
        Public Sub New()
            MyBase.New()
        End Sub
        Public Sub New(message As String)
            MyBase.New(message)
        End Sub
        Public Sub New(message As String, inner As Exception)
            MyBase.New(message, inner)
        End Sub
    End Class

    ''' <summary>
    ''' SMTPの認証方法を表す列挙体
    ''' </summary>
    Public Enum SmtpAuthMethod
        ''' <summary>
        ''' 認証を行わない
        ''' </summary>
        None
        ''' <summary>
        ''' AUTH PLAIN
        ''' </summary>
        Plain
        ''' <summary>
        ''' AUTH LOGIN
        ''' </summary>
        Login
        ''' <summary>
        ''' AUTH CRAM-MD5
        ''' </summary>
        CramMd5
    End Enum

    ''' <summary>
    ''' SMTPでメールを送信するためのクラス
    ''' </summary>
    Public Class Smtp
        Implements IDisposable
#Region "プロパティ"
        Private _smtpServer As String = ""
        ''' <summary>
        ''' SMTPサーバー
        ''' </summary>
        Public Property SmtpServer() As String
            Get
                Return _smtpServer
            End Get
            Set(value As String)
                _smtpServer = value
            End Set
        End Property

        Private _smtpPort As Integer = 25
        ''' <summary>
        ''' ポート番号
        ''' </summary>
        Public Property SmtpPort() As Integer
            Get
                Return _smtpPort
            End Get
            Set(value As Integer)
                _smtpPort = value
            End Set
        End Property

        Private _authUserName As String = ""
        ''' <summary>
        ''' 認証で用いるユーザー名
        ''' </summary>
        Public Property AuthUserName() As String
            Get
                Return _authUserName
            End Get
            Set(value As String)
                _authUserName = value
            End Set
        End Property

        Private _authPassword As String = ""
        ''' <summary>
        ''' 認証で用いるパスワード
        ''' </summary>
        Public Property AuthPassword() As String
            Get
                Return _authPassword
            End Get
            Set(value As String)
                _authPassword = value
            End Set
        End Property

        Private _authMethod As SmtpAuthMethod = SmtpAuthMethod.None
        ''' <summary>
        ''' 使用する認証方法
        ''' </summary>
        Public Property AuthMethod() As SmtpAuthMethod
            Get
                Return _authMethod
            End Get
            Set(value As SmtpAuthMethod)
                _authMethod = value
            End Set
        End Property

        Private _heloName As String = System.Net.Dns.GetHostName()
        ''' <summary>
        ''' EHLOで送信するドメイン名
        ''' </summary>
        Public Property HeloName() As String
            Get
                Return _heloName
            End Get
            Set(value As String)
                _heloName = value
            End Set
        End Property
#End Region

#Region "フィールド"
        Private _socket As TcpClient
        Private _stream As NetworkStream
        Private _reader As System.IO.StreamReader
        Private _writer As System.IO.StreamWriter
        'SMTPサーバーから受信したメッセージ
        Private _receivedMessage As String
        'SMTPサーバーから受信したReply Code
        Private _receivedCode As Integer
        Private _encoding As Encoding
#End Region

#Region "パブリックメソッド"
        ''' <summary>
        ''' メールを送信する
        ''' </summary>
        ''' <param name="mail">送信するメール</param>
        Public Sub Send(mail As SmtpMailMessage)
            '初期設定
            _encoding = mail.Encoding

            Try
                'SMTPサーバーと接続する
                Connect()

                'EHLO
                SendHeloCommand(HeloName)
                'AUTH
                If AuthMethod <> SmtpAuthMethod.None Then
                    Authenticate()
                End If
                'MAIL FROM
                SendMailFromCommand(mail.From)
                'RCPT TO
                SendRcptToCommand(mail.[To])
                'DATA
                SendDataCommand(mail)
                'QUIT
                SendQuitCommand()
            Finally
                '切断する
                Close()
            End Try
        End Sub

        ''' <summary>
        ''' SMTPサーバーと接続する
        ''' </summary>
        Public Sub Connect()
            If _socket IsNot Nothing Then
                Close()
            End If

            'TcpClientオブジェクトを作成する
            _socket = New TcpClient()

            'SMTPサーバーと接続する
            _socket.Connect(SmtpServer, SmtpPort)
            'サーバーとデータの送受信を行うストリームを取得する
            _stream = _socket.GetStream()
            _reader = New System.IO.StreamReader(_stream, _encoding)
            _writer = New System.IO.StreamWriter(_stream, _encoding)

            'サーバーからのはじめのメッセージを受信
            ReceiveData()
            If _receivedCode <> 220 Then
                Throw New SmtpException(_receivedMessage)
            End If
        End Sub

        ''' <summary>
        ''' SMTPサーバーに文字列を送信し、応答を受信する
        ''' </summary>
        ''' <param name="message">送信する文字列</param>
        ''' <returns>受信した応答</returns>
        Public Function SendAndReceiveData(message As String) As String
            If _socket Is Nothing Then
                Throw New SmtpException("サーバーに接続していません。")
            End If

            SendData(message)
            ReceiveData()

            Return _receivedMessage
        End Function

        ''' <summary>
        ''' SMTPサーバーとの接続を終了する
        ''' </summary>
        Public Sub Close()
            Dispose()
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            If Not (_reader Is Nothing) Then
                _reader.Close()
            End If
            If Not (_writer Is Nothing) Then
                _writer.Close()
            End If
            If Not (_stream Is Nothing) Then
                _stream.Close()
            End If
            If Not (_socket Is Nothing) Then
                _socket.Close()
            End If
            _socket = Nothing
        End Sub
#End Region

#Region "プライベートメソッド"
        'サーバーからデータを受信する
        Private Sub ReceiveData()
            Dim buffer As New StringBuilder()
            Dim line As String
            Do
                '1行読み取る
                line = _reader.ReadLine()
                If line Is Nothing Then
                    Exit Do
                End If
                buffer.Append(line).Append(vbCrLf)
                '表示
                System.Diagnostics.Debug.WriteLine("S: " & line)
            Loop While System.Text.RegularExpressions.Regex.IsMatch( _
                line, "^\d+-")

            '受信したメッセージとコードをフィールドに入れる
            _receivedMessage = buffer.ToString()
            _receivedCode = Integer.Parse(_receivedMessage.Substring(0, 3))
        End Sub

        'サーバーにデータを送信する
        Private Sub SendData(str As String)
            '送信
            _writer.Write(str)
            _writer.Flush()

            '表示
            System.Diagnostics.Debug.Write("C: " & str)
        End Sub

        'エンコードし、Base64に変換
        Private Function GetBase64String(str As String) As String
            Return Convert.ToBase64String(_encoding.GetBytes(str))
        End Function

        'EHLOコマンドを送る
        Private Sub SendHeloCommand(domainName As String)
            SendAndReceiveData("EHLO " & domainName & vbCrLf)
            If _receivedCode <> 250 Then
                Throw New SmtpException(_receivedMessage)
            End If
        End Sub

        'MAIL FROMコマンドを送る
        Private Sub SendMailFromCommand(fromAddress As String)
            SendAndReceiveData("MAIL FROM:<" & fromAddress & ">" & vbCrLf)
            If _receivedCode <> 250 Then
                Throw New SmtpException(_receivedMessage)
            End If
        End Sub

        'RCPT TOコマンドを送る
        Private Sub SendRcptToCommand(toAddress As String)
            SendAndReceiveData("RCPT TO:<" & toAddress & ">" & vbCrLf)
            If _receivedCode < 250 OrElse 251 < _receivedCode Then
                Throw New SmtpException(_receivedMessage)
            End If
        End Sub

        'DATAコマンドを送る
        Private Sub SendDataCommand(mail As SmtpMailMessage)
            SendAndReceiveData("DATA" & vbCrLf)
            If _receivedCode <> 354 Then
                Throw New SmtpException(_receivedMessage)
            End If

            '送信データを作成する
            Dim data As String = CreateSendStringDataFromMessage(mail)
            SendAndReceiveData(data)
            If _receivedCode <> 250 Then
                Throw New SmtpException(_receivedMessage)
            End If
        End Sub

        'SmtpMailMessageをDATAコマンドで送信する文字列に変換する
        Private Function CreateSendStringDataFromMessage( _
                mail As SmtpMailMessage) As String
            Dim data As New StringBuilder()

            data.Append("From: ").Append(mail.From).Append(vbCrLf)
            data.Append("To: ").Append(mail.[To]).Append(vbCrLf)
            '件名をBase64でエンコード
            data.Append("Subject: =?" & _encoding.BodyName & "?B?"). _
                Append(GetBase64String(mail.Subject)).Append("?="). _
                Append(vbCrLf)
            data.Append("MIME-Version: 1.0" & vbCrLf)
            data.Append("Content-Transfer-Encoding: 7bit" & vbCrLf)
            data.Append("Content-Type: text/plain; charset=" & _
                        _encoding.BodyName & vbCrLf)

            data.Append(vbCrLf).Append(mail.Body.Replace( _
                vbCrLf & "." & vbCrLf, vbCrLf & ".." & vbCrLf))

            data.Append(vbCrLf & "." & vbCrLf)

            Return data.ToString()
        End Function

        'QUITコマンドを送る
        Private Sub SendQuitCommand()
            SendAndReceiveData("QUIT" & vbCrLf)
            If _receivedCode <> 221 Then
                Throw New SmtpException(_receivedMessage)
            End If
        End Sub

        '認証を行う
        Private Sub Authenticate()
            Select Case AuthMethod
                Case SmtpAuthMethod.Plain
                    AuthenticateWithAuthPlain()
                    Exit Select
                Case SmtpAuthMethod.Login
                    AuthenticateWithAuthLogin()
                    Exit Select
                Case SmtpAuthMethod.CramMd5
                    AuthenticateWithCramMd5()
                    Exit Select
            End Select
        End Sub

        'AUTH PLAINで認証を行う
        Private Sub AuthenticateWithAuthPlain()
            SendAndReceiveData("AUTH PLAIN" & vbCrLf)
            If _receivedCode <> 334 Then
                If _receivedCode = 502 Then
                    '認証の必要なし
                    Return
                End If
                Throw New SmtpException(_receivedMessage)
            End If

            Dim str As String = AuthUserName & ControlChars.NullChar & _
                AuthUserName & ControlChars.NullChar & AuthPassword
            SendAndReceiveData(GetBase64String(str) & vbCrLf)
            If _receivedCode <> 235 Then
                Throw New SmtpException(_receivedMessage)
            End If
        End Sub

        'AUTH LOGINで認証を行う
        Private Sub AuthenticateWithAuthLogin()
            SendAndReceiveData("AUTH LOGIN" & vbCrLf)
            If _receivedCode <> 334 Then
                If _receivedCode = 502 Then
                    '認証の必要なし
                    Return
                End If
                Throw New SmtpException(_receivedMessage)
            End If

            SendAndReceiveData(GetBase64String(AuthUserName) & vbCrLf)
            If _receivedCode <> 334 Then
                Throw New SmtpException(_receivedMessage)
            End If

            SendAndReceiveData(GetBase64String(AuthPassword) & vbCrLf)
            If _receivedCode <> 235 Then
                Throw New SmtpException(_receivedMessage)
            End If
        End Sub

        'CRAM-MD5で認証を行う
        Private Sub AuthenticateWithCramMd5()
            SendAndReceiveData("AUTH CRAM-MD5" & vbCrLf)
            If _receivedCode <> 334 Then
                If _receivedCode = 502 Then
                    '認証の必要なし
                    Return
                End If
                Throw New SmtpException(_receivedMessage)
            End If

            SendAndReceiveData(CreateCramMd5ResponseString( _
                _receivedMessage.Substring(4), AuthUserName, AuthPassword) _
                & vbCrLf)
            If _receivedCode <> 235 Then
                Throw New SmtpException(_receivedMessage)
            End If
        End Sub

        'CRAM-MD5で返す文字列を計算する
        Private Shared Function CreateCramMd5ResponseString( _
                challenge As String, username As String, password As String) _
                As String
            'デコードする
            Dim decCha As Byte() = Convert.FromBase64String(challenge)
            'passwordをキーとしてHMAC-MD5で暗号化する
            Dim hmacMd5 As New System.Security.Cryptography.HMACMD5( _
                Encoding.UTF8.GetBytes(password))
            Dim encCha As Byte() = hmacMd5.ComputeHash(decCha)
            hmacMd5.Clear()
            '16進数の文字列にする
            Dim hexCha As String = BitConverter.ToString(encCha). _
                Replace("-", "").ToLower()
            'usernameを付ける
            hexCha = username & " " & hexCha
            'Base64で文字列にする
            Return Convert.ToBase64String(Encoding.UTF8.GetBytes(hexCha))
        End Function
#End Region
    End Class

End Namespace
C#
コードを隠すコードを選択
using System;
using System.Net.Sockets;
using System.Text;

namespace Dobon.Mail
{
    public class Program
    {
        /// <summary>
        /// テスト用のエントリポイント
        /// </summary>
        public static void Main()
        {
            //Debug.Writeでコンソールに出力するようにする
            System.Diagnostics.Debug.Listeners.Clear();
            System.Diagnostics.Debug.Listeners.Add(
                new System.Diagnostics.ConsoleTraceListener());

            //SmtpMailMessageオブジェクトを作成する
            SmtpMailMessage mail = new SmtpMailMessage();
            //From
            mail.From = "xxx@xxx.xxx";
            //To
            mail.To = "xxx@xxx.xxx";
            //Subject
            mail.Subject = "テスト";
            //本文
            mail.Body = "これはテストです。";

            //Smtpオブジェクトを作成する
            Smtp smtp = new Smtp();
            //SMTPサーバー名
            smtp.SmtpServer = "localhost";
            //ポート番号
            smtp.SmtpPort = 25;
            //認証が必要な時は、認証方法を設定する
            smtp.AuthMethod = SmtpAuthMethod.CramMd5;
            smtp.AuthUserName = "user";
            smtp.AuthPassword = "pass";
            //メールを送信する
            smtp.Send(mail);

            Console.ReadLine();
        }
    }

    /// <summary>
    /// 送信用のメールメッセージを表すクラス
    /// </summary>
    public class SmtpMailMessage
    {
        private string _from = "";
        /// <summary>
        /// 送信者のメールアドレス
        /// </summary>
        public string From
        {
            get
            {
                return _from;
            }
            set
            {
                _from = value;
            }
        }

        private string _to = "";
        /// <summary>
        /// 送信先のメールアドレス
        /// </summary>
        public string To
        {
            get
            {
                return _to;
            }
            set
            {
                _to = value;
            }
        }

        private string _subject = "";
        /// <summary>
        /// メールの件名
        /// </summary>
        public string Subject
        {
            get
            {
                return _subject;
            }
            set
            {
                _subject = value;
            }
        }

        private string _body = "";
        /// <summary>
        /// メールの本文
        /// </summary>
        public string Body
        {
            get
            {
                return _body;
            }
            set
            {
                _body = value;
            }
        }

        private Encoding _encoding = Encoding.GetEncoding(50220);
        /// <summary>
        /// 使用するエンコーディング
        /// </summary>
        public Encoding Encoding
        {
            get
            {
                return _encoding;
            }
            set
            {
                _encoding = value;
            }
        }
    }

    /// <summary>
    /// Smtpクラスで発生するエラーを表したクラス
    /// </summary>
    public class SmtpException : Exception
    {
        public SmtpException()
            : base()
        {
        }
        public SmtpException(string message)
            : base(message)
        {
        }
        public SmtpException(string message, Exception inner)
            : base(message, inner)
        {
        }
    }

    /// <summary>
    /// SMTPの認証方法を表す列挙体
    /// </summary>
    public enum SmtpAuthMethod
    {
        /// <summary>
        /// 認証を行わない
        /// </summary>
        None,
        /// <summary>
        /// AUTH PLAIN
        /// </summary>
        Plain,
        /// <summary>
        /// AUTH LOGIN
        /// </summary>
        Login,
        /// <summary>
        /// AUTH CRAM-MD5
        /// </summary>
        CramMd5
    }

    /// <summary>
    /// SMTPでメールを送信するためのクラス
    /// </summary>
    public class Smtp : IDisposable
    {
        #region プロパティ
        private string _smtpServer = "";
        /// <summary>
        /// SMTPサーバー
        /// </summary>
        public string SmtpServer
        {
            get
            {
                return _smtpServer;
            }
            set
            {
                _smtpServer = value;
            }
        }

        private int _smtpPort = 25;
        /// <summary>
        /// ポート番号
        /// </summary>
        public int SmtpPort
        {
            get
            {
                return _smtpPort;
            }
            set
            {
                _smtpPort = value;
            }
        }

        private string _authUserName = "";
        /// <summary>
        /// 認証で用いるユーザー名
        /// </summary>
        public string AuthUserName
        {
            get
            {
                return _authUserName;
            }
            set
            {
                _authUserName = value;
            }
        }

        private string _authPassword = "";
        /// <summary>
        /// 認証で用いるパスワード
        /// </summary>
        public string AuthPassword
        {
            get
            {
                return _authPassword;
            }
            set
            {
                _authPassword = value;
            }
        }

        private SmtpAuthMethod _authMethod = SmtpAuthMethod.None;
        /// <summary>
        /// 使用する認証方法
        /// </summary>
        public SmtpAuthMethod AuthMethod
        {
            get
            {
                return _authMethod;
            }
            set
            {
                _authMethod = value;
            }
        }

        private string _heloName = System.Net.Dns.GetHostName();
        /// <summary>
        /// EHLOで送信するドメイン名
        /// </summary>
        public string HeloName
        {
            get
            {
                return _heloName;
            }
            set
            {
                _heloName = value;
            }
        }
        #endregion

        #region フィールド
        private TcpClient _socket;
        private NetworkStream _stream;
        private System.IO.StreamReader _reader;
        private System.IO.StreamWriter _writer;
        //SMTPサーバーから受信したメッセージ
        private string _receivedMessage;
        //SMTPサーバーから受信したReply Code
        private int _receivedCode;
        private Encoding _encoding;
        #endregion

        #region パブリックメソッド
        /// <summary>
        /// メールを送信する
        /// </summary>
        /// <param name="mail">送信するメール</param>
        public void Send(SmtpMailMessage mail)
        {
            //初期設定
            _encoding = mail.Encoding;

            try
            {
                //SMTPサーバーと接続する
                Connect();

                //EHLO
                SendHeloCommand(HeloName);
                //AUTH
                if (AuthMethod != SmtpAuthMethod.None)
                    Authenticate();
                //MAIL FROM
                SendMailFromCommand(mail.From);
                //RCPT TO
                SendRcptToCommand(mail.To);
                //DATA
                SendDataCommand(mail);
                //QUIT
                SendQuitCommand();
            }
            finally
            {
                //切断する
                Close();
            }
        }

        /// <summary>
        /// SMTPサーバーと接続する
        /// </summary>
        public void Connect()
        {
            if (_socket != null)
                Close();

            //TcpClientオブジェクトを作成する
            _socket = new TcpClient();

            //SMTPサーバーと接続する
            _socket.Connect(SmtpServer, SmtpPort);
            //サーバーとデータの送受信を行うストリームを取得する
            _stream = _socket.GetStream();
            _reader = new System.IO.StreamReader(_stream, _encoding);
            _writer = new System.IO.StreamWriter(_stream, _encoding);

            //サーバーからのはじめのメッセージを受信
            ReceiveData();
            if (_receivedCode != 220)
                throw new SmtpException(_receivedMessage);
        }

        /// <summary>
        /// SMTPサーバーに文字列を送信し、応答を受信する
        /// </summary>
        /// <param name="message">送信する文字列</param>
        /// <returns>受信した応答</returns>
        public string SendAndReceiveData(string message)
        {
            if (_socket == null)
                throw new SmtpException("サーバーに接続していません。");

            SendData(message);
            ReceiveData();

            return _receivedMessage;
        }

        /// <summary>
        /// SMTPサーバーとの接続を終了する
        /// </summary>
        public void Close()
        {
            Dispose();
        }

        public void Dispose()
        {
            if (_reader != null)
            {
                _reader.Close();
            }
            if (_writer != null)
            {
                _writer.Close();
            }
            if (_stream != null)
            {
                _stream.Close();
            }
            if (_socket != null)
            {
                _socket.Close();
            }
            _socket = null;
        }
        #endregion

        #region プライベートメソッド
        //サーバーからデータを受信する
        private void ReceiveData()
        {
            StringBuilder buffer = new StringBuilder();
            string line;
            do
            {
                //1行読み取る
                line = _reader.ReadLine();
                if (line == null)
                {
                    break;
                }
                buffer.Append(line).Append("\r\n");
                //表示
                System.Diagnostics.Debug.WriteLine("S: " + line);
            }
            while (System.Text.RegularExpressions.Regex.IsMatch(
                    line, @"^\d+-"));

            //受信したメッセージとコードをフィールドに入れる
            _receivedMessage = buffer.ToString();
            _receivedCode = int.Parse(_receivedMessage.Substring(0, 3));
        }

        //サーバーにデータを送信する
        private void SendData(string str)
        {
            //送信
            _writer.Write(str);
            _writer.Flush();

            //表示
            System.Diagnostics.Debug.Write("C: " + str);
        }

        //エンコードし、Base64に変換
        private string GetBase64String(string str)
        {
            return Convert.ToBase64String(_encoding.GetBytes(str));
        }

        //EHLOコマンドを送る
        private void SendHeloCommand(string domainName)
        {
            SendAndReceiveData("EHLO " + domainName + "\r\n");
            if (_receivedCode != 250)
                throw new SmtpException(_receivedMessage);
        }

        //MAIL FROMコマンドを送る
        private void SendMailFromCommand(string fromAddress)
        {
            SendAndReceiveData("MAIL FROM:<" + fromAddress + ">\r\n");
            if (_receivedCode != 250)
                throw new SmtpException(_receivedMessage);
        }

        //RCPT TOコマンドを送る
        private void SendRcptToCommand(string toAddress)
        {
            SendAndReceiveData("RCPT TO:<" + toAddress + ">\r\n");
            if (_receivedCode < 250 || 251 < _receivedCode)
                throw new SmtpException(_receivedMessage);
        }

        //DATAコマンドを送る
        private void SendDataCommand(SmtpMailMessage mail)
        {
            SendAndReceiveData("DATA\r\n");
            if (_receivedCode != 354)
                throw new SmtpException(_receivedMessage);

            //送信データを作成する
            string data = CreateSendStringDataFromMessage(mail);
            SendAndReceiveData(data);
            if (_receivedCode != 250)
                throw new SmtpException(_receivedMessage);
        }

        //SmtpMailMessageをDATAコマンドで送信する文字列に変換する
        private string CreateSendStringDataFromMessage(SmtpMailMessage mail)
        {
            StringBuilder data = new StringBuilder();

            data.Append("From: ").Append(mail.From).Append("\r\n");
            data.Append("To: ").Append(mail.To).Append("\r\n");
            //件名をBase64でエンコード
            data.Append("Subject: =?" + _encoding.BodyName + "?B?").
                Append(GetBase64String(mail.Subject)).
                Append("?=").Append("\r\n");
            data.Append("MIME-Version: 1.0\r\n");
            data.Append("Content-Transfer-Encoding: 7bit\r\n");
            data.Append("Content-Type: text/plain; charset=" +
                _encoding.BodyName + "\r\n");

            data.Append("\r\n").
                Append(mail.Body.Replace("\r\n.\r\n", "\r\n..\r\n"));

            data.Append("\r\n.\r\n");

            return data.ToString();
        }

        //QUITコマンドを送る
        private void SendQuitCommand()
        {
            SendAndReceiveData("QUIT\r\n");
            //if (_receivedCode != 221)
            //    throw new SmtpException(_receivedMessage);
        }

        //認証を行う
        private void Authenticate()
        {
            switch (AuthMethod)
            {
                case SmtpAuthMethod.Plain:
                    AuthenticateWithAuthPlain();
                    break;
                case SmtpAuthMethod.Login:
                    AuthenticateWithAuthLogin();
                    break;
                case SmtpAuthMethod.CramMd5:
                    AuthenticateWithCramMd5();
                    break;
            }
        }

        //AUTH PLAINで認証を行う
        private void AuthenticateWithAuthPlain()
        {
            SendAndReceiveData("AUTH PLAIN\r\n");
            if (_receivedCode != 334)
            {
                if (_receivedCode == 502)
                    //認証の必要なし
                    return;
                throw new SmtpException(_receivedMessage);
            }

            string str = AuthUserName + '\0' + AuthUserName +
                '\0' + AuthPassword;
            SendAndReceiveData(GetBase64String(str) + "\r\n");
            if (_receivedCode != 235)
                throw new SmtpException(_receivedMessage);
        }

        //AUTH LOGINで認証を行う
        private void AuthenticateWithAuthLogin()
        {
            SendAndReceiveData("AUTH LOGIN\r\n");
            if (_receivedCode != 334)
            {
                if (_receivedCode == 502)
                    //認証の必要なし
                    return;
                throw new SmtpException(_receivedMessage);
            }

            SendAndReceiveData(GetBase64String(AuthUserName) + "\r\n");
            if (_receivedCode != 334)
                throw new SmtpException(_receivedMessage);

            SendAndReceiveData(GetBase64String(AuthPassword) + "\r\n");
            if (_receivedCode != 235)
                throw new SmtpException(_receivedMessage);
        }

        //CRAM-MD5で認証を行う
        private void AuthenticateWithCramMd5()
        {
            SendAndReceiveData("AUTH CRAM-MD5\r\n");
            if (_receivedCode != 334)
            {
                if (_receivedCode == 502)
                    //認証の必要なし
                    return;
                throw new SmtpException(_receivedMessage);
            }

            SendAndReceiveData(CreateCramMd5ResponseString(
                _receivedMessage.Substring(4), AuthUserName, AuthPassword)
                + "\r\n");
            if (_receivedCode != 235)
                throw new SmtpException(_receivedMessage);
        }

        //CRAM-MD5で返す文字列を計算する
        private static string CreateCramMd5ResponseString(
            string challenge, string username, string password)
        {
            //デコードする
            byte[] decCha = Convert.FromBase64String(challenge);
            //passwordをキーとしてHMAC-MD5で暗号化する
            System.Security.Cryptography.HMACMD5 hmacMd5 =
                new System.Security.Cryptography.HMACMD5(
                    Encoding.UTF8.GetBytes(password));
            byte[] encCha = hmacMd5.ComputeHash(decCha);
            hmacMd5.Clear();
            //16進数の文字列にする
            string hexCha = BitConverter.ToString(encCha).
                Replace("-", "").ToLower();
            //usernameを付ける
            hexCha = username + " " + hexCha;
            //Base64で文字列にする
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(hexCha));
        }
        #endregion
    }

}
  • 履歴:
  • 2013/6/27 EHLOにドメイン名を付けるようにした。MAIL FROMやRCPT TOで送信するメールアドレスを <> で囲むようにした。CRAM-MD5に対応など。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • .NET Tipsをご利用いただく際は、注意事項をお守りください。
共有する

この記事への評価

この記事へのコメント

この記事に関するコメントを投稿するには、下のボタンをクリックしてください。投稿フォームへ移動します。通常のご質問、ご意見等は掲示板へご投稿ください。