[Dnsmail-cvs] dnsmail SmtpStream.cs,NONE,1.1
Brought to you by:
ethem
From: Ethem E. <et...@us...> - 2006-08-04 12:59:08
|
Update of /cvsroot/dnsmail/dnsmail In directory sc8-pr-cvs12.sourceforge.net:/tmp/cvs-serv1481/dnsmail Added Files: SmtpStream.cs Log Message: - OpensslStream class removed and new SmtpStream class derived from NetworkStream added. - New class handles normal and ssl connections automatically. - Socket and OpenSslStream removed from DnsMail client. New SmtpStream member added. --- NEW FILE: SmtpStream.cs --- namespace Erle { using System; using System.IO; using System.Text; using System.Threading; using System.Net.Sockets; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; internal class SmtpStream : NetworkStream, IDisposable { private IntPtr sslSession = IntPtr.Zero; private IntPtr sslContext = IntPtr.Zero; private X509Certificate x509cert = null; #region .ctor .cctor and .dtor private static readonly String _sslVersion; private static readonly bool _sslAvailable; static SmtpStream() { 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 SmtpStream(Socket s) : this(s, false) {} public SmtpStream(Socket s, bool ownsSocket) : base(s, FileAccess.ReadWrite, ownsSocket) {} ~SmtpStream() { Dispose(false); } public override void Close() { ((IDisposable)this).Dispose(); } void IDisposable.Dispose() { Flush(); Dispose(true); GC.SuppressFinalize(this); } private bool disposed = false; protected override void Dispose(bool disposing) { if (!disposed) { disposed = true; if (disposing) { x509cert = null; } if (sslSession != IntPtr.Zero) { try { UnsafeOpenSsl.SSL_free(sslSession); } catch {throw;} sslSession = IntPtr.Zero; } if (sslContext != IntPtr.Zero) { try { UnsafeOpenSsl.SSL_CTX_free(sslContext); } catch {throw;} sslContext = IntPtr.Zero; } } base.Dispose(disposing); } #endregion #region Properties public X509Certificate X509Certificate { get { return x509cert; } } public bool SslActive { get { return _sslAvailable && !disposed && handshake; } } public override bool CanRead { get { return !disposed && base.CanRead; } } public override bool CanWrite { get { return !disposed && base.CanWrite; } } public unsafe override bool DataAvailable { get { if (handshake && !disposed) { int r = 0; byte[] t = new byte[1]; fixed (byte* y = &t[0]) { r = UnsafeOpenSsl.SSL_peek(sslSession, y, 1); } return (r > 0); } else { return base.DataAvailable; } } } public static String OpenSslVersion { get { return _sslVersion; } } public static bool SslAvailable { get { return _sslAvailable; } } #endregion #region Handshake public static void write(string x) { System.Web.HttpContext.Current.Response.Write(x + "<br />"); } private bool handshake = false; public bool Handshake(bool cerrequest) { bool ret = false; if (_sslAvailable && !disposed && !handshake && (Socket.RemoteEndPoint != null)) { 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, Socket.Handle); StringBuilder errors = new StringBuilder(); bool oldblock = Socket.Blocking; if (oldblock) Socket.Blocking = false; int err, cnt = 0; while (((err=UnsafeOpenSsl.SSL_connect(sslSession)) <= 0) && handle_ssl_error(err, errors)) cnt++; if (Socket.Blocking != oldblock) Socket.Blocking = oldblock; if (err <= 0) { try { if (errors.Length > 0) throw new Exception(errors.ToString()); else if (err == 0) throw new Exception("Connection closed"); else 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 Read && Write unsafe public override int Read(byte[] buffer, int offset, int count) { int read = -1; if (handshake) { fixed(byte* ptr = &buffer[offset]) { try { read = UnsafeOpenSsl.SSL_read(sslSession, ptr, count); } catch { read = -1; } } } else { read = base.Read(buffer, offset, count); } return read; } unsafe public override void Write(byte[] buffer, int offset, int count) { if (handshake) { 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 { base.Write(buffer, offset, count); } } public bool Poll(int microSeconds, SelectMode mode) { return Socket.Poll(microSeconds, mode); } #endregion } }; |