⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 smtpclient.cs

📁 A project written in C# sends email without smtp server. It queries dns server for mx records and se
💻 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 + -