/// <summary> /// Resolves a DNS host name or IP to IPAddress[]. /// </summary> /// <param name="hostName_IP">Host name or IP address.</param> /// <returns></returns> public static IPAddress[] Resolve(string hostName_IP) { // If hostName_IP is IP try{ return(new IPAddress[] { IPAddress.Parse(hostName_IP) }); } catch { } // This is probably NetBios name if (hostName_IP.IndexOf(".") == -1) { return(System.Net.Dns.Resolve(hostName_IP).AddressList); } else { // hostName_IP must be host name, try to resolve it's IP Dns_Client dns = new Dns_Client(); DnsServerResponse resp = dns.Query(hostName_IP, QTYPE.A); if (resp.ResponseCode == RCODE.NO_ERROR) { A_Record[] records = resp.GetARecords(); IPAddress[] retVal = new IPAddress[records.Length]; for (int i = 0; i < records.Length; i++) { retVal[i] = IPAddress.Parse(records[i].IP); } return(retVal); } else { throw new Exception(resp.ResponseCode.ToString()); } } }
/// <summary> /// Connects to sepcified host. /// </summary> /// <param name="localEndpoint">Sets local endpoint. Pass null, to use default.</param> /// <param name="host">Host name or IP address.</param> /// <param name="port">Port where to connect.</param> public void Connect(IPEndPoint localEndpoint,string host,int port) { Socket s = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.IP); s.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.ReceiveTimeout,30000); s.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.NoDelay,1); m_pSocket = new BufferedSocket(s); if(localEndpoint != null){ m_pSocket.Bind(localEndpoint); } m_pSocket.Activity += new EventHandler(m_pSocket_Activity); if(SessionLog != null){ m_pLogger = new SocketLogger(s,SessionLog); m_pLogger.SessionID = Guid.NewGuid().ToString(); m_pSocket.Logger = m_pLogger; } if(host.IndexOf("@") == -1){ m_pSocket.Connect(new IPEndPoint(Dns_Client.Resolve(host)[0],port)); } else{ //---- Parse e-domain -------------------------------// string domain = host; // eg. Ivx <*****@*****.**> if(domain.IndexOf("<") > -1 && domain.IndexOf(">") > -1){ domain = domain.Substring(domain.IndexOf("<")+1,domain.IndexOf(">") - domain.IndexOf("<")-1); } if(domain.IndexOf("@") > -1){ domain = domain.Substring(domain.LastIndexOf("@") + 1); } if(domain.Trim().Length == 0){ if(m_pLogger != null){ m_pLogger.AddTextEntry("Destination address '" + host + "' is invalid, aborting !"); } throw new Exception("Destination address '" + host + "' is invalid, aborting !"); } //--- Get MX record -------------------------------------------// Dns_Client dns = new Dns_Client(); Dns_Client.DnsServers = m_pDnsServers; DnsServerResponse dnsResponse = dns.Query(domain,QTYPE.MX); switch(dnsResponse.ResponseCode) { case RCODE.NO_ERROR: MX_Record[] mxRecords = dnsResponse.GetMXRecords(); // Try all available hosts by MX preference order, if can't connect specified host. foreach(MX_Record mx in mxRecords){ try{ if(m_pLogger != null){ m_pLogger.AddTextEntry("Connecting with mx record to: " + mx.Host); } m_pSocket.Connect(new IPEndPoint(Dns_Client.Resolve(mx.Host)[0],port)); break; } catch{ // Just skip and let for to try next host. if(m_pLogger != null){ m_pLogger.AddTextEntry("Failed connect to: " + mx.Host); } } } // None of MX didn't connect if(mxRecords.Length > 0 && !m_pSocket.Connected){ throw new Exception("Destination email server is down"); } /* Rfc 2821 5 If no MX records are found, but an A RR is found, the A RR is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host. */ if(mxRecords.Length == 0){ // Try to connect with A record IPAddress[] ipEntry = null; try{ if(m_pLogger != null){ m_pLogger.AddTextEntry("No mx record, trying to get A record for: " + domain); } ipEntry = Dns_Client.Resolve(domain); } catch{ if(m_pLogger != null){ m_pLogger.AddTextEntry("Invalid domain,no MX or A record: " + domain); } throw new Exception("Invalid domain,no MX or A record: " + domain); } try{ if(m_pLogger != null){ m_pLogger.AddTextEntry("Connecting with A record to:" + domain); } m_pSocket.Connect(new IPEndPoint(ipEntry[0],port)); } catch{ if(m_pLogger != null){ m_pLogger.AddTextEntry("Failed connect to:" + domain); } throw new Exception("Destination email server is down"); } } break; case RCODE.NAME_ERROR: if(m_pLogger != null){ m_pLogger.AddTextEntry("Invalid domain,no MX or A record: " + domain); } throw new Exception("Invalid domain,no MX or A record: " + domain); case RCODE.SERVER_FAILURE: if(m_pLogger != null){ m_pLogger.AddTextEntry("Dns server unvailable."); } throw new Exception("Dns server unvailable."); } } /* * Notes: Greeting may be single or multiline response. * * Examples: * 220<SP>SMTP server ready<CRLF> * * 220-SMTP server ready<CRLF> * 220-Addtitional text<CRLF> * 220<SP>final row<CRLF> * */ // Read server response string responseLine = m_pSocket.ReadLine(1000); while(!responseLine.StartsWith("220 ")){ // If lisne won't start with 220, then its error response if(!responseLine.StartsWith("220")){ throw new Exception(responseLine); } responseLine = m_pSocket.ReadLine(1000); } m_Connected = true; }
//---- SMTP implementation ----// #region function SendMessageToServer private bool SendMessageToServer(string[] to,string reverse_path,Stream message) { // Get email from to string for(int i=0;i<to.Length;i++){ to[i] = (new InfoControl.Net.Mail.Mime.eAddress(to[i])).Email; } ArrayList defectiveEmails = new ArrayList(); Socket so = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); so.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.SendTimeout,15000); BufferedSocket socket = new BufferedSocket(so); SocketLogger logger = null; if(m_LogCmds && this.SessionLog != null){ logger = new SocketLogger(so,this.SessionLog); logger.SessionID = socket.GetHashCode().ToString(); socket.Logger = logger; } try{ string reply = ""; bool supports_SIZE = false; bool supports_8BIT = false; bool supports_BDAT = false; if(m_UseSmartHost){ socket.Connect(new IPEndPoint(System.Net.Dns.Resolve(m_SmartHost).AddressList[0],m_Port)); } else{ //---- Parse e-domain -------------------------------// string domain = to[0]; // eg. Ivx <*****@*****.**> if(domain.IndexOf("<") > -1 && domain.IndexOf(">") > -1){ domain = domain.Substring(domain.IndexOf("<")+1,domain.IndexOf(">") - domain.IndexOf("<")-1); } if(domain.IndexOf("@") > -1){ domain = domain.Substring(domain.LastIndexOf("@") + 1); } if(domain.Trim().Length == 0){ if(logger != null){ logger.AddTextEntry("Destination address '" + to[0] + "' is invalid, aborting !"); } return false; } //--- Get MX record -------------------------------------------// Dns_Client dns = new Dns_Client(); Dns_Client.DnsServers = m_DnsServers; DnsServerResponse dnsResponse = dns.Query(domain,QTYPE.MX); switch(dnsResponse.ResponseCode) { case RCODE.NO_ERROR: MX_Record[] mxRecords = dnsResponse.GetMXRecords(); // Try all available hosts by MX preference order, if can't connect specified host. foreach(MX_Record mx in mxRecords){ try{ if(logger != null){ logger.AddTextEntry("Connecting with mx record to: " + mx.Host); } socket.Connect(new IPEndPoint(System.Net.Dns.Resolve(mx.Host).AddressList[0],m_Port)); break; } catch{ // Just skip and let for to try next host. if(logger != null){ logger.AddTextEntry("Failed connect to: " + mx.Host); } } } /* Rfc 2821 5 If no MX records are found, but an A RR is found, the A RR is treated as if it was associated with an implicit MX RR, with a preference of 0, pointing to that host. */ if(mxRecords.Length == 0){ // Try to connect with A record IPHostEntry ipEntry = null; try{ if(logger != null){ logger.AddTextEntry("No mx record, trying to get A record for: " + domain); } ipEntry = System.Net.Dns.Resolve(domain); } catch{ if(logger != null){ logger.AddTextEntry("Invalid domain,no MX or A record: " + domain); } OnError(SMTP_ErrorType.InvalidEmailAddress,to,"email domain <" + domain + "> is invalid"); defectiveEmails.AddRange(to); if(logger != null){ logger.Flush(); } return false; } try{ if(logger != null){ logger.AddTextEntry("Connecting with A record to:" + domain); } socket.Connect(new IPEndPoint(ipEntry.AddressList[0],m_Port)); } catch{ if(logger != null){ logger.AddTextEntry("Failed connect to:" + domain); } } } break; case RCODE.NAME_ERROR: if(logger != null){ logger.AddTextEntry("Invalid domain,no MX or A record: " + domain); } OnError(SMTP_ErrorType.InvalidEmailAddress,to,"email domain <" + domain + "> is invalid"); defectiveEmails.AddRange(to); if(logger != null){ logger.Flush(); } return false; case RCODE.SERVER_FAILURE: if(logger != null){ logger.AddTextEntry("Dns server unvailable."); } OnError(SMTP_ErrorType.UnKnown,to,"Dns server unvailable."); defectiveEmails.AddRange(to); if(logger != null){ logger.Flush(); } return false; } } if(!socket.Connected){ OnError(SMTP_ErrorType.UnKnown,to,"Unable connect to server !"); if(logger != null){ logger.Flush(); } return false; } #region Get 220 reply from server /* NOTE: reply may be multiline 220 xx ready or 220-someBull 200 xx */ // Server must reply 220 - Server OK reply = socket.ReadLine(); if(!IsReplyCode("220",reply)){ OnError(SMTP_ErrorType.UnKnown,to,reply); socket.SendLine("QUIT"); if(logger != null){ logger.Flush(); } return false; } else{ // 220-xxx<CRLF> // 220 aa<CRLF> - means end // reply isn't complete, get more while(reply.IndexOf("220 ") == -1){ reply += socket.ReadLine(); } } #endregion #region cmd EHLO/HELO // Send greeting to server socket.SendLine("EHLO " + m_HostName); reply = socket.ReadLine(); if(!IsReplyCode("250",reply)){ // EHLO failed, mayby server doesn't support it, try HELO socket.SendLine("HELO " + m_HostName); reply = socket.ReadLine(); if(!IsReplyCode("250",reply)){ OnError(SMTP_ErrorType.UnKnown,to,reply); socket.SendLine("QUIT"); defectiveEmails.AddRange(to); if(logger != null){ logger.Flush(); } return false; } // else{ // supports_ESMTP = false; // } } else{ // 250-xxx<CRLF> // 250 aa<CRLF> - means end // reply isn't complete, get more while(reply.IndexOf("250 ") == -1){ reply += socket.ReadLine(); } // Check if SIZE argument is supported if(reply.ToUpper().IndexOf("SIZE") > -1){ supports_SIZE = true; } // Check if 8BITMIME argument is supported if(reply.ToUpper().IndexOf("8BITMIME") > -1){ supports_8BIT = true; } // Check if CHUNKING argument is supported if(reply.ToUpper().IndexOf("CHUNKING") > -1){ supports_BDAT = true; } } #endregion //*** All server today support 8-bit, just skip it. // If server doesn't support 8bit mime, check if message is 8bit. // If is we MAY NOT send this message or loss of data /* if(!supports_8BIT){ if(Is8BitMime(message)){ OnError(SMTP_ErrorType.NotSupported,to,"Message is 8-Bit mime and server doesn't support it."); socket.SendLine("QUIT"); if(logger != null){ logger.Flush(); } return false; } }*/ #region cmd AUTH if (this.m_Username != null && m_Username.Length > 0 && m_Password != null && m_Password.Length > 0){ if(reply.ToUpper().IndexOf("AUTH") > -1){ if(reply.ToUpper().IndexOf("LOGIN") > -1){ socket.SendLine("AUTH LOGIN"); reply = socket.ReadLine(); if(!IsReplyCode("334",reply)){ OnError(SMTP_ErrorType.NotAuthenticated,to,"Failed to authenticate"); socket.SendLine("QUIT"); if(logger != null){ logger.Flush(); } return false; } socket.SendLine(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(m_Username.ToCharArray()))); reply = socket.ReadLine(); if(!IsReplyCode("334",reply)){ OnError(SMTP_ErrorType.NotAuthenticated,to,"Failed to authenticate"); socket.SendLine("QUIT"); if(logger != null){ logger.Flush(); } return false; } socket.SendLine(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(m_Password.ToCharArray()))); reply = socket.ReadLine(); if(!IsReplyCode("235",reply)){ OnError(SMTP_ErrorType.NotAuthenticated,to,"Failed to authenticate"); socket.SendLine("QUIT"); if(logger != null){ logger.Flush(); } return false; } } // if(reply.ToUpper().IndexOf("CRAM-MD5") > -1) // { // socket.SendLine("AUTH CRAM-MD5"); // reply = socket.ReadLine(); // if (IsReplyCode("334",auth)) // { // socket.SendLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(m_Username.ToCharArray()))); // socket.SendLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(m_Password.ToCharArray()))); // } // } } else{ //server did not support AUTH } } #endregion #region cmd MAIL // NOTE: Syntax:{MAIL FROM:<*****@*****.**> [SIZE=msgSize]<CRLF>} // Send Mail From if(supports_SIZE){ socket.SendLine("MAIL FROM:<" + reverse_path + "> SIZE=" + (message.Length - message.Position)); } else{ socket.SendLine("MAIL FROM:<" + reverse_path + ">"); } reply = socket.ReadLine(); if(!IsReplyCode("250",reply)){ // To Do: Check if size exceeded error: OnError(SMTP_ErrorType.UnKnown,to,reply); socket.SendLine("QUIT"); defectiveEmails.AddRange(to); if(logger != null){ logger.Flush(); } return false; } #endregion #region cmd RCPT // NOTE: Syntax:{RCPT TO:<*****@*****.**><CRLF>} bool isAnyValidEmail = false; foreach(string rcpt in to){ // Send Mail To socket.SendLine("RCPT TO:<" + rcpt + ">"); reply = socket.ReadLine(); if(!IsReplyCode("250",reply)){ // Is unknown user if(IsReplyCode("550",reply)){ OnError(SMTP_ErrorType.InvalidEmailAddress,new string[]{rcpt},reply); } else{ OnError(SMTP_ErrorType.UnKnown,new string[]{rcpt},reply); } defectiveEmails.Add(rcpt); } else{ isAnyValidEmail = true; } } // If there isn't any valid email - quit. if(!isAnyValidEmail){ socket.SendLine("QUIT"); if(logger != null){ logger.Flush(); } return false; } //---------------------------------------------// #endregion #region cmd DATA if(!(supports_BDAT && m_UseBDAT)){ // Notify Data Start socket.SendLine("DATA"); reply = socket.ReadLine(); if(!IsReplyCode("354",reply)){ OnError(SMTP_ErrorType.UnKnown,to,reply); socket.SendLine("QUIT"); defectiveEmails.AddRange(to); if(logger != null){ logger.Flush(); } return false; } //------- Do period handling -----------------------------------------// // If line starts with '.', add additional '.'.(Read rfc for more info) MemoryStream msgStrmPeriodOk = Core.DoPeriodHandling(message,true,false); //--------------------------------------------------------------------// // Check if message ends with <CRLF>, if not add it. -------// if(msgStrmPeriodOk.Length >= 2){ byte[] byteEnd = new byte[2]; msgStrmPeriodOk.Position = msgStrmPeriodOk.Length - 2; msgStrmPeriodOk.Read(byteEnd,0,2); if(byteEnd[0] != (byte)'\r' && byteEnd[1] != (byte)'\n'){ msgStrmPeriodOk.Write(new byte[]{(byte)'\r',(byte)'\n'},0,2); } } msgStrmPeriodOk.Position = 0; //-----------------------------------------------------------// //---- Send message --------------------------------------------// long totalSent = 0; long totalLength = msgStrmPeriodOk.Length; while(totalSent < totalLength){ byte[] dataBuf = new byte[4000]; int nCount = msgStrmPeriodOk.Read(dataBuf,0,dataBuf.Length); int countSended = socket.Send(dataBuf,0,nCount,SocketFlags.None); totalSent += countSended; if(countSended != nCount){ msgStrmPeriodOk.Position = totalSent; } OnPartOfMessageIsSent(countSended,totalSent,totalLength); } //-------------------------------------------------------------// msgStrmPeriodOk.Close(); // Notify End of Data socket.SendLine("."); reply = socket.ReadLine(); if(!IsReplyCode("250",reply)){ OnError(SMTP_ErrorType.UnKnown,to,reply); socket.SendLine("QUIT"); defectiveEmails.AddRange(to); if(logger != null){ logger.Flush(); } return false; } } #endregion #region cmd BDAT if(supports_BDAT && m_UseBDAT){ socket.SendLine("BDAT " + (message.Length - message.Position) + " LAST"); //---- Send message --------------------------------------------// long totalSent = 0; long totalLength = message.Length - message.Position; while(totalSent < totalLength){ byte[] dataBuf = new byte[4000]; int nCount = message.Read(dataBuf,0,dataBuf.Length); int countSended = socket.Send(dataBuf,0,nCount,SocketFlags.None); totalSent += countSended; if(countSended != nCount){ message.Position = totalSent; } OnPartOfMessageIsSent(countSended,totalSent,totalLength); } //-------------------------------------------------------------// // Get store result reply = socket.ReadLine(); if(!reply.StartsWith("250")){ OnError(SMTP_ErrorType.UnKnown,to,reply); socket.SendLine("QUIT"); defectiveEmails.AddRange(to); if(logger != null){ logger.Flush(); } return false; } } #endregion #region cmd QUIT // Notify server - server can exit now socket.SendLine("QUIT"); // reply = socket.ReadLine(); #endregion } catch(Exception x){ OnError(SMTP_ErrorType.UnKnown,to,x.Message); defectiveEmails.AddRange(to); if(logger != null){ logger.Flush(); } return false; } finally{ // Raise event OnSendJobCompleted(Thread.CurrentThread.GetHashCode().ToString(),to,defectiveEmails); } if(logger != null){ logger.Flush(); } return true; }
/// <summary> /// Resolves a DNS host name or IP to IPAddress[]. /// </summary> /// <param name="hostName_IP">Host name or IP address.</param> /// <returns></returns> public static IPAddress[] Resolve(string hostName_IP) { // If hostName_IP is IP try{ return new IPAddress[]{IPAddress.Parse(hostName_IP)}; } catch{ } // This is probably NetBios name if(hostName_IP.IndexOf(".") == -1){ return System.Net.Dns.Resolve(hostName_IP).AddressList; } else{ // hostName_IP must be host name, try to resolve it's IP Dns_Client dns = new Dns_Client(); DnsServerResponse resp = dns.Query(hostName_IP,QTYPE.A); if(resp.ResponseCode == RCODE.NO_ERROR){ A_Record[] records = resp.GetARecords(); IPAddress[] retVal = new IPAddress[records.Length]; for(int i=0;i<records.Length;i++){ retVal[i] = IPAddress.Parse(records[i].IP); } return retVal; } else{ throw new Exception(resp.ResponseCode.ToString()); } } }