📄 dns.c
字号:
/* If we're doing a full autodetect, we first have to determine the
local host's FQDN. This gets quite tricky because the behavior of
gethostbyaddr() changed with Win2K so we have to use the DNS API, but
this isn't available in older versions of Windows. If we're using
the DNS API, we have to use the barely-documented
DNS_QUERY_BYPASS_CACHE option to get what we want */
if( gethostname( cachedFQDN, MAX_DNS_SIZE ) == 0 && \
( hostInfo = gethostbyname( cachedFQDN ) ) != NULL )
{
int i;
for( i = 0; hostInfo->h_addr_list[ i ] != NULL; i++ )
{
struct in_addr address;
/* Reverse the byte order for the in-addr.arpa lookup and
convert the address to dotted-decimal notation */
address.S_un.S_addr = *( ( DWORD * ) hostInfo->h_addr_list[ i ] );
sprintf( cachedFQDN, "%s.in-addr.arpa", inet_ntoa( address ) );
/* Check for a name */
if( DnsQuery( cachedFQDN, DNS_TYPE_PTR, DNS_QUERY_BYPASS_CACHE,
NULL, &pDns, NULL ) == 0 )
break;
}
}
if( pDns == NULL )
return( setSocketError( stream, "Couldn't determine FQDN of local "
"machine", CRYPT_ERROR_NOTFOUND, TRUE ) );
#ifdef __WINCE__
unicodeToAscii( fqdnBuffer, pDns->Data.PTR.pNameHost,
wcslen( pDns->Data.PTR.pNameHost ) );
#else
fqdnPtr = pDns->Data.PTR.pNameHost;
#endif /* Win32 vs. WinCE */
convertToSrv( cachedFQDN, fqdnPtr );
DnsRecordListFree( pDns, DnsFreeRecordList );
/* Remember the value that we just found to lighten the load on the
resolver when we perform repeat queries */
strcpy( fqdn, cachedFQDN );
lastFetchTime = getTime();
return( CRYPT_OK );
}
static int findHostInfo( STREAM *stream, char *hostName, int *hostPort,
const char *name )
{
PDNS_RECORD pDns = NULL, pDnsInfo = NULL, pDnsCursor;
DWORD dwRet;
int nameLength, priority = 32767;
/* If we're running on anything other than a heavily-SP'd Win2K or WinXP,
there's not much that we can do */
if( hDNS == NULL_INSTANCE )
return( setSocketError( stream, "DNS services not available",
CRYPT_ERROR_NOTFOUND, TRUE ) );
/* If we're doing a full autodetect, we construct the SRV query using
the local machine's FQDN. This fails more often than not because of
NATing and the use of private networks, but at least we can try */
if( !strCompareZ( name, "[Autodetect]" ) )
{
const int status = getSrvFQDN( stream, hostName );
if( cryptStatusError( status ) )
return( status );
name = hostName;
}
/* Perform a DNS SRV lookup to find the host info. SRV has basic load-
balancing facilities, but for now we just use the highest-priority
host that we find (it's rarely-enough used that we'll be lucky to
find SRV info, let alone any load-balancing setup) */
dwRet = DnsQuery( ( const LPSTR ) name, DNS_TYPE_SRV, DNS_QUERY_STANDARD,
NULL, &pDns, NULL );
if( dwRet != 0 || pDns == NULL )
return( getSocketError( stream, CRYPT_ERROR_NOTFOUND ) );
for( pDnsCursor = pDns; pDnsCursor != NULL;
pDnsCursor = pDnsCursor->pNext )
if( pDnsCursor->Data.SRV.wPriority < priority )
{
priority = pDnsCursor->Data.SRV.wPriority;
pDnsInfo = pDnsCursor;
}
#ifdef __WINCE__
if( pDnsInfo == NULL || \
wcslen( pDnsInfo->Data.SRV.pNameTarget ) > MAX_URL_SIZE - 1 )
#else
if( pDnsInfo == NULL || \
strlen( pDnsInfo->Data.SRV.pNameTarget ) > MAX_URL_SIZE - 1 )
#endif /* Win32 vs. WinCE */
{
DnsRecordListFree( pDns, DnsFreeRecordList );
return( setSocketError( stream, "Invalid DNS SRV entry for host",
CRYPT_ERROR_NOTFOUND, TRUE ) );
}
/* Copy over the host info for this SRV record */
#ifdef __WINCE__
nameLength = wcslen( pDnsInfo->Data.SRV.pNameTarget ) + 1;
unicodeToAscii( hostName, pDnsInfo->Data.SRV.pNameTarget, nameLength );
#else
nameLength = strlen( pDnsInfo->Data.SRV.pNameTarget ) + 1;
memcpy( hostName, pDnsInfo->Data.SRV.pNameTarget, nameLength );
#endif /* Win32 vs. WinCE */
*hostPort = pDnsInfo->Data.SRV.wPort;
/* Clean up */
DnsRecordListFree( pDns, DnsFreeRecordList );
return( CRYPT_OK );
}
#elif defined( __UNIX__ ) && \
!( defined( __CYGWIN__) || ( defined( sun ) && OSVERSION <= 5 ) || \
defined( __TANDEM_NSK__ ) || defined( __TANDEM_OSS__ ) )
#define SRV_PRIORITY_OFFSET ( NS_RRFIXEDSZ + 0 )
#define SRV_WEIGHT_OFFSET ( NS_RRFIXEDSZ + 2 )
#define SRV_PORT_OFFSET ( NS_RRFIXEDSZ + 4 )
#define SRV_NAME_OFFSET ( NS_RRFIXEDSZ + 6 )
static int getFQDN( STREAM *stream, char *fqdn )
{
struct hostent *hostInfo;
char *hostNamePtr = NULL;
int i;
/* First, get the host name, and if it's the FQDN, exit */
if( gethostname( fqdn, MAX_DNS_SIZE ) == -1 )
return( CRYPT_ERROR_NOTFOUND );
if( strchr( fqdn, '.' ) != NULL )
/* If the hostname has a dot in it, it's the FQDN */
return( CRYPT_OK );
/* Now get the hostent info and walk through it looking for the FQDN */
if( ( hostInfo = gethostbyname( fqdn ) ) == NULL )
return( CRYPT_ERROR_NOTFOUND );
for( i = 0; hostInfo->h_addr_list[ i ] != NULL; i++ )
{
char **aliasPtrPtr;
/* If the hostname has a dot in it, it's the FQDN. This should be
the same as the gethostname() output, but we check again just in
case */
if( strchr( hostInfo->h_name, '.' ) != NULL )
{
hostNamePtr = hostInfo->h_name;
break;
}
/* Try for the FQDN in the aliases */
if( hostInfo->h_aliases == NULL )
continue;
for( aliasPtrPtr = hostInfo->h_aliases;
*aliasPtrPtr != NULL && !strchr( *aliasPtrPtr, '.' );
aliasPtrPtr++ );
if( *aliasPtrPtr != NULL )
{
hostNamePtr = *aliasPtrPtr;
break;
}
}
if( hostNamePtr == NULL )
return( CRYPT_ERROR_NOTFOUND );
/* We found the FQDN, return it to the caller */
strcpy( fqdn, hostNamePtr );
return( CRYPT_OK );
}
static int findHostInfo( STREAM *stream, char *hostName, int *hostPort,
const char *name )
{
union {
HEADER header;
BYTE buffer[ NS_PACKETSZ ];
} dnsQueryInfo;
char *namePtr, *endPtr;
int resultLen, nameLen, qCount, aCount, minPriority = 32767, i;
/* If we're doing a full autodetect, we construct the SRV query using
the local machine's FQDN. This fails more often than not because of
NATing and the use of private networks, but at least we can try */
if( !strCompareZ( name, "[Autodetect]" ) )
{
const int status = getFQDN( stream, hostName );
if( cryptStatusError( status ) )
return( status );
name = hostName;
}
#ifdef EBCDIC_CHARS
else
/* We're about to use OS functions, convert the input to EBCDIC. If
we've used autodetection, the output from getFQDN will already be
in EBCDIC form */
name = bufferToEbcdic( hostName, name );
#endif /* EBCDIC_CHARS */
/* Try and fetch a DNS SRV record (RFC 2782) matching the host info */
resultLen = res_query( name, C_IN, T_SRV, dnsQueryInfo.buffer,
NS_PACKETSZ );
if( resultLen < NS_HFIXEDSZ || resultLen > NS_PACKETSZ )
return( getSocketError( stream, CRYPT_ERROR_NOTFOUND ) );
if( dnsQueryInfo.header.rcode || dnsQueryInfo.header.tc )
/* If we get a non-zero response code (rcode) or the results were
truncated (tc), we can't go any further. In theory a truncated
response is probably OK since many servers return the address
records for the host in the Additional Data section to save the
client having to perform a second lookup and we don't need these
at this point so we can ignore the fact that they've been
truncated, but for now we treat truncation as an error */
return( setSocketError( stream, "RR contains non-zero response "
"code or response was truncated",
CRYPT_ERROR_NOTFOUND, FALSE ) );
qCount = ntohs( dnsQueryInfo.header.qdcount );
aCount = ntohs( dnsQueryInfo.header.ancount );
if( qCount < 0 || aCount <= 0 )
/* No answer entries, we're done */
return( setSocketError( stream, "RR contains no answer entries",
CRYPT_ERROR_NOTFOUND, FALSE ) );
/* Skip the queries */
namePtr = dnsQueryInfo.buffer + NS_HFIXEDSZ;
endPtr = dnsQueryInfo.buffer + resultLen;
for( i = 0; i < qCount; i++ )
{
nameLen = dn_skipname( namePtr, endPtr );
if( nameLen <= 0 )
return( setSocketError( stream, "RR contains invalid question",
CRYPT_ERROR_BADDATA, FALSE ) );
namePtr += nameLen + NS_QFIXEDSZ;
}
/* Process the answers. SRV has basic load-balancing facilities, but
for now we just use the highest-priority host that we find (it's
rarely-enough used that we'll be lucky to find SRV info, let alone
any load-balancing setup) */
for( i = 0; i < aCount; i++ )
{
int priority, port;
nameLen = dn_skipname( namePtr, endPtr );
if( nameLen <= 0 )
return( setSocketError( stream, "RR contains invalid answer",
CRYPT_ERROR_BADDATA, FALSE ) );
namePtr += nameLen;
priority = ntohs( *( ( u_short * ) ( namePtr + SRV_PRIORITY_OFFSET ) ) );
port = ntohs( *( ( u_short * ) ( namePtr + SRV_PORT_OFFSET ) ) );
namePtr += NS_SRVFIXEDSZ;
if( priority < minPriority )
{
/* We've got a new higher-priority host, use that */
nameLen = dn_expand( dnsQueryInfo.buffer, endPtr,
namePtr, hostName, MAX_URL_SIZE - 1 );
*hostPort = port;
minPriority = priority;
}
else
/* It's a lower-priority host, skip it */
nameLen = dn_skipname( namePtr, endPtr );
if( nameLen <= 0 )
return( setSocketError( stream, "RR contains invalid answer",
CRYPT_ERROR_NOTFOUND, FALSE ) );
hostName[ nameLen ] = '\0';
namePtr += nameLen;
}
#ifdef EBCDIC_CHARS
ebcdicToAscii( hostName, strlen( hostName ) );
#endif /* EBCDIC_CHARS */
return( CRYPT_OK );
}
#else
/* If there's no DNS support available in the OS, there's not much that we
can do to handle automatic host detection. Setting localPort as a side-
effect is necessary because the #define otherwise no-ops it out, leading
to declared-but-not-used warnings from some compilers */
#define findHostInfo( stream, nameBuffer, localPort, name ) \
CRYPT_ERROR_NOTFOUND; *( localPort ) = -1
#endif /* OS-specific host detection */
/****************************************************************************
* *
* General DNS Interface *
* *
****************************************************************************/
/* Get a host's IP address */
int getAddressInfo( STREAM *stream, struct addrinfo **addrInfoPtrPtr,
const char *name, const int port,
const BOOLEAN isServer )
{
struct addrinfo hints;
char nameBuffer[ MAX_URL_SIZE ], portBuffer[ 16 ];
int localPort = port;
assert( isServer || name != NULL );
/* If we're a client and using auto-detection of a PKI service, try and
locate it via DNS SRV */
if( !isServer && \
( !strCompareZ( name, "[Autodetect]" ) || *name == '_' ) )
{
int status;
status = findHostInfo( stream, nameBuffer, &localPort, name );
if( cryptStatusError( status ) )
return( status );
name = nameBuffer;
}
#ifdef EBCDIC_CHARS
if( name != NULL )
name = bufferToEbcdic( nameBuffer, name );
#endif /* EBCDIC_CHARS */
/* Set up the port information and hint information needed by
getaddrinfo(). The use of PF_UNSPEC is a bit problematic because RFC
2553 is usually interpreted to mean "look for all addresses" rather
than the more sensible "look for any address". The reason why this
is a problem is because getaddrinfo() ends up looking for unnecessary
IPv6 addresses, either by returning IPv6 addresses when the system
doesn't do IPv6 or spending a lot of time groping around for IPv6
stuff and/or further unnecessary addresses when it's already got what
it needs. This is made worse by confusion over implementation
details, for example early implementations of getaddrinfo() in glibc
would always try an AAAA lookup even on an IPv4-only system/network,
resulting in long delays as the resolver timed out and fell back to a
straight A lookup. There was some disagreement over whether this was
right or wrong, and how to fix it (IPv6 purists who never noticed the
problem seemed to think that it was right, everyone else thought that
it was wrong). Variations of this problem exist, e.g. if an IPv4
address is in /etc/hosts and DNS is down, the resolver will still
spend ages (several minutes in some cases) groping around for an IPv6
address before it finally gives up and falls back to what it already
knows from /etc/hosts. Switching the hint from AF_UNSPEC to AF_INET
bypasses this problem, but has the downside of disabling IPv6 use.
This problem was partially fixed post-RFC 2553 by adding the
AI_ADDRCONFIG flag, which tells getaddrinfo() to only do AAAA queries
if the system has at least one IPv6 source address configured, and
the same for A and IPv4 (in other words it applies some common sense,
which is how it should have behaved in the first place).
Unfortunately this flag isn't very widely supported yet, so it usually
ends up being no-op'd out by the auto-config.
Bounds Checker may crash in the getaddrinfo() call if maximum checking
is enabled. To fix this, set the checking level to normal rather than
maximum */
memset( &hints, 0, sizeof( struct addrinfo ) );
sPrintf( portBuffer, "%d", port );
hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG;
if( isServer )
/* If it's a server, set the AI_PASSIVE flag so that if the
interface that we're binding to isn't explicitly specified we get
any interface */
hints.ai_flags |= AI_PASSIVE;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if( getaddrinfo( name, portBuffer, &hints, addrInfoPtrPtr ) )
return( getHostError( stream, CRYPT_ERROR_OPEN ) );
return( CRYPT_OK );
}
void freeAddressInfo( struct addrinfo *addrInfoPtr )
{
freeaddrinfo( addrInfoPtr );
}
void getNameInfo( const struct sockaddr *sockAddr, char *address,
const int addressMaxLen, int *port )
{
char portBuf[ 32 ];
/* Clear return values */
strcpy( address, "<Unknown>" );
*port = 0;
/* Some Windows implementations of getnameinfo() call down to
getservbyport() assuming that it will always succeed and therefore
leave the port/service arg unchanged when it doesn't, so the following
call must be made with the NI_NUMERICSERV flag specified (which it
would be anyway, cryptlib always treats the port as a numeric arg).
Oddly enough the macro version of this function in wspiapi.h used for
IPv4-only situations does get it correct */
if( getnameinfo( sockAddr, sizeof( struct sockaddr ), address,
addressMaxLen, portBuf, 32,
NI_NUMERICHOST | NI_NUMERICSERV ) == 0 )
{
#ifdef EBCDIC_CHARS
ebcdicToAscii( address, strlen( address ) );
ebcdicToAscii( portBuf, strlen( portBuf ) );
#endif /* EBCDIC_CHARS */
*port = aToI( portBuf );
}
}
#endif /* USE_TCP */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -