[Dnsmail-cvs] dnsmail SmtpClient.cs, NONE, 1.1 Erle.DnsMail.csproj, 1.6, 1.7
Brought to you by:
ethem
From: Ethem E. <et...@us...> - 2006-08-07 10:17:51
|
Update of /cvsroot/dnsmail/dnsmail In directory sc8-pr-cvs12.sourceforge.net:/tmp/cvs-serv2476/dnsmail Modified Files: Erle.DnsMail.csproj Added Files: SmtpClient.cs Log Message: SmtpStream removed, SmtpClient derived from TcpStream added. Index: Erle.DnsMail.csproj =================================================================== RCS file: /cvsroot/dnsmail/dnsmail/Erle.DnsMail.csproj,v retrieving revision 1.6 retrieving revision 1.7 diff -C2 -d -r1.6 -r1.7 *** Erle.DnsMail.csproj 7 Aug 2006 08:43:41 -0000 1.6 --- Erle.DnsMail.csproj 7 Aug 2006 10:17:47 -0000 1.7 *************** *** 110,114 **** /> <File ! RelPath = "SmtpStream.cs" SubType = "Code" BuildAction = "Compile" --- 110,114 ---- /> <File ! RelPath = "SmtpClient.cs" SubType = "Code" BuildAction = "Compile" *************** *** 158,159 **** --- 158,160 ---- </CSHARP> </VisualStudioProject> + --- NEW FILE: SmtpClient.cs --- 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) : base(local) { NoDelay = true; ReceiveTimeout = 3000000; SendTimeout = 3000000; LingerState = new LingerOption(true, 5); Connect(remote); if (!Client.Poll(21000000, SelectMode.SelectRead) || SendAndReceive(null) != Commands.Ok) { throw new MailServerException(LastResponse); } dataStream = GetStream(); recvBuffer = new byte[256]; esmtp = (LastAnswer.ToLower().IndexOf("esmtp") != -1); } ~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 (disposing) { UseLog = false; x509cert = null; recvBuffer = null; } if (dataStream != null) { try { if (Client.Poll(1000, SelectMode.SelectWrite)) SendAndReceive(quitcmd); } catch { throw; } finally { // Don't close dataStream. SSL session needs it. // base.Dispose() closes datastream. dataStream = null; } } if (_sslAvailable) { if (sslSession != IntPtr.Zero) { try { UnsafeOpenSsl.SSL_free(sslSession); } catch { throw; } finally { sslSession = IntPtr.Zero; } } if (sslContext != IntPtr.Zero) { try { UnsafeOpenSsl.SSL_CTX_free(sslContext); } catch { throw; } finally { 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 new 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.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 : byte { Pending = 0, Info, Ok, More, Reject, Error } public Commands SendAndReceive(String val) { bool quit = (val == quitcmd); if (val != null) { 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); } while (tmpResp[tmpResp.Length - 1] != '\n' && Client.Poll(500, SelectMode.SelectRead)); if (tmpResp.Length < 4) throw new Exception((SslActive ? "SSL_" : "") + "SOCKET READ ERROR"); if (UseLog) logs.Append(tmpResp); if (!quit) { string log = tmpResp.Trim(); lastcode = int.Parse(log.Substring(0, 3)); lastanswer = log.Substring(4); } return (Commands)Convert.ToByte(tmpResp[0]); } 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 } }; |