Click here to Skip to main content
15,884,099 members
Articles / Programming Languages / C#

LumiSoft MailServer

Rate me:
Please Sign up or sign in to vote.
3.79/5 (22 votes)
17 Nov 2006CPOL1 min read 321.3K   4.9K   74  
Full featured SMTP/POP3/IMAP server
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

using LumiSoft.Net.STUN.Message;

namespace LumiSoft.Net.STUN.Client
{
    /// <summary>
    /// This class implements STUN client. Defined in RFC 3489.
    /// </summary>
    /// <example>
    /// <code>
    /// // Create new socket for STUN client.
    /// Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
    /// socket.Bind(new IPEndPoint(IPAddress.Any,0));
    /// 
    /// // Query STUN server
    /// STUN_Result result = STUN_Client.Query("stunserver.org",3478,socket);
    /// if(result.NetType != STUN_NetType.UdpBlocked){
    ///     // UDP blocked or !!!! bad STUN server
    /// }
    /// else{
    ///     IPEndPoint publicEP = result.PublicEndPoint;
    ///     // Do your stuff
    /// }
    /// </code>
    /// </example>
    public class STUN_Client
    {
        #region static method Query

        /// <summary>
        /// Gets NAT info from STUN server.
        /// </summary>
        /// <param name="host">STUN server name or IP.</param>
        /// <param name="port">STUN server port. Default port is 3478.</param>
        /// <param name="socket">UDP socket to use.</param>
        /// <returns>Returns UDP netwrok info.</returns>
        /// <exception cref="Exception">Throws exception if unexpected error happens.</exception>
        public static STUN_Result Query(string host,int port,Socket socket)
        {
            if(host == null){
                throw new ArgumentNullException("host");
            }
            if(socket == null){
                throw new ArgumentNullException("socket");
            }
            if(port < 1){
                throw new ArgumentException("Port value must be >= 1 !");
            }
            if(socket.ProtocolType != ProtocolType.Udp){
                throw new ArgumentException("Socket must be UDP socket !");
            }

            IPEndPoint remoteEndPoint = new IPEndPoint(System.Net.Dns.GetHostAddresses(host)[0],port);
            
            socket.ReceiveTimeout = 3000;
            socket.SendTimeout = 3000;

            /*
                In test I, the client sends a STUN Binding Request to a server, without any flags set in the
                CHANGE-REQUEST attribute, and without the RESPONSE-ADDRESS attribute. This causes the server 
                to send the response back to the address and port that the request came from.
            
                In test II, the client sends a Binding Request with both the "change IP" and "change port" flags
                from the CHANGE-REQUEST attribute set.  
              
                In test III, the client sends a Binding Request with only the "change port" flag set.
                          
                                    +--------+
                                    |  Test  |
                                    |   I    |
                                    +--------+
                                         |
                                         |
                                         V
                                        /\              /\
                                     N /  \ Y          /  \ Y             +--------+
                      UDP     <-------/Resp\--------->/ IP \------------->|  Test  |
                      Blocked         \ ?  /          \Same/              |   II   |
                                       \  /            \? /               +--------+
                                        \/              \/                    |
                                                         | N                  |
                                                         |                    V
                                                         V                    /\
                                                     +--------+  Sym.      N /  \
                                                     |  Test  |  UDP    <---/Resp\
                                                     |   II   |  Firewall   \ ?  /
                                                     +--------+              \  /
                                                         |                    \/
                                                         V                     |Y
                              /\                         /\                    |
               Symmetric  N  /  \       +--------+   N  /  \                   V
                  NAT  <--- / IP \<-----|  Test  |<--- /Resp\               Open
                            \Same/      |   I    |     \ ?  /               Internet
                             \? /       +--------+      \  /
                              \/                         \/
                              |                           |Y
                              |                           |
                              |                           V
                              |                           Full
                              |                           Cone
                              V              /\
                          +--------+        /  \ Y
                          |  Test  |------>/Resp\---->Restricted
                          |   III  |       \ ?  /
                          +--------+        \  /
                                             \/
                                              |N
                                              |       Port
                                              +------>Restricted

            */

            // Test I
            STUN_Message test1 = new STUN_Message();
            test1.Type = STUN_MessageType.BindingRequest;
            STUN_Message test1response = DoTransaction(test1,socket,remoteEndPoint);
    
            // UDP blocked.
            if(test1response == null){
                return new STUN_Result(STUN_NetType.UdpBlocked,null);
            }
            else{
                // Test II
                STUN_Message test2 = new STUN_Message();
                test2.Type = STUN_MessageType.BindingRequest;
                test2.ChangeRequest = new STUN_t_ChangeRequest(true,true);

                // No NAT.
                if(socket.LocalEndPoint.Equals(test1response.MappedAddress)){
                    STUN_Message test2Response = DoTransaction(test2,socket,remoteEndPoint);
                    // Open Internet.
                    if(test2Response != null){
                        return new STUN_Result(STUN_NetType.OpenInternet,test1response.MappedAddress);
                    }
                    // Symmetric UDP firewall.
                    else{
                        return new STUN_Result(STUN_NetType.SymmetricUdpFirewall,test1response.MappedAddress);
                    }
                }
                // NAT
                else{
                    STUN_Message test2Response = DoTransaction(test2,socket,remoteEndPoint);
                    // Full cone NAT.
                    if(test2Response != null){
                        return new STUN_Result(STUN_NetType.FullCone,test1response.MappedAddress);
                    }
                    else{
                        /*
                            If no response is received, it performs test I again, but this time, does so to 
                            the address and port from the CHANGED-ADDRESS attribute from the response to test I.
                        */

                        // Test I(II)
                        STUN_Message test12 = new STUN_Message();
                        test12.Type = STUN_MessageType.BindingRequest;

                        STUN_Message test12Response = DoTransaction(test12,socket,test1response.ChangedAddress);
                        if(test12Response == null){
                            throw new Exception("STUN Test I(II) dind't get resonse !");
                        }
                        else{
                            // Symmetric NAT
                            if(!test12Response.MappedAddress.Equals(test1response.MappedAddress)){
                                return new STUN_Result(STUN_NetType.Symmetric,test1response.MappedAddress);
                            }
                            else{
                                // Test III
                                STUN_Message test3 = new STUN_Message();
                                test3.Type = STUN_MessageType.BindingRequest;
                                test3.ChangeRequest = new STUN_t_ChangeRequest(false,true);

                                STUN_Message test3Response = DoTransaction(test3,socket,test1response.ChangedAddress);
                                // Restricted
                                if(test3Response != null){
                                    return new STUN_Result(STUN_NetType.RestrictedCone,test1response.MappedAddress);
                                }
                                // Port restricted
                                else{
                                    return new STUN_Result(STUN_NetType.PortRestrictedCone,test1response.MappedAddress);
                                }
                            }
                        }
                    }
                }
            }
        }

