cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
dns_srv.c
Go to the documentation of this file.
1 /****************************************************************************
2 * *
3 * cryptlib DNS SRV Interface Routines *
4 * Copyright Peter Gutmann 1998-2007 *
5 * *
6 ****************************************************************************/
7 
8 #include <ctype.h>
9 #if defined( INC_ALL )
10  #include "crypt.h"
11  #include "stream_int.h"
12  #include "tcp.h"
13 #else
14  #include "crypt.h"
15  #include "io/stream_int.h"
16  #include "io/tcp.h"
17 #endif /* Compiler-specific includes */
18 
19 /* Use DNS SRV to auto-detect host information. Note that this code is
20  disabled by default, before enabling it you should make sure that your
21  system's DNS services can't serve as an attack vector due to the
22  complexity of DNS packet processing. The Unix DNS interface is
23  particularly bad here, the problematic nature of the requirement that
24  implementations manually disassemble the DNS data themselves has been
25  demonstrated by the numerous bugs that have hit implementations that did
26  this, examples being the bind 9.2.1 gethostans() vulnerability and, in a
27  rather extreme example, the l0pht antisniff 1.0 vulnerability which
28  required no less than three successive patches to the same code to
29  finally eradicate the one bug (!!). The fact that every new
30  implementation that wants to use this functionality has to independently
31  reinvent the code to do it means that these vulnerabilities will be with
32  us more or less forever, which is why this facility is disabled by
33  default */
34 
35 #if defined( USE_TCP ) && defined( USE_DNSSRV )
36 
37 #if defined( _MSC_VER )
38  #pragma message( " Building with DNS SRV enabled." )
39 #endif /* Warn with VC++ */
40 
41 /****************************************************************************
42 * *
43 * Init/Shutdown Routines *
44 * *
45 ****************************************************************************/
46 
47 #ifdef __WINDOWS__
48 
49 #ifndef TEXT
50  #define TEXT /* Win32 windows.h defines this, but not the Win16 one */
51 #endif /* TEXT */
52 
53 #if defined( _MSC_VER ) && defined( _PREFAST_ )
54  #define OUT_STRING_OPT __out_bcount_z
55 #else
56  #define OUT_STRING_OPT( size )
57 #endif /* Additional markups for Win32 API functions */
58 
59 #ifdef DnsQuery
60  #undef DnsQuery /* Newer versions of the platform SDK have conflicting defines */
61 #endif /* DnsQuery defined as a macro in windns.h */
62 
63 /* Global function pointers. These are necessary because the functions need
64  to be dynamically linked since not all systems contain the necessary
65  libraries */
66 
67 typedef CHECK_RETVAL struct hostent FAR * ( SOCKET_API *GETHOSTNAME )\
68  ( OUT_STRING_OPT( namelen ) \
69  char FAR *name, int namelen ) \
70  STDC_NONNULL_ARG( ( 1 ) );
71 typedef CHECK_RETVAL struct hostent FAR * ( SOCKET_API *GETHOSTBYNAME )\
72  ( IN_STRING const char FAR *name ) \
73  STDC_NONNULL_ARG( ( 1 ) );
74 typedef CHECK_RETVAL char FAR * ( SOCKET_API *INET_NTOA )( struct in_addr in );
75 #if defined( _MSC_VER ) && ( _MSC_VER > 800 )
76 typedef CHECK_RETVAL DNS_STATUS ( WINAPI *DNSQUERY )\
77  ( IN_STRING const LPSTR lpstrName, const WORD wType,
78  const DWORD fOptions, const PIP4_ARRAY aipServers,
79  OUT_PTR PDNS_RECORD *ppQueryResultsSet,
80  STDC_UNUSED PVOID *pReserved ) \
81  STDC_NONNULL_ARG( ( 1, 4, 5 ) );
82 typedef VOID ( WINAPI *DNSFREEFN )\
83  ( IN PVOID pData, DNS_FREE_TYPE FreeType ) \
84  STDC_NONNULL_ARG( ( 1 ) );
85 typedef VOID ( WINAPI *DNSRECORDLISTFREE )\
86  ( IN PDNS_RECORD pRecordList, DNS_FREE_TYPE FreeType ) \
87  STDC_NONNULL_ARG( ( 1 ) );
88  /* This is a special case, in Win2K it was DnsRecordListFree(), in WinXP
89  and newer this changed to DnsFree() with almost the same parameters,
90  the first one was changed from a PDNS_RECORD to a PVOID. We just
91  point the function pointer to the appropriate one */
92 
93 static GETHOSTNAME pgethostname = NULL;
94 static GETHOSTBYNAME pgethostbyname = NULL;
95 static INET_NTOA pinet_ntoa = NULL;
96 static DNSQUERY pDnsQuery = NULL;
97 static DNSFREEFN pDnsFreeFn = NULL;
98 
99 #define gethostname pgethostname
100 #define gethostbyname pgethostbyname
101 #define inet_ntoa pinet_ntoa
102 #define DnsQuery pDnsQuery
103 #define DnsFreeFn pDnsFreeFn
104 
105 #endif /* 32-bit VC++ */
106 
107 static INSTANCE_HANDLE hDNS;
108 
109 CHECK_RETVAL \
110 int initDNSSRV( const INSTANCE_HANDLE hTCP )
111  {
112  /* Get the required TCP/IP functions */
113  gethostname = ( GETHOSTNAME ) DynamicBind( hTCP, TEXT( "gethostname" ) );
114  gethostbyname = ( GETHOSTBYNAME ) DynamicBind( hTCP, TEXT( "gethostbyname" ) );
115  inet_ntoa = ( INET_NTOA ) DynamicBind( hTCP, TEXT( "inet_ntoa" ) );
116  if( gethostname == NULL || gethostbyname == NULL || inet_ntoa == NULL )
117  return( CRYPT_ERROR );
118 
119  /* Get the required DNS functions if they're available */
120 #if defined( __WIN16__ )
121  hDNS = NULL_INSTANCE;
122 #else
123  #if defined( __WIN32__ )
124  hDNS = DynamicLoad( "dnsapi.dll" );
125  #elif defined( __WINCE__ )
126  hDNS = hTCP;
127  #endif /* Win32 vs.WinCE */
128  if( hDNS != NULL_INSTANCE )
129  {
130  DnsQuery = ( DNSQUERY ) DynamicBind( hDNS, TEXT( "DnsQuery_A" ) );
131  DnsFreeFn = ( DNSFREEFN ) DynamicBind( hDNS, TEXT( "DnsFree" ) );
132  if( DnsFreeFn == NULL )
133  DnsFreeFn = ( DNSFREEFN ) DynamicBind( hDNS, TEXT( "DnsRecordListFree" ) );
134  if( ( DnsQuery == NULL || DnsFreeFn == NULL ) && hDNS != hTCP )
135  {
136  DynamicUnload( hDNS );
137  hDNS = NULL_INSTANCE;
138  return( CRYPT_ERROR );
139  }
140  }
141 #endif /* Win16 vs.Win32/WinCE */
142 
143  return( CRYPT_OK );
144  }
145 
146 void endDNSSRV( const INSTANCE_HANDLE hTCP )
147  {
148  if( hDNS != NULL_INSTANCE && hDNS != hTCP )
149  DynamicUnload( hDNS );
150  hDNS = NULL_INSTANCE;
151  }
152 #endif /* __WINDOWS__ */
153 
154 /****************************************************************************
155 * *
156 * Windows DNS SRV Interface *
157 * *
158 ****************************************************************************/
159 
160 #if defined( __WINDOWS__ ) && !defined( __WIN16__ )
161 
162 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
163 static int convertToSrv( OUT_BUFFER_FIXED( srvNameMaxLen ) char *srvName,
164  IN_LENGTH_DNS const int srvNameMaxLen,
165  IN_STRING const char *hostName )
166  {
167  const int hostNameLength = strlen( hostName ) + 1;
168  int i; /* For trailing '\0' */
169 
170  assert( isReadPtr( srvName, srvNameMaxLen ) );
171  assert( isReadPtr( hostName, MIN_DNS_SIZE ) );
172 
173  REQUIRES( srvNameMaxLen > 0 && srvNameMaxLen <= MAX_DNS_SIZE );
174  REQUIRES( srvName != hostName );
175 
176  /* Clear return value */
177  memset( srvName, 0, min( 16, srvNameMaxLen ) );
178 
179  /* Make sure that the (worst-case) result will fit into the output
180  buffer */
181  if( 16 + hostNameLength > srvNameMaxLen )
182  return( CRYPT_ERROR_OVERFLOW );
183 
184  /* Prepend the service info to the start of the host name. This
185  converts foo.bar.com into _pkiboot._tcp.bar.com in preparation for
186  the DNS SRV lookup */
187  for( i = 0; i < hostNameLength; i++ )
188  {
189  if( hostName[ i ] == '.' )
190  break;
191  }
192  memcpy( srvName, "_pkiboot._tcp.", 14 );
193  if( i < hostNameLength )
194  memcpy( srvName + 14, hostName + i, hostNameLength - i );
195  else
196  memcpy( srvName + 14, hostName, hostNameLength );
197 
198  return( CRYPT_OK );
199  }
200 
201 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
202 static int getSrvFQDN( INOUT NET_STREAM_INFO *netStream,
203  OUT_BUFFER_FIXED( fqdnMaxLen ) char *fqdn,
204  IN_LENGTH_DNS const int fqdnMaxLen )
205  {
206  PDNS_RECORD pDns = NULL;
207  struct hostent *hostInfo;
208  static char cachedFQDN[ MAX_DNS_SIZE + 8 ];
209  static time_t lastFetchTime = 0;
210 #ifdef __WINCE__
211  char fqdnBuffer[ MAX_DNS_SIZE + 8 ], *fqdnPtr = fqdnBuffer;
212 #else
213  char *fqdnPtr;
214 #endif /* Win32 vs. WinCE */
215  int status;
216 
217  assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
218  assert( isWritePtr( fqdn, fqdnMaxLen ) );
219 
220  REQUIRES( fqdnMaxLen > 0 && fqdnMaxLen <= MAX_DNS_SIZE );
221 
222  /* Clear return value */
223  memset( fqdn, 0, min( 16, fqdnMaxLen ) );
224 
225  /* The uncached FQDN check is quite slow and resource-intensive (it
226  seems to do a full reload of the DNS subsystem), to lighten the load
227  we only try a new one once a minute */
228  if( lastFetchTime >= getTime() - 60 )
229  {
230  strlcpy_s( fqdn, fqdnMaxLen, cachedFQDN );
231  return( CRYPT_OK );
232  }
233 
234  /* If we're doing a full autodetect we first have to determine the local
235  host's FQDN. This gets quite tricky because the behavior of
236  gethostbyaddr() changed with Win2K so we have to use the DNS API, but
237  this isn't available in older versions of Windows. If we are using
238  the DNS API, we have to use the barely-documented
239  DNS_QUERY_BYPASS_CACHE option to get what we want */
240  if( gethostname( cachedFQDN, MAX_DNS_SIZE ) == 0 && \
241  ( hostInfo = gethostbyname( cachedFQDN ) ) != NULL )
242  {
243  int i;
244 
245  for( i = 0;
246  i < IP_ADDR_COUNT && hostInfo->h_addr_list[ i ] != NULL;
247  i++ )
248  {
249  struct in_addr address;
250 
251  /* Reverse the byte order for the in-addr.arpa lookup and
252  convert the address to dotted-decimal notation */
253  address.S_un.S_addr = *( ( DWORD * ) hostInfo->h_addr_list[ i ] );
254  sprintf_s( cachedFQDN, MAX_DNS_SIZE, "%s.in-addr.arpa",
255  inet_ntoa( address ) );
256 
257  /* Check for a name */
258  if( DnsQuery( cachedFQDN, DNS_TYPE_PTR, DNS_QUERY_BYPASS_CACHE,
259  NULL, &pDns, NULL ) == 0 )
260  break;
261  }
262  }
263  if( pDns == NULL )
264  {
265  return( setSocketError( netStream,
266  "Couldn't determine FQDN of local machine",
267  40, CRYPT_ERROR_NOTFOUND, TRUE ) );
268  }
269 #ifdef __WINCE__
270  unicodeToAscii( fqdnBuffer, MAX_DNS_SIZE, pDns->Data.PTR.pNameHost,
271  wcslen( pDns->Data.PTR.pNameHost ) + 1 );
272 #else
273  fqdnPtr = pDns->Data.PTR.pNameHost;
274 #endif /* Win32 vs. WinCE */
275  status = convertToSrv( cachedFQDN, MAX_DNS_SIZE, fqdnPtr );
276  DnsFreeFn( pDns, DnsFreeRecordList );
277  if( cryptStatusError( status ) )
278  {
279  return( setSocketError( netStream,
280  "Couldn't convert FQDN into SRV query name",
281  41, CRYPT_ERROR_NOTFOUND, TRUE ) );
282  }
283 
284  /* Remember the value that we just found to lighten the load on the
285  resolver when we perform repeat queries */
286  strlcpy_s( fqdn, fqdnMaxLen, cachedFQDN );
287  lastFetchTime = getTime();
288 
289  return( CRYPT_OK );
290  }
291 
292 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4, 5 ) ) \
293 int findHostInfo( INOUT NET_STREAM_INFO *netStream,
294  OUT_BUFFER_FIXED( hostNameMaxLen ) char *hostName,
295  IN_LENGTH_DNS const int hostNameMaxLen,
296  OUT_PORT_Z int *hostPort,
297  IN_BUFFER( nameLen ) const char *name,
298  IN_LENGTH_DNS const int nameLen )
299  {
300  PDNS_RECORD pDns = NULL, pDnsInfo = NULL, pDnsCursor;
301  DWORD dwRet;
302  char nameBuffer[ MAX_DNS_SIZE + 8 ];
303  int priority = 32767, i;
304 
305  assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
306  assert( isWritePtr( hostName, hostNameMaxLen ) );
307  assert( isWritePtr( hostPort, sizeof( int ) ) );
308  assert( isReadPtr( name, MIN_DNS_SIZE ) );
309 
310  REQUIRES( hostNameMaxLen > 0 && hostNameMaxLen <= MAX_DNS_SIZE );
311  REQUIRES( nameLen > 0 && nameLen < MAX_DNS_SIZE );
312  REQUIRES( hostName != name );
313 
314  /* Clear return values */
315  memset( hostName, 0, min( 16, hostNameMaxLen ) );
316  *hostPort = 0;
317 
318  /* Convert the name to a null-terminated string */
319  memcpy( nameBuffer, name, nameLen );
320  nameBuffer[ nameLen ] = '\0';
321  name = nameBuffer;
322 
323  /* If we're running on anything other than a heavily-SP'd Win2K or
324  WinXP and newer there's not much that we can do */
325  if( hDNS == NULL_INSTANCE )
326  {
327  return( setSocketError( netStream, "DNS services not available", 26,
329  }
330 
331  /* If we're doing a full autodetect we construct the SRV query using the
332  local machine's FQDN. This fails more often than not because of
333  NATing and the use of private networks, but at least we can try */
334  if( !strCompareZ( name, "[Autodetect]" ) )
335  {
336  const int status = getSrvFQDN( netStream, hostName, hostNameMaxLen );
337  if( cryptStatusError( status ) )
338  return( status );
339  name = hostName;
340  }
341 
342  /* Perform a DNS SRV lookup to find the host info. SRV has basic load-
343  balancing facilities but for now we just use the highest-priority
344  host that we find (it's rarely-enough used that we'll be lucky to
345  find SRV info let alone any load-balancing setup) */
346  dwRet = DnsQuery( ( const LPSTR ) name, DNS_TYPE_SRV, DNS_QUERY_STANDARD,
347  NULL, &pDns, NULL );
348  if( dwRet != 0 || pDns == NULL )
349  {
350  int dummy;
351 
352  return( getSocketError( netStream, CRYPT_ERROR_NOTFOUND, &dummy ) );
353  }
354  for( pDnsCursor = pDns, i = 0;
355  pDnsCursor != NULL && i < IP_ADDR_COUNT;
356  pDnsCursor = pDnsCursor->pNext, i++ )
357  {
358  if( pDnsCursor->Data.SRV.wPriority < priority )
359  {
360  priority = pDnsCursor->Data.SRV.wPriority;
361  pDnsInfo = pDnsCursor;
362  }
363  }
364 #ifdef __WINCE__
365  if( pDnsInfo == NULL || \
366  wcslen( pDnsInfo->Data.SRV.pNameTarget ) + 1 > hostNameMaxLen )
367 #else
368  if( pDnsInfo == NULL || \
369  strlen( pDnsInfo->Data.SRV.pNameTarget ) + 1 > hostNameMaxLen )
370 #endif /* Win32 vs. WinCE */
371  {
372  DnsFreeFn( pDns, DnsFreeRecordList );
373  return( setSocketError( netStream, "Invalid DNS SRV entry for host", 30,
375  }
376 
377  /* Copy over the host info for this SRV record */
378 #ifdef __WINCE__
379  unicodeToAscii( hostName, hostNameMaxLen,
380  pDnsInfo->Data.SRV.pNameTarget,
381  wcslen( pDnsInfo->Data.SRV.pNameTarget ) + 1 );
382 #else
383  strlcpy_s( hostName, hostNameMaxLen, pDnsInfo->Data.SRV.pNameTarget );
384 #endif /* Win32 vs. WinCE */
385  *hostPort = pDnsInfo->Data.SRV.wPort;
386 
387  /* Clean up */
388  DnsFreeFn( pDns, DnsFreeRecordList );
389  return( CRYPT_OK );
390  }
391 
392 /****************************************************************************
393 * *
394 * Unix DNS SRV Interface *
395 * *
396 ****************************************************************************/
397 
398 #elif defined( __UNIX__ ) && \
399  !( defined( __CYGWIN__) || ( defined( sun ) && OSVERSION <= 5 ) || \
400  defined( __TANDEM_NSK__ ) || defined( __TANDEM_OSS__ ) || \
401  defined( __UCLIBC__ ) )
402 
403 #define SRV_PRIORITY_OFFSET ( NS_RRFIXEDSZ + 0 )
404 #define SRV_WEIGHT_OFFSET ( NS_RRFIXEDSZ + 2 )
405 #define SRV_PORT_OFFSET ( NS_RRFIXEDSZ + 4 )
406 #define SRV_NAME_OFFSET ( NS_RRFIXEDSZ + 6 )
407 
408 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
409 static int getFQDN( INOUT NET_STREAM_INFO *netStream,
410  OUT_BUFFER_FIXED( fqdnMaxLen ) char *fqdn,
411  IN_LENGTH_DNS const int fqdnMaxLen )
412  {
413  struct hostent *hostInfo;
414  char *hostNamePtr = NULL;
415  int addressCount;
416 
417  assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
418  assert( isWritePtr( fqdn, fqdnMaxLen ) );
419 
420  REQUIRES( fqdnMaxLen > 0 && fqdnMaxLen <= MAX_DNS_SIZE );
421 
422  /* Clear return value */
423  memset( fqdn, 0, min( 16, fqdnMaxLen ) );
424 
425  /* First get the host name and if it's the FQDN, exit. gethostname()
426  has the idiotic property that if the name doesn't fit into the given
427  buffer the function will return a (possibly non-null-terminated)
428  truncated value instead of reporting an error (or at least that's
429  what the spec says, hopefully no implementation is stupid enough to
430  actually do this), so to be safe we force null-termination after
431  we've called the function */
432  if( gethostname( fqdn, fqdnMaxLen ) == -1 )
433  return( CRYPT_ERROR_NOTFOUND );
434  fqdn[ fqdnMaxLen - 1 ] = '\0';
435  if( strchr( fqdn, '.' ) != NULL )
436  {
437  /* If the hostname has a dot in it, it's the FQDN */
438  return( CRYPT_OK );
439  }
440 
441  /* Now get the hostent info and walk through it looking for the FQDN */
442  if( ( hostInfo = gethostbyname( fqdn ) ) == NULL )
443  return( CRYPT_ERROR_NOTFOUND );
444  for( addressCount = 0;
445  addressCount < IP_ADDR_COUNT && \
446  hostInfo->h_addr_list[ addressCount ] != NULL; addressCount++ )
447  {
448  char **aliasPtrPtr;
449  int i;
450 
451  /* If the hostname has a dot in it, it's the FQDN. This should be
452  the same as the gethostname() output, but we check again just in
453  case */
454  if( strchr( hostInfo->h_name, '.' ) != NULL )
455  {
456  hostNamePtr = hostInfo->h_name;
457  break;
458  }
459 
460  /* Try for the FQDN in the aliases */
461  if( hostInfo->h_aliases == NULL )
462  continue;
463  for( aliasPtrPtr = hostInfo->h_aliases, i = 0;
464  *aliasPtrPtr != NULL && !strchr( *aliasPtrPtr, '.' ) && \
465  i < IP_ADDR_COUNT;
466  aliasPtrPtr++, i++ );
467  if( *aliasPtrPtr != NULL )
468  {
469  hostNamePtr = *aliasPtrPtr;
470  break;
471  }
472  }
473  if( hostNamePtr == NULL )
474  return( CRYPT_ERROR_NOTFOUND );
475 
476  /* We found the FQDN, return it to the caller */
477  if( strlen( hostNamePtr ) + 1 > fqdnMaxLen )
478  return( CRYPT_ERROR_OVERFLOW );
479  strlcpy_s( fqdn, fqdnMaxLen, hostNamePtr );
480  return( CRYPT_OK );
481  }
482 
483 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4, 5 ) ) \
484 int findHostInfo( INOUT NET_STREAM_INFO *netStream,
485  OUT_BUFFER_FIXED( hostNameMaxLen ) char *hostName,
486  IN_LENGTH_DNS const int hostNameMaxLen,
487  OUT_PORT_Z int *hostPort,
488  IN_BUFFER( nameLen ) const char *name,
489  IN_LENGTH_DNS const int nameLen )
490  {
491  union {
492  HEADER header;
493  BYTE buffer[ NS_PACKETSZ + 8 ];
494  } dnsQueryInfo;
495  BYTE *namePtr, *endPtr;
496  char nameBuffer[ MAX_DNS_SIZE + 8 ];
497  int resultLen, nameSegmentLen, qCount, aCount, minPriority = 32767;
498  int i;
499 
500  assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
501  assert( isWritePtr( hostName, hostNameMaxLen ) );
502  assert( isWritePtr( hostPort, sizeof( int ) ) );
503  assert( isReadPtr( name, MIN_DNS_SIZE ) );
504 
505  REQUIRES( hostNameMaxLen > 0 && hostNameMaxLen <= MAX_DNS_SIZE );
506  REQUIRES( nameLen > 0 && nameLen < MAX_DNS_SIZE );
507  REQUIRES( hostName != name );
508 
509  /* Clear return values */
510  memset( hostName, 0, min( 16, hostNameMaxLen ) );
511  *hostPort = 0;
512 
513  /* Convert the name to a null-terminated string */
514  memcpy( nameBuffer, name, nameLen );
515  nameBuffer[ nameLen ] = '\0';
516  name = nameBuffer;
517 
518  /* If we're doing a full autodetect, we construct the SRV query using
519  the local machine's FQDN. This fails more often than not because of
520  NATing and the use of private networks, but at least we can try */
521  if( !strCompareZ( name, "[Autodetect]" ) )
522  {
523  const int status = getFQDN( netStream, hostName, hostNameMaxLen );
524  if( cryptStatusError( status ) )
525  return( status );
526  name = hostName;
527  }
528 #ifdef EBCDIC_CHARS
529  else
530  {
531  /* We're about to use OS functions, convert the input to EBCDIC. If
532  we've used autodetection the output from getFQDN will already be
533  in EBCDIC form */
534  name = bufferToEbcdic( hostName, name );
535  }
536 #endif /* EBCDIC_CHARS */
537 
538  /* Try and fetch a DNS SRV record (RFC 2782) matching the host info.
539  Unlike Windows' relatively nice DnsQuery() API, Unix has a horribly
540  clunky interface that requires manually grovelling through wire-
541  format data to dig out the bits of interest. OpenBSD provides a
542  function getrrsetbyname()/freerrset() that's equivalent to Windows'
543  DnsQuery()/DnsFree() but it's OpenBSD-only so we can't really rely
544  on it being present */
545  resultLen = res_query( name, C_IN, T_SRV, dnsQueryInfo.buffer,
546  NS_PACKETSZ );
547  if( resultLen < NS_HFIXEDSZ || resultLen > NS_PACKETSZ )
548  {
549  int dummy;
550 
551  return( getSocketError( netStream, CRYPT_ERROR_NOTFOUND, &dummy ) );
552  }
553  if( dnsQueryInfo.header.rcode != 0 || dnsQueryInfo.header.tc != 0 )
554  {
555  /* If we get a non-zero response code (rcode) or the results were
556  truncated (tc), we can't go any further. In theory a truncated
557  response is probably OK since many servers return the address
558  records for the host in the Additional Data section to save the
559  client having to perform a second lookup and we don't need these
560  at this point so we can ignore the fact that they've been
561  truncated, but for now we treat truncation as an error */
562  return( setSocketError( netStream,
563  "RR contains non-zero response code or "
564  "response was truncated", 60,
566  }
567  qCount = ntohs( dnsQueryInfo.header.qdcount );
568  aCount = ntohs( dnsQueryInfo.header.ancount );
569  if( qCount < 0 || qCount > 100 || aCount <= 0 || aCount > 100 )
570  {
571  /* No answer entries (or a suspicious number of entries, which is
572  less likely), we're done */
573  return( setSocketError( netStream, "RR contains no answer entries", 29,
575  }
576 
577  /* Skip the queries */
578  namePtr = dnsQueryInfo.buffer + NS_HFIXEDSZ;
579  endPtr = dnsQueryInfo.buffer + resultLen;
580  for( i = 0; i < qCount && namePtr < endPtr && i < 100; i++ )
581  {
582  nameSegmentLen = dn_skipname( namePtr, endPtr );
583  if( nameSegmentLen <= 0 || nameSegmentLen > MAX_DNS_SIZE )
584  {
585  return( setSocketError( netStream,
586  "RR contains invalid question", 28,
588  }
589  namePtr += nameSegmentLen + NS_QFIXEDSZ;
590  }
591  if( namePtr > endPtr )
592  {
593  return( setSocketError( netStream, "RR contains invalid data", 24,
595  }
596 
597  /* Process the answers. SRV has basic load-balancing facilities, but
598  for now we just use the highest-priority host that we find (it's
599  rarely-enough used that we'll be lucky to find SRV info, let alone
600  any load-balancing setup) */
601  for( i = 0; i < aCount && i < FAILSAFE_ITERATIONS_MED; i++ )
602  {
603  int priority, port;
604 
605  nameSegmentLen = dn_skipname( namePtr, endPtr );
606  if( nameSegmentLen <= 0 || nameSegmentLen > MAX_DNS_SIZE )
607  {
608  return( setSocketError( netStream, "RR contains invalid answer", 26,
610  }
611  namePtr += nameSegmentLen;
612  priority = ntohs( *( ( u_short * ) ( namePtr + SRV_PRIORITY_OFFSET ) ) );
613  port = ntohs( *( ( u_short * ) ( namePtr + SRV_PORT_OFFSET ) ) );
614  namePtr += NS_SRVFIXEDSZ;
615  if( priority < minPriority )
616  {
617  /* We've got a new higher-priority host, use that */
618  nameSegmentLen = dn_expand( dnsQueryInfo.buffer, endPtr,
619  namePtr, hostName, hostNameMaxLen );
620  *hostPort = port;
621  minPriority = priority;
622  }
623  else
624  {
625  /* It's a lower-priority host, skip it */
626  nameSegmentLen = dn_skipname( namePtr, endPtr );
627  }
628  if( nameSegmentLen <= 0 || nameSegmentLen > MAX_DNS_SIZE )
629  {
630  return( setSocketError( netStream, "RR contains invalid answer", 26,
632  }
633  hostName[ nameSegmentLen ] = '\0';
634  namePtr += nameSegmentLen;
635  }
636  if( namePtr > endPtr )
637  {
638  return( setSocketError( netStream, "RR contains invalid data", 24,
640  }
641 #ifdef EBCDIC_CHARS
642  ebcdicToAscii( hostName, strlen( hostName ) );
643 #endif /* EBCDIC_CHARS */
644 
645  return( CRYPT_OK );
646  }
647 #endif /* OS-specific host detection */
648 
649 #endif /* USE_TCP && USE_DNSSRV */