modMXQuery.bas
上传用户:bcdefg2008
上传日期:2013-02-28
资源大小:144k
文件大小:28k
源码类别:

Email服务器

开发平台:

Visual Basic

  1. Attribute VB_Name = "modMXQuery"
  2. Option Explicit
  3. ' winsock
  4. Private Const DNS_RECURSION As Byte = 1
  5. Private Const AF_INET = 2
  6. Private Const SOCKET_ERROR = -1
  7. Private Const ERROR_BUFFER_OVERFLOW = 111
  8. Private Const SOCK_DGRAM = 2
  9. Private Const INADDR_NONE = &HFFFFFFFF
  10. Private Const INADDR_ANY = &H0
  11. ' registry access
  12. Private Const REG_SZ = 1&
  13. Private Const ERROR_SUCCESS = 0&
  14. Private Const HKEY_CLASSES_ROOT = &H80000000
  15. Private Const HKEY_CURRENT_USER = &H80000001
  16. Private Const HKEY_LOCAL_MACHINE = &H80000002
  17. Private Const KEY_QUERY_VALUE = &H1&
  18. Private Const KEY_ENUMERATE_SUB_KEYS = &H8&
  19. Private Const KEY_NOTIFY = &H10&
  20. Private Const READ_CONTROL = &H20000
  21. Private Const SYNCHRONIZE = &H100000
  22. Private Const STANDARD_RIGHTS_READ = READ_CONTROL
  23. Private Const KEY_READ = STANDARD_RIGHTS_READ Or KEY_QUERY_VALUE Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY
  24. ' winsock
  25. Private Type WSADATA
  26.     wVersion                As Integer
  27.     wHighVersion            As Integer
  28.     szDescription(256)      As Byte
  29.     szSystemStatus(128)     As Byte
  30.     iMaxSockets             As Integer
  31.     iMaxUdpDg               As Integer
  32.     lpVendorInfo            As Long
  33. End Type
  34. Private Type DNS_HEADER
  35.     qryID                   As Integer
  36.     options                 As Byte
  37.     response                As Byte
  38.     qdcount                 As Integer
  39.     ancount                 As Integer
  40.     nscount                 As Integer
  41.     arcount                 As Integer
  42. End Type
  43. Private Type IP_ADDRESS_STRING
  44.     IpAddressStr(4 * 4 - 1) As Byte
  45. End Type
  46.  
  47. Private Type IP_MASK_STRING
  48.     IpMaskString(4 * 4 - 1) As Byte
  49. End Type
  50.  
  51. Private Type IP_ADDR_STRING
  52.     Next                    As Long
  53.     IpAddress               As IP_ADDRESS_STRING
  54.     IpMask                  As IP_MASK_STRING
  55.     Context                 As Long
  56. End Type
  57. Private Type FIXED_INFO
  58.     HostName(128 + 4 - 1)   As Byte
  59.     DomainName(128 + 4 - 1) As Byte
  60.     CurrentDnsServer        As Long
  61.     DnsServerList           As IP_ADDR_STRING
  62.     NodeType                As Long
  63.     ScopeId(256 + 4 - 1)    As Byte
  64.     EnableRouting           As Long
  65.     EnableProxy             As Long
  66.     EnableDns               As Long
  67. End Type
  68. Private Type SOCKADDR
  69.     sin_family              As Integer
  70.     sin_port                As Integer
  71.     sin_addr                As Long
  72.     sin_zero                As String * 8
  73. End Type
  74. Private Type HostEnt
  75.     h_name                  As Long
  76.     h_aliases               As Long
  77.     h_addrtype              As Integer
  78.     h_length                As Integer
  79.     h_addr_list             As Long
  80. End Type
  81. ' registry
  82. Private Type FILETIME
  83.     dwLowDateTime           As Long
  84.     dwHighDateTime          As Long
  85. End Type
  86. ' public type for passing DNS info
  87. Public Type DNS_INFO
  88.     Servers()               As String
  89.     Count                   As Long
  90.     LocalDomain             As String
  91.     RootDomain              As String
  92. End Type
  93. ' used below
  94. Public Type MX_RECORD
  95.     Server                  As String
  96.     Pref                    As Integer
  97. End Type
  98. ' public type for passing MX info
  99. Public Type MX_INFO
  100.     Best                    As String
  101.     Domain                  As String
  102.     List()                  As MX_RECORD
  103.     Count                   As Long
  104. End Type
  105. Public DNS                  As DNS_INFO
  106. Public MX                   As MX_INFO
  107. ' API prototypes
  108. ' winsock, 'wsock32.dll' used instead of 'ws2_32.dll' for wider compatibility
  109. Private Declare Function gethostbyname Lib "wsock32.dll" (ByVal host_name As String) As Long
  110. Private Declare Function gethostbyaddr Lib "ws2_32.dll" (addr As Long, ByVal addr_len As Long, ByVal addr_type As Long) As Long
  111. Private Declare Function inet_addr Lib "wsock32.dll" (ByVal cp As String) As Long
  112. Private Declare Function recvfrom Lib "wsock32.dll" (ByVal s As Long, buf As Any, ByVal buflen As Long, ByVal flags As Long, from As SOCKADDR, fromlen As Long) As Long
  113. Private Declare Function socket Lib "wsock32.dll" (ByVal af As Long, ByVal s_type As Long, ByVal protocol As Long) As Long
  114. Private Declare Function htons Lib "wsock32.dll" (ByVal hostshort As Long) As Integer
  115. Private Declare Function ntohs Lib "wsock32.dll" (ByVal netshort As Long) As Integer
  116. Private Declare Function sendto Lib "wsock32.dll" (ByVal s As Long, buf As Any, ByVal buflen As Long, ByVal flags As Long, to_addr As SOCKADDR, ByVal tolen As Long) As Long
  117. Private Declare Function WSAGetLastError Lib "wsock32.dll" () As Long
  118. Private Declare Function WSAStartup Lib "wsock32.dll" (ByVal wVersionRequired As Long, lpWSAData As WSADATA) As Long
  119. Private Declare Function WSACleanup Lib "wsock32.dll" () As Long
  120. ' Registry access
  121. Private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, phkResult As Long) As Long
  122. Private Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
  123. Private Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, ByVal lpData As String, lpcbData As Long) As Long
  124. Private Declare Function RegEnumKeyEx Lib "advapi32.dll" Alias "RegEnumKeyExA" (ByVal hKey As Long, ByVal dwIndex As Long, ByVal lpName As String, lpcbName As Long, ByVal lpReserved As Long, ByVal lpClass As String, lpcbClass As Long, lpftLastWriteTime As FILETIME) As Long
  125. ' misc
  126. Private Declare Function GetNetworkParams Lib "iphlpapi.dll" (pFixedInfo As Any, pOutBufLen As Long) As Long
  127. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  128. Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
  129. Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
  130. Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
  131. Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
  132. Public Sub GetDNSInfo()
  133.     ' get the DNS servers and the local IP Domain name
  134.     
  135.     Dim sBuffer                 As String
  136.     Dim sDNSBuff                As String
  137.     Dim sDomainBuff             As String
  138.     Dim sKey                    As String
  139.     Dim lngFixedInfoNeeded      As Long
  140.     Dim bytFixedInfoBuffer()    As Byte
  141.     Dim udtFixedInfo            As FIXED_INFO
  142.     Dim lngIpAddrStringPtr      As Long
  143.     Dim udtIpAddrString         As IP_ADDR_STRING
  144.     Dim strDnsIpAddress         As String
  145.     Dim nRet                    As Long
  146.     Dim sTmp()                  As String
  147.     Dim I                       As Long
  148.        
  149.     ' get dns servers with the new GetNetworkParams call (only works on 98/ME/2000)
  150.     ' if GetNetworkParams is not supported then try reading from the registry
  151.     If Exported("iphlpapi.dll", "GetNetworkParams") Then
  152.         nRet = GetNetworkParams(ByVal vbNullString, lngFixedInfoNeeded)
  153.         If nRet = ERROR_BUFFER_OVERFLOW Then
  154.             ReDim bytFixedInfoBuffer(lngFixedInfoNeeded)
  155.             nRet = GetNetworkParams(bytFixedInfoBuffer(0), lngFixedInfoNeeded)
  156.             CopyMemory udtFixedInfo, bytFixedInfoBuffer(0), Len(udtFixedInfo)
  157.             With udtFixedInfo
  158.                 ' get the DNS servers
  159.                 lngIpAddrStringPtr = VarPtr(.DnsServerList)
  160.                 Do While lngIpAddrStringPtr
  161.                     CopyMemory udtIpAddrString, ByVal lngIpAddrStringPtr, Len(udtIpAddrString)
  162.                     With udtIpAddrString
  163.                         strDnsIpAddress = StripTerminator(StrConv(.IpAddress.IpAddressStr, vbUnicode))
  164.                         sDNSBuff = sDNSBuff & strDnsIpAddress & ","
  165.                         lngIpAddrStringPtr = .Next
  166.                     End With
  167.                 Loop
  168.                 ' get the ip domain name
  169.                 sDomainBuff = StripTerminator(StrConv(.DomainName, vbUnicode))
  170.             End With
  171.         End If
  172.     End If
  173.     
  174.     ' if GetNetworkParams didn't get the data we need,
  175.     ' try known locations in the registry for DNS & domain info
  176.     If Len(sDNSBuff) = 0 Or Len(sDomainBuff) = 0 Then
  177.         ' DNS servers configured through Network control panel applet (95/98/ME)
  178.         sKey = "SystemCurrentControlSetServicesVxDMSTCP"
  179.         sBuffer = GetRegStr(HKEY_LOCAL_MACHINE, sKey, "NameServer", "")
  180.         If Len(sBuffer) Then sDNSBuff = sBuffer & ","
  181.         sBuffer = GetRegStr(HKEY_LOCAL_MACHINE, sKey, "Domain", "")
  182.         If Len(sBuffer) Then sDomainBuff = sBuffer
  183.         ' DNS servers configured through Network control panel applet (NT/2000)
  184.         sKey = "SYSTEMCurrentControlSetServicesTcpipParameters"
  185.         sBuffer = GetRegStr(HKEY_LOCAL_MACHINE, sKey, "NameServer", "")
  186.         If Len(sBuffer) Then sDNSBuff = sBuffer & ","
  187.         sBuffer = GetRegStr(HKEY_LOCAL_MACHINE, sKey, "Domain", "")
  188.         If Len(sBuffer) Then sDomainBuff = sBuffer
  189.         ' DNS servers configured DHCP (NT/2000/XP)
  190.         sKey = "SYSTEMCurrentControlSetServicesTcpipParameters"
  191.         sBuffer = GetRegStr(HKEY_LOCAL_MACHINE, sKey, "DhcpNameServer", "")
  192.         If Len(sBuffer) Then sDNSBuff = sBuffer & ","
  193.         sBuffer = GetRegStr(HKEY_LOCAL_MACHINE, sKey, "DHCPDomain", "")
  194.         If Len(sBuffer) Then sDomainBuff = sBuffer
  195.         ' DNS servers configured through Network control panel applet (XP)
  196.         sKey = "SYSTEMCurrentControlSetServicesTcpipParametersInterfaces"
  197.         sTmp = EnumRegKey(HKEY_LOCAL_MACHINE, sKey)
  198.         For I = 0 To UBound(sTmp)
  199.             sBuffer = GetRegStr(HKEY_LOCAL_MACHINE, sKey & "" & sTmp(I), "NameServer", "")
  200.             If Len(sBuffer) Then sDNSBuff = sBuffer & ","
  201.             sBuffer = GetRegStr(HKEY_LOCAL_MACHINE, sKey & "" & sTmp(I), "Domain", "")
  202.             If Len(sBuffer) Then sDomainBuff = sBuffer
  203.         Next
  204.         
  205.         ' DNS servers configured DHCP (95/98/ME)
  206.         ' *** haven't found one ***
  207.     
  208.     End If
  209.     ' get rid of any space delimiters (2000)
  210.     sDNSBuff = Replace(sDNSBuff, " ", ",")
  211.     ' trim any trailing commas
  212.     If Right(sDNSBuff, 1) = "," Then sDNSBuff = Left(sDNSBuff, Len(sDNSBuff) - 1)
  213.     ' load our type struc
  214.     DNS.Servers = Split(sDNSBuff, ",")
  215.     DNS.Count = UBound(DNS.Servers) + 1
  216.     DNS.LocalDomain = sDomainBuff
  217.     ' cheap trick
  218.     If sDomainBuff = "" And DNS.Count > 0 Then
  219.         sDomainBuff = GetRemoteHostName(DNS.Servers(0))
  220.         nRet = InStr(sDomainBuff, ".")
  221.         If nRet Then
  222.             DNS.LocalDomain = Mid$(sDomainBuff, nRet + 1)
  223.         End If
  224.     End If
  225.     sTmp = Split(sDomainBuff, ".")
  226.     nRet = UBound(sTmp)
  227.     If nRet > 0 Then
  228.         DNS.RootDomain = sTmp(nRet - 1) & "." & sTmp(nRet)
  229.     Else
  230.         DNS.RootDomain = sDomainBuff
  231.     End If
  232. End Sub
  233. Public Function MX_Query(ByVal ms_Domain As String) As String
  234.     
  235.     ' Performs the actual IP work to contact the DNS server,
  236.     ' calls the other functions to parse and return the
  237.     ' best server to send email through
  238.     
  239.     Dim StartupData     As WSADATA
  240.     Dim SocketBuffer    As SOCKADDR
  241.     Dim IpAddr          As Long
  242.     Dim iRC             As Integer
  243.     Dim dnsHead         As DNS_HEADER
  244.     Dim iSock           As Integer
  245.     Dim dnsQuery()      As Byte
  246.     Dim sQName          As String
  247.     Dim dnsQueryNdx     As Integer
  248.     Dim iTemp           As Integer
  249.     Dim iNdx            As Integer
  250.     Dim dnsReply(2048)  As Byte
  251.     Dim iAnCount        As Integer
  252.     Dim dwFlags         As Long
  253.     MX.Count = 0
  254.     MX.Best = vbNullString
  255.     ReDim MX.List(0)
  256.     ' if DNSInfo hasn't been called, call it now
  257.     If DNS.Count = 0 Then GetDNSInfo
  258.     
  259.     ' check to see that we found a dns server
  260.     If DNS.Count = 0 Then
  261.         ' problem
  262.         Err.Raise 20000, "MXQuery", "No DNS entries found, MX Query cannot contine."
  263.         Exit Function
  264.     End If
  265.    
  266.     ' if null was passed in then use the local domain name
  267.     If Len(ms_Domain) = 0 Then ms_Domain = DNS.LocalDomain
  268.     
  269.     ' validate domain name
  270.     If Len(ms_Domain) < 5 Then
  271.         Err.Raise 20000, "MXQuery", "No Valid Domain Specified"
  272.         Exit Function
  273.     End If
  274.    
  275.     MX.Domain = ms_Domain
  276.    
  277.     ' Initialize the Winsock, request v1.1
  278.     If WSAStartup(&H101, StartupData) <> ERROR_SUCCESS Then
  279.         iRC = WSACleanup
  280.         Exit Function
  281.     End If
  282.     
  283.     ' Create a socket
  284.     iSock = socket(AF_INET, SOCK_DGRAM, 0)
  285.     If iSock = SOCKET_ERROR Then Exit Function
  286.     ' convert the IP address string to a network ordered long
  287.     IpAddr = GetHostByNameAlias(DNS.Servers(0))
  288.     If IpAddr = -1 Then Exit Function
  289.     
  290.     ' Setup the connnection parameters
  291.     SocketBuffer.sin_family = AF_INET
  292.     SocketBuffer.sin_port = htons(53)
  293.     SocketBuffer.sin_addr = IpAddr
  294.     SocketBuffer.sin_zero = String$(8, 0)
  295.     
  296.     ' Set the DNS parameters
  297.     dnsHead.qryID = htons(&H11DF)
  298.     dnsHead.options = DNS_RECURSION
  299.     dnsHead.qdcount = htons(1)
  300.     dnsHead.ancount = 0
  301.     dnsHead.nscount = 0
  302.     dnsHead.arcount = 0
  303.     
  304.     dnsQueryNdx = 0
  305.     
  306.     ReDim dnsQuery(4000)
  307.     
  308.     ' Setup the dns structure to send the query in
  309.     ' First goes the DNS header information
  310.     CopyMemory dnsQuery(dnsQueryNdx), dnsHead, 12
  311.     dnsQueryNdx = dnsQueryNdx + 12
  312.     
  313.     ' Then the domain name (as a QNAME)
  314.     sQName = MakeQName(MX.Domain)
  315.     iNdx = 0
  316.     While (iNdx < Len(sQName))
  317.         dnsQuery(dnsQueryNdx + iNdx) = Asc(Mid(sQName, iNdx + 1, 1))
  318.         iNdx = iNdx + 1
  319.     Wend
  320.     dnsQueryNdx = dnsQueryNdx + Len(sQName)
  321.     
  322.     ' Null terminate the string
  323.     dnsQuery(dnsQueryNdx) = &H0
  324.     dnsQueryNdx = dnsQueryNdx + 1
  325.     
  326.     ' The type of query (15 means MX query)
  327.     iTemp = htons(15)
  328.     CopyMemory dnsQuery(dnsQueryNdx), iTemp, Len(iTemp)
  329.     dnsQueryNdx = dnsQueryNdx + Len(iTemp)
  330.     
  331.     ' The class of query (1 means INET)
  332.     iTemp = htons(1)
  333.     CopyMemory dnsQuery(dnsQueryNdx), iTemp, Len(iTemp)
  334.     dnsQueryNdx = dnsQueryNdx + Len(iTemp)
  335.     
  336.     ReDim Preserve dnsQuery(dnsQueryNdx - 1)
  337.     ' Send the query to the DNS server
  338.     iRC = sendto(iSock, dnsQuery(0), dnsQueryNdx + 1, 0, SocketBuffer, Len(SocketBuffer))
  339.     If (iRC = SOCKET_ERROR) Or (iRC = 0) Then
  340.         Err.Raise 20000, "MXQuery", "Problem sending MX query"
  341.         iRC = WSACleanup
  342.         Exit Function
  343.     End If
  344.     ' Wait for answer from the DNS server
  345.     iRC = recvfrom(iSock, dnsReply(0), 2048, 0, SocketBuffer, Len(SocketBuffer))
  346.     If (iRC = SOCKET_ERROR) Or (iRC = 0) Then
  347.         Err.Raise 20000, "MXQuery", "Problem receiving MX query"
  348.         iRC = WSACleanup
  349.         Exit Function
  350.     End If
  351.     ' Get the number of answers
  352.     CopyMemory iAnCount, dnsReply(6), 2
  353.     iAnCount = ntohs(iAnCount)
  354.     
  355.     iRC = WSACleanup
  356.     
  357.     If iAnCount Then
  358.         ' Parse the answer buffer
  359.         MX_Query = GetMXName(dnsReply(), 12, iAnCount)
  360.         
  361.     Else
  362.         ' if we didn't find anything and we are part of
  363.         ' a sub domain, go up one level and try again
  364.         ' the last pass is at the root domain level
  365.         If InStr(MX.Domain, DNS.RootDomain) > 1 Then
  366.             MX.Domain = Mid$(MX.Domain, InStr(MX.Domain, ".") + 1)
  367.             MX_Query = MX_Query(MX.Domain)
  368.         End If
  369.     End If
  370.     
  371. End Function
  372. Private Sub ParseName(dnsReply() As Byte, iNdx As Integer, sName As String)
  373.     
  374. ' Parse the server name out of the MX record, returns it in variable sName.
  375. ' iNdx is also modified to point to the end of the parsed structure.
  376.     
  377.     Dim iCompress       As Integer      ' Compression index (index to original buffer)
  378.     Dim iChCount        As Integer      ' Character count (number of chars to read from buffer)
  379.         
  380.     ' While we dont encounter a null char (end-of-string specifier)
  381.     While (dnsReply(iNdx) <> 0)
  382.         ' Read the next character in the stream (length specifier)
  383.         iChCount = dnsReply(iNdx)
  384.         ' If our length specifier is 192 (0xc0) we have a compressed string
  385.         If (iChCount = 192) Then
  386.             ' Read the location of the rest of the string (offset into buffer)
  387.             iCompress = dnsReply(iNdx + 1)
  388.             ' Call ourself again, this time with the offset of the compressed string
  389.             ParseName dnsReply(), iCompress, sName
  390.             ' Step over the compression indicator and compression index
  391.             iNdx = iNdx + 2
  392.             ' After a compressed string, we are done
  393.             Exit Sub
  394.         End If
  395.         
  396.         ' Move to next char
  397.         iNdx = iNdx + 1
  398.         ' While we should still be reading chars
  399.         While (iChCount)
  400.             ' add the char to our string
  401.             sName = sName + Chr(dnsReply(iNdx))
  402.             iChCount = iChCount - 1
  403.             iNdx = iNdx + 1
  404.         Wend
  405.         ' If the next char isn't null then the string continues, so add the dot
  406.         If (dnsReply(iNdx) <> 0) Then sName = sName + "."
  407.     Wend
  408.     
  409. End Sub
  410. Private Function GetMXName(dnsReply() As Byte, iNdx As Integer, iAnCount As Integer) As String
  411.     
  412. ' Parses the buffer returned by the DNS server, returns the best
  413. ' MX server (lowest preference number), iNdx is modified to point
  414. ' to the current buffer position (should be the end of the buffer
  415. ' by the end, unless a record other than MX is found)
  416.     
  417.     Dim iChCount        As Integer     ' Character counter
  418.     Dim sTemp           As String      ' Holds the original query string
  419.     Dim iBestPref       As Integer     ' Holds the "best" preference number (lowest)
  420.     Dim iMXCount        As Integer
  421.     
  422.     
  423.     MX.Count = 0
  424.     MX.Best = vbNullString
  425.     ReDim MX.List(0)
  426.     iMXCount = 0
  427.     iBestPref = -1
  428.     
  429.     ParseName dnsReply(), iNdx, sTemp
  430.     
  431.     ' Step over null
  432.     iNdx = iNdx + 2
  433.     
  434.     ' Step over 6 bytes, not sure what the 6 bytes are, but
  435.     ' all other documentation shows steping over these 6 bytes
  436.     iNdx = iNdx + 6
  437.     
  438.     While (iAnCount)
  439.         ' Check to make sure we received an MX record
  440.         If (dnsReply(iNdx) = 15) Then
  441.             Dim sName As String
  442.             Dim iPref As Integer
  443.             
  444.             sName = ""
  445.             
  446.             ' Step over the last half of the integer that specifies the record type (1 byte)
  447.             ' Step over the RR Type, RR Class, TTL (3 integers - 6 bytes)
  448.             iNdx = iNdx + 1 + 6
  449.             
  450.             ' Step over the MX data length specifier (1 integer - 2 bytes)
  451.             iNdx = iNdx + 2
  452.             
  453.             CopyMemory iPref, dnsReply(iNdx), 2
  454.             iPref = ntohs(iPref)
  455.             ' Step over the MX preference value (1 integer - 2 bytes)
  456.             iNdx = iNdx + 2
  457.             
  458.             ' Have to step through the byte-stream, looking for 0xc0 or 192 (compression char)
  459.             ParseName dnsReply(), iNdx, sName
  460.             
  461.             If Trim(sName) <> "" Then
  462.                 iMXCount = iMXCount + 1
  463.                 ReDim Preserve MX.List(iMXCount - 1)
  464.                 MX.List(iMXCount - 1).Server = sName
  465.                 MX.List(iMXCount - 1).Pref = iPref
  466.                 MX.Count = iMXCount
  467.                 If (iBestPref = -1 Or iPref < iBestPref) Then
  468.                     iBestPref = iPref
  469.                     MX.Best = sName
  470.                 End If
  471.             End If
  472.             ' Step over 3 useless bytes
  473.             iNdx = iNdx + 3
  474.         Else
  475.             GetMXName = MX.Best
  476.             SortMX MX.List
  477.             Exit Function
  478.         End If
  479.         iAnCount = iAnCount - 1
  480.     Wend
  481.     
  482.     SortMX MX.List
  483.         
  484.     GetMXName = MX.Best
  485. End Function
  486. Private Function MakeQName(sDomain As String) As String
  487.     
  488. ' Takes sDomain and converts it to the QNAME-type string.
  489. ' QNAME is how a DNS server expects the string.
  490. '
  491. ' Example:  Pass -        mail.com
  492. '           Returns -     &H4mail&H3com
  493. '                          ^      ^
  494. '                          |______|____ These two are character counters, they count
  495. '                                       the number of characters appearing after them
  496.     
  497.     Dim iQCount         As Integer      ' Character count (between dots)
  498.     Dim iNdx            As Integer      ' Index into sDomain string
  499.     Dim iCount          As Integer      ' Total chars in sDomain string
  500.     Dim sQName          As String       ' QNAME string
  501.     Dim sDotName        As String       ' Temp string for chars between dots
  502.     Dim sChar           As String       ' Single char from sDomain string
  503.     
  504.     iNdx = 1
  505.     iQCount = 0
  506.     iCount = Len(sDomain)
  507.     
  508.     ' While we haven't hit end-of-string
  509.     While (iNdx <= iCount)
  510.         ' Read a single char from our domain
  511.         sChar = Mid(sDomain, iNdx, 1)
  512.         ' If the char is a dot, then put our character count and the part of the string
  513.         If (sChar = ".") Then
  514.             sQName = sQName & Chr(iQCount) & sDotName
  515.             iQCount = 0
  516.             sDotName = ""
  517.         Else
  518.             sDotName = sDotName + sChar
  519.             iQCount = iQCount + 1
  520.         End If
  521.         iNdx = iNdx + 1
  522.     Wend
  523.     
  524.     sQName = sQName & Chr(iQCount) & sDotName
  525.     
  526.     MakeQName = sQName
  527.     
  528. End Function
  529. Private Function GetHostByNameAlias(ByVal sHostName As String) As Long
  530.     
  531.     'Return IP address as a long, in network byte order
  532.     
  533.     Dim phe             As Long
  534.     Dim heDestHost      As HostEnt
  535.     Dim addrList        As Long
  536.     Dim retIP           As Long
  537.     
  538.     retIP = inet_addr(sHostName)
  539.     
  540.     If retIP = INADDR_NONE Then
  541.         phe = gethostbyname(sHostName)
  542.         If phe <> 0 Then
  543.             CopyMemory heDestHost, ByVal phe, LenB(heDestHost)
  544.             CopyMemory addrList, ByVal heDestHost.h_addr_list, 4
  545.             CopyMemory retIP, ByVal addrList, heDestHost.h_length
  546.         Else
  547.             retIP = INADDR_NONE
  548.         End If
  549.     End If
  550.     
  551.     GetHostByNameAlias = retIP
  552.     
  553. End Function
  554. Private Function StripTerminator(ByVal strString As String) As String
  555.     
  556.     ' strip off trailing NULL's from API calls
  557.     
  558.     Dim intZeroPos      As Integer
  559.     intZeroPos = InStr(strString, vbNullChar)
  560.     
  561.     If intZeroPos > 1 Then
  562.         StripTerminator = Trim$(Left$(strString, intZeroPos - 1))
  563.     ElseIf intZeroPos = 1 Then
  564.         StripTerminator = vbNullString
  565.     Else
  566.         StripTerminator = strString
  567.     End If
  568.     
  569. End Function
  570. Private Function GetRegStr(hKeyRoot As Long, ByVal sKeyName As String, ByVal sValueName As String, Optional ByVal Default As String = "") As String
  571.    
  572.    Dim lRet             As Long
  573.    Dim hKey             As Long
  574.    Dim lType            As Long
  575.    Dim lBytes           As Long
  576.    Dim sBuff            As String
  577.    
  578.    ' in case there's a permissions violation
  579.    On Local Error GoTo Err_Reg
  580.    ' Assume failure and set return to Default
  581.    GetRegStr = Default
  582.    ' Open the key
  583.    lRet = RegOpenKeyEx(hKeyRoot, sKeyName, 0&, KEY_READ, hKey)
  584.    If lRet = ERROR_SUCCESS Then
  585.       
  586.       ' Determine the buffer size
  587.       lRet = RegQueryValueEx(hKey, sValueName, 0&, lType, ByVal sBuff, lBytes)
  588.       If lRet = ERROR_SUCCESS Then
  589.          ' size the buffer & call again
  590.          If lBytes > 0 Then
  591.             sBuff = Space(lBytes)
  592.             lRet = RegQueryValueEx(hKey, sValueName, 0&, lType, ByVal sBuff, Len(sBuff))
  593.             If lRet = ERROR_SUCCESS Then
  594.                ' Trim NULL and return
  595.                GetRegStr = Left(sBuff, lBytes - 1)
  596.             End If
  597.          End If
  598.       End If
  599.       Call RegCloseKey(hKey)
  600.    End If
  601.    
  602.    Exit Function
  603.    
  604. Err_Reg:
  605.   If hKey Then Call RegCloseKey(hKey)
  606.    
  607. End Function
  608. Private Function EnumRegKey(hKeyRoot As Long, sKeyName As String) As String()
  609.     
  610.     Dim lRet            As Long
  611.     Dim ft              As FILETIME
  612.     Dim hKey            As Long
  613.     Dim CurIdx          As Long
  614.     Dim KeyName         As String
  615.     Dim ClassName       As String
  616.     Dim KeyLen          As Long
  617.     Dim ClassLen        As Long
  618.     Dim RESERVED        As Long
  619.     Dim sEnum()         As String
  620.     
  621.     On Local Error GoTo Err_Enum
  622.     
  623.     ' initialize array
  624.     EnumRegKey = Split("", "")
  625.     
  626.     ' Open the key
  627.     lRet = RegOpenKeyEx(hKeyRoot, sKeyName, 0&, KEY_READ, hKey)
  628.     If lRet <> ERROR_SUCCESS Then Exit Function
  629.     
  630.     ' the key opened so get all the sub keys
  631.     Do
  632.         ' get each sub key until lRet = error
  633.         KeyLen = 2000
  634.         ClassLen = 2000
  635.         KeyName = String$(KeyLen, 0)
  636.         ClassName = String$(ClassLen, 0)
  637.         lRet = RegEnumKeyEx(hKey, CurIdx, KeyName, KeyLen, RESERVED, ClassName, ClassLen, ft)
  638.         If lRet = ERROR_SUCCESS Then
  639.             ReDim Preserve sEnum(CurIdx)
  640.             sEnum(CurIdx) = Left$(KeyName, KeyLen)
  641.         End If
  642.     
  643.         CurIdx = CurIdx + 1
  644.         
  645.     Loop While lRet = ERROR_SUCCESS
  646.       
  647. Err_Enum:
  648.     EnumRegKey = sEnum
  649.     If hKey Then Call RegCloseKey(hKey)
  650. End Function
  651. Private Function Exported(ByVal ModuleName As String, ByVal ProcName As String) As Boolean
  652.    
  653.     ' see if the api supports a call
  654.     
  655.     Dim hModule         As Long
  656.     Dim lpProc          As Long
  657.     Dim FreeLib         As Boolean
  658.    
  659.     ' check to see if the module is already
  660.     ' mapped into this process.
  661.     hModule = GetModuleHandle(ModuleName)
  662.     If hModule = 0 Then
  663.         ' not mapped, load the module into this process.
  664.         hModule = LoadLibrary(ModuleName)
  665.         FreeLib = True
  666.     End If
  667.    
  668.     ' check the procedure address to verify it's exported.
  669.     If hModule Then
  670.         lpProc = GetProcAddress(hModule, ProcName)
  671.         Exported = (lpProc <> 0)
  672.     End If
  673.    
  674.     ' unload library if we loaded it here.
  675.     If FreeLib Then Call FreeLibrary(hModule)
  676.     
  677. End Function
  678. Private Sub SortMX(arr() As MX_RECORD, Optional ByVal bSortDesc As Boolean = False)
  679.     ' simple bubble sort
  680.     Dim ValMX           As MX_RECORD
  681.     Dim index           As Long
  682.     Dim firstItem       As Long
  683.     Dim indexLimit      As Long
  684.     Dim lastSwap        As Long
  685.     firstItem = LBound(arr)
  686.     lastSwap = UBound(arr)
  687.     
  688.     Do
  689.         indexLimit = lastSwap - 1
  690.         lastSwap = 0
  691.         For index = firstItem To indexLimit
  692.             ValMX.Pref = arr(index).Pref
  693.             ValMX.Server = arr(index).Server
  694.             If (ValMX.Pref > arr(index + 1).Pref) Xor bSortDesc Then
  695.                 ' if the items are not in order, swap them
  696.                 arr(index).Pref = arr(index + 1).Pref
  697.                 arr(index).Server = arr(index + 1).Server
  698.                 arr(index + 1).Pref = ValMX.Pref
  699.                 arr(index + 1).Server = ValMX.Server
  700.                 lastSwap = index
  701.             End If
  702.         Next
  703.     Loop While lastSwap
  704. End Sub
  705. Private Function GetRemoteHostName(ByVal strIpAddress As String) As String
  706.     Dim udtHostEnt      As HostEnt  ' HOSTENT structure
  707.     Dim lngPtrHostEnt   As Long     ' pointer to HOSTENT
  708.     Dim lngInetAddr     As Long     ' address as a Long value
  709.     Dim strHostName     As String   ' string buffer for host name
  710.     ' initialize the buffer
  711.     strHostName = String(256, 0)
  712.     ' Convert IP address to Long
  713.     lngInetAddr = inet_addr(strIpAddress)
  714.     If lngInetAddr = INADDR_NONE Then Exit Function
  715.         
  716.     ' Get the HostEnt structure pointer
  717.     lngPtrHostEnt = gethostbyaddr(lngInetAddr, 4, AF_INET)
  718.     If lngPtrHostEnt = 0 Then Exit Function
  719.             
  720.     ' Copy data into the HostEnt structure
  721.     CopyMemory udtHostEnt, ByVal lngPtrHostEnt, LenB(udtHostEnt)
  722.     CopyMemory ByVal strHostName, ByVal udtHostEnt.h_name, Len(strHostName)
  723.     GetRemoteHostName = StripTerminator(strHostName)
  724. End Function