📄 smtpclient.cs
字号:
// $Id: SmtpClient.cs,v 1.8 2006/08/11 14:35:19 ethem Exp $
namespace Erle
{
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using MailServerException = Erle.DnsMail.MailServerException;
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
internal class SmtpClient : TcpClient, IDisposable
{
private IntPtr sslContext = IntPtr.Zero;
private IntPtr sslSession = IntPtr.Zero;
private NetworkStream dataStream = null;
#region .ctor .cctor .dtor
private static readonly String _sslVersion;
private static readonly bool _sslAvailable;
static SmtpClient()
{
try
{
UnsafeOpenSsl.SSL_load_error_strings();
UnsafeOpenSsl.SSL_library_init();
IntPtr tmpctx = IntPtr.Zero;
try
{
tmpctx = UnsafeOpenSsl.SSL_CTX_new(UnsafeOpenSsl.TLSv1_method());
uint version = UnsafeOpenSsl.SSLeay();
_sslAvailable = true;
_sslVersion = "OpenSSL(0x0" + version.ToString("x") + ")";
}
finally
{
if (tmpctx != IntPtr.Zero)
UnsafeOpenSsl.SSL_CTX_free(tmpctx);
}
}
catch
{
_sslAvailable = false;
_sslVersion = String.Empty;
}
}
public SmtpClient(IPEndPoint remote, IPEndPoint local, bool useLog)
: base(local)
{
NoDelay = true;
ReceiveTimeout = 3000000;
SendTimeout = 3000000;
LingerState = new LingerOption(true, 5);
Connect(remote);
if (Client.Poll(21000000, SelectMode.SelectRead))
{
UseLog = useLog;
dataStream = GetStream();
recvBuffer = new byte[256];
if (SendAndReceive(null) == Commands.Ok)
{
esmtp = (LastAnswer.ToLower().IndexOf("esmtp") != -1);
return;
}
throw new MailServerException(LastResponse);
}
throw new MailServerException(String.Empty);
}
~SmtpClient()
{
Dispose(false);
}
void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public new void Close()
{
((IDisposable)this).Dispose();
}
private bool disposed = false;
protected override void Dispose(bool disposing)
{
if (!disposed)
{
disposed = true;
if (dataStream != null)
{
try
{
if (Client.Poll(1000, SelectMode.SelectWrite))
SendAndReceive(quitcmd);
}
catch { }
finally
{
// Don't close dataStream. SSL session needs it.
// base.Dispose() closes datastream.
dataStream = null;
}
}
if (_sslAvailable)
{
if (sslSession != IntPtr.Zero)
{
try { UnsafeOpenSsl.SSL_shutdown(sslSession); }
catch { }
try { UnsafeOpenSsl.SSL_free(sslSession); }
catch { }
sslSession = IntPtr.Zero;
}
if (sslContext != IntPtr.Zero)
{
try { UnsafeOpenSsl.SSL_CTX_free(sslContext); }
catch { }
sslContext = IntPtr.Zero;
}
}
}
base.Dispose(disposing);
}
#endregion
#region Properties
private X509Certificate x509cert;
public X509Certificate X509Certificate
{
get { return x509cert; }
}
public bool SslActive
{
get
{
if (_sslAvailable && !disposed)
{
return handshake;
}
return false;
}
}
private bool uselog;
internal StringBuilder logs;
public bool UseLog
{
get { return uselog; }
set
{
if (value)
{
if (logs == null)
logs = new StringBuilder();
uselog = true;
}
else
{
uselog = false;
if (logs != null)
{
logs.Length = 0;
logs = null;
}
}
}
}
private bool esmtp = false;
public bool ESmtp
{
get
{
return esmtp;
}
}
private int lastcode;
public int LastCode
{
get { return lastcode; }
}
private string lastanswer = String.Empty;
public string LastAnswer
{
get { return lastanswer; }
}
public string LastResponse
{
get
{
return LastCode.ToString() + " " + LastAnswer;
}
}
public int Available
{
get
{
if (handshake && !disposed)
{
int available = 0;
byte[] p = new byte[1];
unsafe
{
fixed (byte* y = p)
{
available = UnsafeOpenSsl.SSL_peek(sslSession, y, 1);
}
}
return available;
}
return base.Client.Available;
}
}
#region Static Properties
public static String OpenSslVersion
{
get { return _sslVersion; }
}
public static bool SslAvailable
{
get { return _sslAvailable; }
}
#endregion
#endregion
#region StartTls
private bool handshake = false;
public bool StartTls(bool cerrequest)
{
bool ret = false;
if (_sslAvailable && !disposed && !handshake && (Client.RemoteEndPoint != null))
{
if (SendAndReceive("STARTTLS\r\n") != Commands.Ok)
{
SendAndReceive("RSET\r\n");
return ret;
}
if (IntPtr.Zero != (sslContext = UnsafeOpenSsl.SSL_CTX_new(UnsafeOpenSsl.TLSv1_method())) &&
IntPtr.Zero != (sslSession = UnsafeOpenSsl.SSL_new(sslContext)))
{
const int SSL_CTRL_OPTIONS = 32;
const int SSL_OP_ALL = 0x00000FFF;
//UnsafeOpenSsl.SSL_set_quiet_shutdown(sslSession, 1);
UnsafeOpenSsl.SSL_ctrl(sslSession, SSL_CTRL_OPTIONS, SSL_OP_ALL, IntPtr.Zero);
UnsafeOpenSsl.SSL_set_fd(sslSession, Client.Handle);
bool oldblock = Client.Blocking;
if (oldblock)
Client.Blocking = false;
int error; StringBuilder errors = new StringBuilder();
while (((error = UnsafeOpenSsl.SSL_connect(sslSession)) <= 0) && handle_ssl_error(error, errors)) ;
if (Client.Blocking != oldblock)
Client.Blocking = oldblock;
if (error <= 0)
{
try
{
if (errors.Length > 0)
{
throw new Exception(errors.ToString());
}
if (error == 0)
{
throw new Exception("Connection closed");
}
throw new Exception("Unknown SSL exception");
}
finally
{
Close();
}
}
handshake = true; // opened
#region get certificate
if (cerrequest)
{
IntPtr cer = IntPtr.Zero;
try
{
if (IntPtr.Zero != (cer = UnsafeOpenSsl.SSL_get_peer_certificate(sslSession)))
{
unsafe
{
int required = UnsafeOpenSsl.i2d_X509(cer, (byte**)0);
if (required > 0 && required < 16384)
{
byte[] buf = new byte[required];
fixed (byte* pBuf = buf)
{
byte* pp = pBuf;
UnsafeOpenSsl.i2d_X509(cer, &pp);
x509cert = new X509Certificate(buf);
}
}
}
}
}
catch { }
finally
{
if (cer != IntPtr.Zero)
{
try
{
UnsafeOpenSsl.X509_free(cer);
}
catch { }
}
}
}
#endregion
ret = true;
}
else
{
Close();
}
}
return ret;
}
#endregion
#region handle_ssl_error
private enum SslError
{
SSL_ERROR_NONE = 0,
SSL_ERROR_SSL = 1,
SSL_ERROR_WANT_READ = 2,
SSL_ERROR_WANT_WRITE = 3,
SSL_ERROR_WANT_X509_LOOKUP = 4,
SSL_ERROR_SYSCALL = 5,
SSL_ERROR_ZERO_RETURN = 6,
SSL_ERROR_WANT_CONNECT = 7,
SSL_ERROR_WANT_ACCEPT = 8
}
private bool handle_ssl_error(int c, StringBuilder errstack)
{
SslError err = (SslError)UnsafeOpenSsl.SSL_get_error(sslSession, c);
bool retry = true;
switch (err)
{
case SslError.SSL_ERROR_ZERO_RETURN:
/* SSL terminated (but socket may still be active) */
retry = false;
break;
case SslError.SSL_ERROR_WANT_READ:
case SslError.SSL_ERROR_WANT_WRITE:
/* re-negotiation || perhaps the SSL layer needs more packets */
Thread.Sleep(50);
break;
case SslError.SSL_ERROR_SYSCALL:
if (UnsafeOpenSsl.ERR_peek_error() == 0)
{
retry = false;
break;
}
goto default;
default:
{
const int SSL_R_NO_SHARED_CIPHER = 193;
int ecode = UnsafeOpenSsl.ERR_get_error();
switch ((int)(ecode & 0xfff)) // reason
{
case SSL_R_NO_SHARED_CIPHER:
{
/* no suitable shared cipher could be used */
retry = false;
break;
}
default:
{
do
{
StringBuilder s = new StringBuilder(512);
UnsafeOpenSsl.ERR_error_string_n(ecode, s, 510);
s.Insert(0, "["); s.Append("]");
errstack.Append(s.ToString() + "\r\n");
}
while ((ecode = UnsafeOpenSsl.ERR_get_error()) != 0);
break;
}
}
retry = false;
break;
}
}
return retry;
}
#endregion
#region Send & Receive
private byte[] recvBuffer;
private const int MaxBuffer = 8192;
private const string quitcmd = "QUIT\r\n";
internal enum Commands
{
Pending = 0,
Info,
Ok,
More,
Reject,
Error
}
public Commands SendAndReceive(String val)
{
bool quit = false;
if (val != null)
{
quit = (val == quitcmd);
byte[] sendBuffer = Encoding.ASCII.GetBytes(val);
Write(sendBuffer, 0, sendBuffer.Length);
}
int breceived = 0;
String tmpResp = String.Empty;
do
{
int bytes = Read(recvBuffer, 0, recvBuffer.Length);
if (bytes <= 0 || breceived > MaxBuffer)
{
break;
}
breceived += bytes;
tmpResp += Encoding.ASCII.GetString(recvBuffer, 0, bytes);
if (tmpResp[tmpResp.Length - 1] == '\n')
{
break;
}
} while (Client.Poll(500, SelectMode.SelectRead));
if (tmpResp.Length < 4)
throw new Exception((SslActive ? "SSL_" : "") + "SOCKET READ ERROR");
if (UseLog)
logs.Append(tmpResp);
if (!quit)
{
lastcode = int.Parse(tmpResp.Substring(0, 3));
lastanswer = tmpResp.Substring(4).Trim();
}
return (Commands)Enum.Parse(typeof(Commands), tmpResp.Substring(0, 1));
}
unsafe public int Read(byte[] buffer, int offset, int count)
{
int read = -1;
if (handshake)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset");
}
if (count < 0 || count > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("count");
}
fixed (byte* ptr = &buffer[offset])
{
try
{
read = UnsafeOpenSsl.SSL_read(sslSession, ptr, count);
}
catch
{
read = -1;
}
}
}
else
{
read = dataStream.Read(buffer, offset, count);
}
return read;
}
unsafe public void Write(byte[] buffer, int offset, int count)
{
if (handshake)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset");
}
if (count < 0 || count > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("count");
}
fixed (byte* ptr = &buffer[offset])
{
while (count > 0)
{
int sent = UnsafeOpenSsl.SSL_write(sslSession, &ptr[offset], count);
if (sent <= 0)
{
throw new Exception("ERR::SSL_write::memory{0x" + ((int)ptr).ToString("x") + "} index[" + offset + "]");
}
offset += sent;
count -= sent;
}
}
}
else
{
dataStream.Write(buffer, offset, count);
}
}
public bool Poll(int microSeconds, SelectMode mode)
{
return Client.Poll(microSeconds, mode);
}
#endregion
#region Hello
public void SayHello(string HeloDomain)
{
if (SendAndReceive("HELO " + HeloDomain + "\r\n") != Commands.Ok)
{
throw new MailServerException(LastResponse);
}
}
#endregion
#region Login
public bool Login(string username, string password, Encoding enc)
{
if (!ESmtp ||
username == null || password == null ||
username == String.Empty || password == String.Empty)
{
return false;
}
if (SendAndReceive("AUTH LOGIN\r\n") != Commands.More)
{
SendAndReceive("RSET\r\n");
return false;
}
if (SendAndReceive(Convert.ToBase64String(enc.GetBytes(username)) + "\r\n") != Commands.More)
throw new MailServerException(LastResponse);
if (SendAndReceive(Convert.ToBase64String(enc.GetBytes(password)) + "\r\n") != Commands.Ok)
throw new MailServerException(LastResponse);
return true;
}
#endregion
#region MailFrom
public bool MailFrom(string from)
{
return (SendAndReceive("MAIL FROM:<" + from + ">\r\n") == Commands.Ok);
}
#endregion
#region RcptTo
public bool RcptTo(string to)
{
return (SendAndReceive("RCPT TO:<" + to + ">\r\n") == Commands.Ok);
}
#endregion
#region StartData
public bool StartData()
{
return (SendAndReceive("DATA\r\n") == Commands.More);
}
#endregion
}
};
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -