Sweden-Number/dlls/dnsapi/libresolv.c

743 lines
20 KiB
C

/*
* Unix interface for libresolv
*
* Copyright 2021 Hans Leidekker for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#if 0
#pragma makedep unix
#endif
#include "config.h"
#ifdef HAVE_RESOLV
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_NAMESER_H
# include <arpa/nameser.h>
#endif
#ifdef HAVE_RESOLV_H
# include <resolv.h>
#endif
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif
#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "windef.h"
#include "winternl.h"
#include "winbase.h"
#include "winnls.h"
#include "windns.h"
#include "wine/debug.h"
#include "wine/heap.h"
#include "dnsapi.h"
WINE_DEFAULT_DEBUG_CHANNEL(dnsapi);
static const char *debugstr_type( unsigned short type )
{
const char *str;
switch (type)
{
#define X(x) case (x): str = #x; break;
X(DNS_TYPE_ZERO)
X(DNS_TYPE_A)
X(DNS_TYPE_NS)
X(DNS_TYPE_MD)
X(DNS_TYPE_MF)
X(DNS_TYPE_CNAME)
X(DNS_TYPE_SOA)
X(DNS_TYPE_MB)
X(DNS_TYPE_MG)
X(DNS_TYPE_MR)
X(DNS_TYPE_NULL)
X(DNS_TYPE_WKS)
X(DNS_TYPE_PTR)
X(DNS_TYPE_HINFO)
X(DNS_TYPE_MINFO)
X(DNS_TYPE_MX)
X(DNS_TYPE_TEXT)
X(DNS_TYPE_RP)
X(DNS_TYPE_AFSDB)
X(DNS_TYPE_X25)
X(DNS_TYPE_ISDN)
X(DNS_TYPE_RT)
X(DNS_TYPE_NSAP)
X(DNS_TYPE_NSAPPTR)
X(DNS_TYPE_SIG)
X(DNS_TYPE_KEY)
X(DNS_TYPE_PX)
X(DNS_TYPE_GPOS)
X(DNS_TYPE_AAAA)
X(DNS_TYPE_LOC)
X(DNS_TYPE_NXT)
X(DNS_TYPE_EID)
X(DNS_TYPE_NIMLOC)
X(DNS_TYPE_SRV)
X(DNS_TYPE_ATMA)
X(DNS_TYPE_NAPTR)
X(DNS_TYPE_KX)
X(DNS_TYPE_CERT)
X(DNS_TYPE_A6)
X(DNS_TYPE_DNAME)
X(DNS_TYPE_SINK)
X(DNS_TYPE_OPT)
X(DNS_TYPE_UINFO)
X(DNS_TYPE_UID)
X(DNS_TYPE_GID)
X(DNS_TYPE_UNSPEC)
X(DNS_TYPE_ADDRS)
X(DNS_TYPE_TKEY)
X(DNS_TYPE_TSIG)
X(DNS_TYPE_IXFR)
X(DNS_TYPE_AXFR)
X(DNS_TYPE_MAILB)
X(DNS_TYPE_MAILA)
X(DNS_TYPE_ANY)
X(DNS_TYPE_WINS)
X(DNS_TYPE_WINSR)
#undef X
default:
return wine_dbg_sprintf( "0x%04x", type );
}
return wine_dbg_sprintf( "%s", str );
}
static const char *debugstr_section( ns_sect section )
{
switch (section)
{
case ns_s_qd: return "Question";
case ns_s_an: return "Answer";
case ns_s_ns: return "Authority";
case ns_s_ar: return "Additional";
default:
return wine_dbg_sprintf( "0x%02x", section );
}
}
/* call res_init() just once because of a bug in Mac OS X 10.4 */
/* call once per thread on systems that have per-thread _res */
static void init_resolver( void )
{
if (!(_res.options & RES_INIT)) res_init();
}
static unsigned long map_options( DWORD options )
{
unsigned long ret = 0;
if (options == DNS_QUERY_STANDARD)
return RES_DEFAULT;
if (options & DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE)
ret |= RES_IGNTC;
if (options & DNS_QUERY_USE_TCP_ONLY)
ret |= RES_USEVC;
if (options & DNS_QUERY_NO_RECURSION)
ret &= ~RES_RECURSE;
if (options & DNS_QUERY_NO_LOCAL_NAME)
ret &= ~RES_DNSRCH;
if (options & DNS_QUERY_NO_HOSTS_FILE)
ret |= RES_NOALIASES;
if (options & DNS_QUERY_TREAT_AS_FQDN)
ret &= ~RES_DEFNAMES;
if (options & DNS_QUERY_DONT_RESET_TTL_VALUES)
FIXME( "option DNS_QUERY_DONT_RESET_TTL_VALUES not implemented\n" );
if (options & DNS_QUERY_RESERVED)
FIXME( "option DNS_QUERY_RESERVED not implemented\n" );
if (options & DNS_QUERY_WIRE_ONLY)
FIXME( "option DNS_QUERY_WIRE_ONLY not implemented\n" );
if (options & DNS_QUERY_NO_WIRE_QUERY)
FIXME( "option DNS_QUERY_NO_WIRE_QUERY not implemented\n" );
if (options & DNS_QUERY_BYPASS_CACHE)
FIXME( "option DNS_QUERY_BYPASS_CACHE not implemented\n" );
if (options & DNS_QUERY_RETURN_MESSAGE)
FIXME( "option DNS_QUERY_RETURN_MESSAGE not implemented\n" );
if (options & DNS_QUERY_NO_NETBT)
TRACE( "netbios query disabled\n" );
return ret;
}
static DNS_STATUS map_error( int error )
{
switch (error)
{
case ns_r_noerror: return ERROR_SUCCESS;
case ns_r_formerr: return DNS_ERROR_RCODE_FORMAT_ERROR;
case ns_r_servfail: return DNS_ERROR_RCODE_SERVER_FAILURE;
case ns_r_nxdomain: return DNS_ERROR_RCODE_NAME_ERROR;
case ns_r_notimpl: return DNS_ERROR_RCODE_NOT_IMPLEMENTED;
case ns_r_refused: return DNS_ERROR_RCODE_REFUSED;
case ns_r_yxdomain: return DNS_ERROR_RCODE_YXDOMAIN;
case ns_r_yxrrset: return DNS_ERROR_RCODE_YXRRSET;
case ns_r_nxrrset: return DNS_ERROR_RCODE_NXRRSET;
case ns_r_notauth: return DNS_ERROR_RCODE_NOTAUTH;
case ns_r_notzone: return DNS_ERROR_RCODE_NOTZONE;
default:
FIXME( "unmapped error code: %d\n", error );
return DNS_ERROR_RCODE_NOT_IMPLEMENTED;
}
}
static DNS_STATUS map_h_errno( int error )
{
switch (error)
{
case NO_DATA:
case HOST_NOT_FOUND: return DNS_ERROR_RCODE_NAME_ERROR;
case TRY_AGAIN: return DNS_ERROR_RCODE_SERVER_FAILURE;
case NO_RECOVERY: return DNS_ERROR_RCODE_REFUSED;
#ifdef NETDB_INTERNAL
case NETDB_INTERNAL: return DNS_ERROR_RCODE;
#endif
default:
FIXME( "unmapped error code: %d\n", error );
return DNS_ERROR_RCODE_NOT_IMPLEMENTED;
}
}
DNS_STATUS CDECL resolv_get_serverlist( IP4_ARRAY *addrs, DWORD *len )
{
unsigned int size;
int i;
init_resolver();
size = FIELD_OFFSET(IP4_ARRAY, AddrArray[_res.nscount]);
if (!addrs || *len < size)
{
*len = size;
return ERROR_INSUFFICIENT_BUFFER;
}
addrs->AddrCount = _res.nscount;
for (i = 0; i < _res.nscount; i++)
addrs->AddrArray[i] = _res.nsaddr_list[i].sin_addr.s_addr;
return ERROR_SUCCESS;
}
DNS_STATUS CDECL resolv_set_serverlist( const IP4_ARRAY *addrs )
{
int i;
init_resolver();
if (!addrs || !addrs->AddrCount) return ERROR_SUCCESS;
if (addrs->AddrCount > MAXNS)
{
WARN( "too many servers: %d only using the first: %d\n",
addrs->AddrCount, MAXNS );
_res.nscount = MAXNS;
}
else _res.nscount = addrs->AddrCount;
for (i = 0; i < _res.nscount; i++)
_res.nsaddr_list[i].sin_addr.s_addr = addrs->AddrArray[i];
return ERROR_SUCCESS;
}
static char *dname_from_msg( ns_msg msg, const unsigned char *pos )
{
char *str, dname[NS_MAXDNAME] = ".";
/* returns *compressed* length, ignore it */
ns_name_uncompress( ns_msg_base(msg), ns_msg_end(msg), pos, dname, sizeof(dname) );
if ((str = RtlAllocateHeap( GetProcessHeap(), 0, strlen(dname) + 1 ))) strcpy( str, dname );
return str;
}
static char *str_from_rdata( const unsigned char *rdata )
{
char *str;
unsigned int len = rdata[0];
if ((str = RtlAllocateHeap( GetProcessHeap(), 0, len + 1 )))
{
memcpy( str, ++rdata, len );
str[len] = 0;
}
return str;
}
static unsigned int get_record_size( const ns_rr *rr )
{
const unsigned char *pos = rr->rdata;
unsigned int num = 0, size = sizeof(DNS_RECORDA);
switch (rr->type)
{
case ns_t_key:
{
pos += sizeof(WORD) + sizeof(BYTE) + sizeof(BYTE);
size += rr->rdata + rr->rdlength - pos - 1;
break;
}
case ns_t_sig:
{
pos += sizeof(PCHAR) + sizeof(WORD) + 2 * sizeof(BYTE);
pos += 3 * sizeof(DWORD) + 2 * sizeof(WORD);
size += rr->rdata + rr->rdlength - pos - 1;
break;
}
case ns_t_hinfo:
case ns_t_isdn:
case ns_t_txt:
case ns_t_x25:
{
while (pos[0] && pos < rr->rdata + rr->rdlength)
{
num++;
pos += pos[0] + 1;
}
size += (num - 1) * sizeof(PCHAR);
break;
}
case ns_t_null:
case ns_t_opt:
{
size += rr->rdlength - 1;
break;
}
case ns_t_nxt:
case ns_t_wks:
case 0xff01: /* WINS */
{
FIXME( "unhandled type: %s\n", debugstr_type( rr->type ) );
break;
}
default:
break;
}
return size;
}
static DNS_STATUS copy_rdata( ns_msg msg, const ns_rr *rr, DNS_RECORDA *r, WORD *dlen )
{
DNS_STATUS ret = ERROR_SUCCESS;
const unsigned char *pos = rr->rdata;
unsigned int i, size;
switch (rr->type)
{
case ns_t_a:
{
r->Data.A.IpAddress = *(const DWORD *)pos;
*dlen = sizeof(DNS_A_DATA);
break;
}
case ns_t_aaaa:
{
for (i = 0; i < sizeof(IP6_ADDRESS)/sizeof(DWORD); i++)
{
r->Data.AAAA.Ip6Address.IP6Dword[i] = *(const DWORD *)pos;
pos += sizeof(DWORD);
}
*dlen = sizeof(DNS_AAAA_DATA);
break;
}
case ns_t_key:
{
/* FIXME: byte order? */
r->Data.KEY.wFlags = *(const WORD *)pos; pos += sizeof(WORD);
r->Data.KEY.chProtocol = *pos++;
r->Data.KEY.chAlgorithm = *pos++;
size = rr->rdata + rr->rdlength - pos;
for (i = 0; i < size; i++)
r->Data.KEY.Key[i] = *pos++;
*dlen = sizeof(DNS_KEY_DATA) + (size - 1) * sizeof(BYTE);
break;
}
case ns_t_rp:
case ns_t_minfo:
{
r->Data.MINFO.pNameMailbox = dname_from_msg( msg, pos );
if (!r->Data.MINFO.pNameMailbox) return ERROR_NOT_ENOUGH_MEMORY;
if (ns_name_skip( &pos, ns_msg_end( msg ) ) < 0)
return DNS_ERROR_BAD_PACKET;
r->Data.MINFO.pNameErrorsMailbox = dname_from_msg( msg, pos );
if (!r->Data.MINFO.pNameErrorsMailbox)
{
RtlFreeHeap( GetProcessHeap(), 0, r->Data.MINFO.pNameMailbox );
return ERROR_NOT_ENOUGH_MEMORY;
}
*dlen = sizeof(DNS_MINFO_DATAA);
break;
}
case ns_t_afsdb:
case ns_t_rt:
case ns_t_mx:
{
r->Data.MX.wPreference = ntohs( *(const WORD *)pos );
r->Data.MX.pNameExchange = dname_from_msg( msg, pos + sizeof(WORD) );
if (!r->Data.MX.pNameExchange) return ERROR_NOT_ENOUGH_MEMORY;
*dlen = sizeof(DNS_MX_DATAA);
break;
}
case ns_t_null:
{
r->Data.Null.dwByteCount = rr->rdlength;
memcpy( r->Data.Null.Data, rr->rdata, rr->rdlength );
*dlen = sizeof(DNS_NULL_DATA) + rr->rdlength - 1;
break;
}
case ns_t_opt:
{
r->Data.OPT.wDataLength = rr->rdlength;
r->Data.OPT.wPad = 0;
memcpy( r->Data.OPT.Data, rr->rdata, rr->rdlength );
*dlen = sizeof(DNS_OPT_DATA) + rr->rdlength - 1;
break;
}
case ns_t_cname:
case ns_t_ns:
case ns_t_mb:
case ns_t_md:
case ns_t_mf:
case ns_t_mg:
case ns_t_mr:
case ns_t_ptr:
{
r->Data.PTR.pNameHost = dname_from_msg( msg, pos );
if (!r->Data.PTR.pNameHost) return ERROR_NOT_ENOUGH_MEMORY;
*dlen = sizeof(DNS_PTR_DATAA);
break;
}
case ns_t_sig:
{
r->Data.SIG.pNameSigner = dname_from_msg( msg, pos );
if (!r->Data.SIG.pNameSigner) return ERROR_NOT_ENOUGH_MEMORY;
if (ns_name_skip( &pos, ns_msg_end( msg ) ) < 0)
return DNS_ERROR_BAD_PACKET;
/* FIXME: byte order? */
r->Data.SIG.wTypeCovered = *(const WORD *)pos; pos += sizeof(WORD);
r->Data.SIG.chAlgorithm = *pos++;
r->Data.SIG.chLabelCount = *pos++;
r->Data.SIG.dwOriginalTtl = *(const DWORD *)pos; pos += sizeof(DWORD);
r->Data.SIG.dwExpiration = *(const DWORD *)pos; pos += sizeof(DWORD);
r->Data.SIG.dwTimeSigned = *(const DWORD *)pos; pos += sizeof(DWORD);
r->Data.SIG.wKeyTag = *(const WORD *)pos;
size = rr->rdata + rr->rdlength - pos;
for (i = 0; i < size; i++)
r->Data.SIG.Signature[i] = *pos++;
*dlen = sizeof(DNS_SIG_DATAA) + (size - 1) * sizeof(BYTE);
break;
}
case ns_t_soa:
{
r->Data.SOA.pNamePrimaryServer = dname_from_msg( msg, pos );
if (!r->Data.SOA.pNamePrimaryServer) return ERROR_NOT_ENOUGH_MEMORY;
if (ns_name_skip( &pos, ns_msg_end( msg ) ) < 0)
return DNS_ERROR_BAD_PACKET;
r->Data.SOA.pNameAdministrator = dname_from_msg( msg, pos );
if (!r->Data.SOA.pNameAdministrator)
{
RtlFreeHeap( GetProcessHeap(), 0, r->Data.SOA.pNamePrimaryServer );
return ERROR_NOT_ENOUGH_MEMORY;
}
if (ns_name_skip( &pos, ns_msg_end( msg ) ) < 0)
return DNS_ERROR_BAD_PACKET;
r->Data.SOA.dwSerialNo = ntohl( *(const DWORD *)pos ); pos += sizeof(DWORD);
r->Data.SOA.dwRefresh = ntohl( *(const DWORD *)pos ); pos += sizeof(DWORD);
r->Data.SOA.dwRetry = ntohl( *(const DWORD *)pos ); pos += sizeof(DWORD);
r->Data.SOA.dwExpire = ntohl( *(const DWORD *)pos ); pos += sizeof(DWORD);
r->Data.SOA.dwDefaultTtl = ntohl( *(const DWORD *)pos ); pos += sizeof(DWORD);
*dlen = sizeof(DNS_SOA_DATAA);
break;
}
case ns_t_srv:
{
r->Data.SRV.wPriority = ntohs( *(const WORD *)pos ); pos += sizeof(WORD);
r->Data.SRV.wWeight = ntohs( *(const WORD *)pos ); pos += sizeof(WORD);
r->Data.SRV.wPort = ntohs( *(const WORD *)pos ); pos += sizeof(WORD);
r->Data.SRV.pNameTarget = dname_from_msg( msg, pos );
if (!r->Data.SRV.pNameTarget) return ERROR_NOT_ENOUGH_MEMORY;
*dlen = sizeof(DNS_SRV_DATAA);
break;
}
case ns_t_hinfo:
case ns_t_isdn:
case ns_t_x25:
case ns_t_txt:
{
i = 0;
while (pos[0] && pos < rr->rdata + rr->rdlength)
{
r->Data.TXT.pStringArray[i] = str_from_rdata( pos );
if (!r->Data.TXT.pStringArray[i])
{
while (i > 0) RtlFreeHeap( GetProcessHeap(), 0, r->Data.TXT.pStringArray[--i] );
return ERROR_NOT_ENOUGH_MEMORY;
}
i++;
pos += pos[0] + 1;
}
r->Data.TXT.dwStringCount = i;
*dlen = sizeof(DNS_TXT_DATAA) + (i - 1) * sizeof(PCHAR);
break;
}
case ns_t_atma:
case ns_t_loc:
case ns_t_nxt:
case ns_t_tsig:
case ns_t_wks:
case 0x00f9: /* TKEY */
case 0xff01: /* WINS */
case 0xff02: /* WINSR */
default:
FIXME( "unhandled type: %s\n", debugstr_type( rr->type ) );
return DNS_ERROR_RCODE_NOT_IMPLEMENTED;
}
return ret;
}
static inline char *heap_strdup( const char *src )
{
char *dst;
if (!src) return NULL;
if ((dst = RtlAllocateHeap( GetProcessHeap(), 0, (strlen( src ) + 1) * sizeof(char) ))) strcpy( dst, src );
return dst;
}
static DNS_STATUS copy_record( ns_msg msg, ns_sect section, unsigned short num, DNS_RECORDA **recp )
{
DNS_STATUS ret;
DNS_RECORDA *record;
WORD dlen;
ns_rr rr;
if (ns_parserr( &msg, section, num, &rr ) < 0)
return DNS_ERROR_BAD_PACKET;
if (!(record = RtlAllocateHeap( GetProcessHeap(), HEAP_ZERO_MEMORY, get_record_size( &rr ) )))
return ERROR_NOT_ENOUGH_MEMORY;
if (!(record->pName = heap_strdup( rr.name )))
{
RtlFreeHeap( GetProcessHeap(), 0, record );
return ERROR_NOT_ENOUGH_MEMORY;
}
record->wType = rr.type;
record->Flags.S.Section = section;
record->Flags.S.CharSet = DnsCharSetUtf8;
record->dwTtl = rr.ttl;
if ((ret = copy_rdata( msg, &rr, record, &dlen )))
{
RtlFreeHeap( GetProcessHeap(), 0, record->pName );
RtlFreeHeap( GetProcessHeap(), 0, record );
return ret;
}
record->wDataLength = dlen;
*recp = record;
TRACE( "found %s record in %s section\n", debugstr_type( rr.type ), debugstr_section( section ) );
return ERROR_SUCCESS;
}
static void free_record_list( DNS_RECORD *list )
{
DNS_RECORD *r, *next;
unsigned int i;
for (r = list; (list = r); r = next)
{
RtlFreeHeap( GetProcessHeap(), 0, r->pName );
switch (r->wType)
{
case DNS_TYPE_HINFO:
case DNS_TYPE_ISDN:
case DNS_TYPE_TEXT:
case DNS_TYPE_X25:
{
for (i = 0; i < r->Data.TXT.dwStringCount; i++)
RtlFreeHeap( GetProcessHeap(), 0, r->Data.TXT.pStringArray[i] );
break;
}
case DNS_TYPE_MINFO:
case DNS_TYPE_RP:
{
RtlFreeHeap( GetProcessHeap(), 0, r->Data.MINFO.pNameMailbox );
RtlFreeHeap( GetProcessHeap(), 0, r->Data.MINFO.pNameErrorsMailbox );
break;
}
case DNS_TYPE_AFSDB:
case DNS_TYPE_RT:
case DNS_TYPE_MX:
{
RtlFreeHeap( GetProcessHeap(), 0, r->Data.MX.pNameExchange );
break;
}
case DNS_TYPE_NXT:
{
RtlFreeHeap( GetProcessHeap(), 0, r->Data.NXT.pNameNext );
break;
}
case DNS_TYPE_CNAME:
case DNS_TYPE_MB:
case DNS_TYPE_MD:
case DNS_TYPE_MF:
case DNS_TYPE_MG:
case DNS_TYPE_MR:
case DNS_TYPE_NS:
case DNS_TYPE_PTR:
{
RtlFreeHeap( GetProcessHeap(), 0, r->Data.PTR.pNameHost );
break;
}
case DNS_TYPE_SIG:
{
RtlFreeHeap( GetProcessHeap(), 0, r->Data.SIG.pNameSigner );
break;
}
case DNS_TYPE_SOA:
{
RtlFreeHeap( GetProcessHeap(), 0, r->Data.SOA.pNamePrimaryServer );
RtlFreeHeap( GetProcessHeap(), 0, r->Data.SOA.pNameAdministrator );
break;
}
case DNS_TYPE_SRV:
{
RtlFreeHeap( GetProcessHeap(), 0, r->Data.SRV.pNameTarget );
break;
}
default: break;
}
next = r->pNext;
RtlFreeHeap( GetProcessHeap(), 0, r );
}
}
#define DNS_MAX_PACKET_SIZE 4096
DNS_STATUS CDECL resolv_query( const char *name, WORD type, DWORD options, DNS_RECORDA **result )
{
DNS_STATUS ret = DNS_ERROR_RCODE_NOT_IMPLEMENTED;
unsigned int i, num;
unsigned char answer[DNS_MAX_PACKET_SIZE];
ns_sect sections[] = { ns_s_an, ns_s_ar };
ns_msg msg;
DNS_RECORDA *record = NULL;
DNS_RRSET rrset;
int len;
DNS_RRSET_INIT( rrset );
init_resolver();
_res.options |= map_options( options );
if ((len = res_query( name, ns_c_in, type, answer, sizeof(answer) )) < 0)
{
ret = map_h_errno( h_errno );
goto exit;
}
if (ns_initparse( answer, len, &msg ) < 0)
{
ret = DNS_ERROR_BAD_PACKET;
goto exit;
}
#define RCODE_MASK 0x0f
if ((msg._flags & RCODE_MASK) != ns_r_noerror)
{
ret = map_error( msg._flags & RCODE_MASK );
goto exit;
}
for (i = 0; i < ARRAY_SIZE(sections); i++)
{
for (num = 0; num < ns_msg_count( msg, sections[i] ); num++)
{
ret = copy_record( msg, sections[i], num, &record );
if (ret != ERROR_SUCCESS) goto exit;
DNS_RRSET_ADD( rrset, (DNS_RECORD *)record );
}
}
exit:
DNS_RRSET_TERMINATE( rrset );
if (ret != ERROR_SUCCESS)
free_record_list( rrset.pFirstRR );
else
*result = (DNS_RECORDA *)rrset.pFirstRR;
return ret;
}
static const struct resolv_funcs funcs =
{
resolv_get_serverlist,
resolv_query,
resolv_set_serverlist
};
NTSTATUS CDECL __wine_init_unix_lib( HMODULE module, DWORD reason, const void *ptr_in, void *ptr_out )
{
if (reason != DLL_PROCESS_ATTACH) return STATUS_SUCCESS;
*(const struct resolv_funcs **)ptr_out = &funcs;
return STATUS_SUCCESS;
}
#endif /* HAVE_RESOLV */