        #endregion


        #region method GetSharedSecret

        private void GetSharedSecret()
        {
            /*
                *) Open TLS connection to STUN server.
                *) Send Shared Secret request.
            */

            /*
            using(SocketEx socket = new SocketEx()){
                socket.RawSocket.ReceiveTimeout = 5000;
                socket.RawSocket.SendTimeout = 5000;

                socket.Connect(host,port);
                socket.SwitchToSSL_AsClient();                

                // Send Shared Secret request.
                STUN_Message sharedSecretRequest = new STUN_Message();
                sharedSecretRequest.Type = STUN_MessageType.SharedSecretRequest;
                socket.Write(sharedSecretRequest.ToByteData());
                
                // TODO: Parse message

                // We must get  "Shared Secret" or "Shared Secret Error" response.

                byte[] receiveBuffer = new byte[256];
                socket.RawSocket.Receive(receiveBuffer);

                STUN_Message sharedSecretRequestResponse = new STUN_Message();
                if(sharedSecretRequestResponse.Type == STUN_MessageType.SharedSecretResponse){
                }
                // Shared Secret Error or Unknown response, just try again.
                else{
                    // TODO: Unknown response
                }
            }*/
        }

        #endregion

        #region method DoTransaction

        /// <summary>
        /// Does STUN transaction. Returns transaction response or null if transaction failed.
        /// </summary>
        /// <param name="request">STUN message.</param>
        /// <param name="socket">Socket to use for send/receive.</param>
        /// <param name="remoteEndPoint">Remote end point.</param>
        /// <returns>Returns transaction response or null if transaction failed.</returns>
        private static STUN_Message DoTransaction(STUN_Message request,Socket socket,IPEndPoint remoteEndPoint)
        {                        
            byte[] requestBytes = request.ToByteData();                              
            DateTime startTime = DateTime.Now;
            // We do it only 2 sec and retransmit with 100 ms.
            while(startTime.AddSeconds(2) > DateTime.Now){
                try{
                    socket.SendTo(requestBytes,remoteEndPoint);

                    // We got response.
                    if(socket.Poll(100,SelectMode.SelectRead)){
                        byte[] receiveBuffer = new byte[512];
                        socket.Receive(receiveBuffer);

                        // Parse message
                        STUN_Message response = new STUN_Message();
                        response.Parse(receiveBuffer);

                        // Check that transaction ID matches or not response what we want.
                        if(request.TransactionID.Equals(response.TransactionID)){
                            return response;
                        }
                    }
                }
                catch{
                }
            }

            return null;
        }

        #endregion

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Estonia Estonia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions