4 * Portable Windows Library
6 * Copyright (c) 2003 Equivalence Pty. Ltd.
8 * The contents of this file are subject to the Mozilla Public License
9 * Version 1.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at
11 * http://www.mozilla.org/MPL/
13 * Software distributed under the License is distributed on an "AS IS"
14 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
15 * the License for the specific language governing rights and limitations
18 * The Original Code is Portable Windows Library.
20 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
22 * Contributor(s): ______________________________________.
24 * Copyright 2003 Equivalence Pty. Ltd.
27 * Revision 1.11 2004/01/03 03:37:53 csoutheren
28 * Fixed compile problem on Linux
30 * Revision 1.10 2004/01/03 03:10:42 csoutheren
31 * Fixed more problems with looking up SRV records, especially on Windows
33 * Revision 1.9 2004/01/02 13:22:04 csoutheren
34 * Fixed problem with extracting SRV records from DNS
36 * Revision 1.8 2003/11/02 15:52:58 shawn
37 * added arpa/nameser_compat.h for Mac OS X 10.3 (Panther)
39 * Revision 1.7 2003/04/28 23:57:40 robertj
40 * Fixed Solaris compatibility
42 * Revision 1.6 2003/04/22 23:21:37 craigs
43 * Swapped includes at request of Shawn Hsiao for compatiobility with MacOSX
45 * Revision 1.5 2003/04/16 14:21:12 craigs
46 * Added set of T_SRV for MacOS
48 * Revision 1.4 2003/04/16 08:00:19 robertj
49 * Windoes psuedo autoconf support
51 * Revision 1.3 2003/04/15 08:14:32 craigs
52 * Added single string form of GetSRVRecords
54 * Revision 1.2 2003/04/15 08:05:19 craigs
55 * Added Unix implementation
57 * Revision 1.1 2003/04/15 04:06:35 craigs
63 #pragma implementation "pdns.h"
68 #include <ptclib/pdns.h>
72 #include <ptclib/random.h>
78 #pragma comment(lib, P_DNS_LIBRARY)
82 #define P_HAS_RESOLVER 1
85 #include <arpa/nameser.h>
87 #if defined(P_MACOSX) && (P_MACOSX >= 700)
88 #include <arpa/nameser_compat.h>
99 /////////////////////////////////////////////////
101 PDNS::Record::Record()
107 /////////////////////////////////////////////////
110 #ifdef P_HAS_RESOLVER
112 #define DNS_STATUS int
113 #define DNS_TYPE_SRV T_SRV
114 #define DNS_TYPE_MX T_MX
115 #define DNS_TYPE_A T_A
116 #define DnsFreeRecordList 0
117 #define DNS_QUERY_STANDARD 0
118 #define DNS_QUERY_BYPASS_CACHE 0
120 typedef struct _DnsAData
{
125 char pNameExchange
[MAXDNAME
];
131 char pNameHost
[MAXDNAME
];
134 typedef struct _DnsSRVData
{
136 char pNameTarget
[MAXDNAME
];
143 typedef struct _DnsRecordFlags
145 unsigned Section
: 2;
147 unsigned CharSet
: 2;
149 unsigned Reserved
: 24;
152 typedef enum _DnsSection
164 char pName
[MAXDNAME
];
169 DWORD DW
; // flags as DWORD
170 DNS_RECORD_FLAGS S
; // flags as structure
181 typedef DnsRecord
* PDNS_RECORD
;
183 static BOOL
GetDN(const BYTE
* reply
, const BYTE
* replyEnd
, BYTE
* & cp
, char * buff
)
185 int len
= dn_expand(reply
, replyEnd
, cp
, buff
, MAXDNAME
);
192 static BOOL
ProcessDNSRecords(
194 const BYTE
* replyEnd
,
199 PDNS_RECORD
* results
)
201 PDNS_RECORD lastRecord
= NULL
;
202 PDNS_RECORD newRecord
= NULL
;
204 PINDEX rrCount
= anCount
+ nsCount
+ arCount
;
209 for (i
= 0; i
< rrCount
; i
++) {
211 if (newRecord
== NULL
)
212 newRecord
= new DnsRecord
;
214 memset(newRecord
, 0, sizeof(DnsRecord
));
217 newRecord
->Flags
.S
.Section
= DnsSectionAnswer
;
218 else if (i
< nsCount
)
219 newRecord
->Flags
.S
.Section
= DnsSectionAuthority
;
220 else if (i
< arCount
)
221 newRecord
->Flags
.S
.Section
= DnsSectionAddtional
;
224 if (!GetDN(reply
, replyEnd
, cp
, newRecord
->pName
)) {
229 // get other common parts of the record
236 GETSHORT(dnsClass
, cp
);
240 newRecord
->wType
= type
;
248 GETSHORT(newRecord
->Data
.SRV
.wPriority
, data
);
249 GETSHORT(newRecord
->Data
.SRV
.wWeight
, data
);
250 GETSHORT(newRecord
->Data
.SRV
.wPort
, data
);
251 if (!GetDN(reply
, replyEnd
, data
, newRecord
->Data
.SRV
.pNameTarget
)) {
258 GETSHORT(newRecord
->Data
.MX
.wPreference
, data
);
259 if (!GetDN(reply
, replyEnd
, data
, newRecord
->Data
.MX
.pNameExchange
)) {
266 GETLONG(newRecord
->Data
.A
.IpAddress
, data
);
270 if (!GetDN(reply
, replyEnd
, data
, newRecord
->Data
.NS
.pNameHost
)) {
282 if (*results
== NULL
)
283 *results
= newRecord
;
285 newRecord
->pNext
= NULL
;
287 if (lastRecord
!= NULL
)
288 lastRecord
->pNext
= newRecord
;
290 lastRecord
= newRecord
;
300 void DnsRecordListFree(PDNS_RECORD rec
, int /* FreeType */)
302 while (rec
!= NULL
) {
303 PDNS_RECORD next
= rec
->pNext
;
309 DNS_STATUS
DnsQuery_A(const char * service
,
313 PDNS_RECORD
* results
,
328 int replyLen
= res_search(service
, C_IN
, requestType
, (BYTE
*)&reply
, sizeof(reply
));
333 BYTE
* replyStart
= reply
.buf
;
334 BYTE
* replyEnd
= reply
.buf
+ replyLen
;
335 BYTE
* cp
= reply
.buf
+ sizeof(HEADER
);
337 // ignore questions in response
339 for (i
= 0; i
< ntohs(reply
.hdr
.qdcount
); i
++) {
340 char qName
[MAXDNAME
];
341 if (!GetDN(replyStart
, replyEnd
, cp
, qName
))
346 if (!ProcessDNSRecords(replyStart
,
349 ntohs(reply
.hdr
.ancount
),
350 ntohs(reply
.hdr
.nscount
),
351 ntohs(reply
.hdr
.arcount
),
353 DnsRecordListFree(*results
, 0);
361 #endif // P_HAS_RESOLVER
364 /////////////////////////////////////////////////
366 PObject::Comparison
PDNS::SRVRecord::Compare(const PObject
& obj
) const
368 PDNS::SRVRecord
& other
= (PDNS::SRVRecord
&)obj
;
369 if (priority
< other
.priority
)
371 else if (priority
> other
.priority
)
373 if (weight
< other
.weight
)
375 else if (weight
> other
.weight
)
380 void PDNS::SRVRecord::PrintOn(ostream
& strm
) const
382 strm
<< "host=" << hostName
<< ":" << port
<< "(" << hostAddress
<< "), "
383 << "priority=" << priority
<< ", "
384 << "weight=" << weight
;
387 /////////////////////////////////////////////////
389 void PDNS::SRVRecordList::PrintOn(ostream
& strm
) const
392 for (i
= 0; i
< GetSize(); i
++)
393 strm
<< (*this)[i
] << endl
;
396 PDNS::SRVRecord
* PDNS::SRVRecordList::GetFirst()
401 // create a list of all prioities, to save time
408 WORD lastPri
= (*this)[0].priority
;
409 priList
[0] = lastPri
;
410 (*this)[0].used
= FALSE
;
411 for (i
= 1; i
< GetSize(); i
++) {
412 (*this)[i
].used
= FALSE
;
413 if ((*this)[i
].priority
!= lastPri
) {
414 priList
.SetSize(priPos
+1);
415 lastPri
= (*this)[i
].priority
;
416 priList
[priPos
] = lastPri
;
425 PDNS::SRVRecord
* PDNS::SRVRecordList::GetNext()
427 if (priList
.GetSize() == 0)
430 while (priPos
< priList
.GetSize()) {
432 WORD currentPri
= priList
[priPos
];
434 // find first record at current priority
436 for (firstPos
= 0; (firstPos
< GetSize()) && ((*this)[firstPos
].priority
!= currentPri
); firstPos
++)
438 if (firstPos
== GetSize())
441 // calculate total of all unused weights at this priority
442 unsigned totalWeight
= (*this)[firstPos
].weight
;
443 PINDEX i
= firstPos
+ 1;
445 while (i
< GetSize() && ((*this)[i
].priority
== currentPri
)) {
446 if (!(*this)[i
].used
) {
447 totalWeight
+= (*this)[i
].weight
;
452 // if no matches found, go to the next priority level
458 // selected the correct item
459 if (totalWeight
> 0) {
460 unsigned targetWeight
= PRandom::Number() % (totalWeight
+1);
462 for (i
= 0; i
< GetSize() && ((*this)[i
].priority
== currentPri
); i
++) {
463 if (!(*this)[i
].used
) {
464 totalWeight
+= (*this)[i
].weight
;
465 if (totalWeight
>= targetWeight
) {
466 (*this)[i
].used
= TRUE
;
473 // pick a random item at this priority
474 PINDEX j
= firstPos
+ ((count
== 0) ? 0 : (PRandom::Number() % count
) );
476 for (i
= 0; i
< GetSize() && ((*this)[i
].priority
== currentPri
); i
++) {
477 if (!(*this)[i
].used
) {
479 (*this)[i
].used
= TRUE
;
486 // go to the next priority level
493 ///////////////////////////////////////////////////////
495 BOOL
PDNS::GetSRVRecords(const PString
& _service
,
496 const PString
& type
,
497 const PString
& domain
,
498 SRVRecordList
& recordList
)
501 if (_service
.IsEmpty())
505 if (_service
[0] != '_')
506 service
= PString("_") + _service
;
510 service
+= PString("._") + type
+ "." + domain
;
512 return GetSRVRecords(service
, recordList
);
515 BOOL
PDNS::GetSRVRecords(const PString
& service
, PDNS::SRVRecordList
& recordList
)
517 recordList
.RemoveAll();
519 PDNS_RECORD results
= NULL
;
520 DNS_STATUS status
= DnsQuery_A((const char *)service
,
522 DNS_QUERY_STANDARD
| DNS_QUERY_BYPASS_CACHE
,
530 PDNS_RECORD dnsRecord
= results
;
531 while (dnsRecord
!= NULL
) {
533 (dnsRecord
->Flags
.S
.Section
== DnsSectionAnswer
) &&
534 (dnsRecord
->wType
== DNS_TYPE_SRV
) &&
535 (strcmp(dnsRecord
->Data
.SRV
.pNameTarget
, ".") != 0)
537 SRVRecord
* record
= new SRVRecord();
538 record
->hostName
= PString(dnsRecord
->Data
.SRV
.pNameTarget
);
539 record
->port
= results
->Data
.SRV
.wPort
;
540 record
->priority
= results
->Data
.SRV
.wPriority
;
541 record
->weight
= results
->Data
.SRV
.wWeight
;
543 // see if any A records match this hostname
544 PDNS_RECORD aRecord
= results
;
545 while (aRecord
!= NULL
) {
546 if ((dnsRecord
->Flags
.S
.Section
== DnsSectionAddtional
) && (dnsRecord
->wType
== DNS_TYPE_A
)) {
547 record
->hostAddress
= PIPSocket::Address(dnsRecord
->Data
.A
.IpAddress
);
550 aRecord
= aRecord
->pNext
;
553 // if no A record found, then get address the hard way
555 PIPSocket::GetHostAddress(record
->hostName
, record
->hostAddress
);
557 // add record to the list
558 recordList
.Append(record
);
560 dnsRecord
= dnsRecord
->pNext
;
564 DnsRecordListFree(results
, DnsFreeRecordList
);
566 return recordList
.GetSize() != 0;
569 ///////////////////////////////////////////////////////
571 PObject::Comparison
PDNS::MXRecord::Compare(const PObject
& obj
) const
573 PDNS::MXRecord
& other
= (PDNS::MXRecord
&)obj
;
574 if (preference
< other
.preference
)
576 else if (preference
> other
.preference
)
581 void PDNS::MXRecord::PrintOn(ostream
& strm
) const
583 strm
<< "host=" << hostName
<< "(" << hostAddress
<< "), "
584 << "preference=" << preference
;
587 ///////////////////////////////////////////////////////
589 BOOL
PDNS::GetMXRecords(const PString
& domain
, MXRecordList
& recordList
)
591 if (domain
.IsEmpty())
594 recordList
.RemoveAll();
596 PDNS_RECORD results
= NULL
;
597 DNS_STATUS status
= DnsQuery_A((const char *)domain
,
607 PDNS_RECORD dnsRecord
= results
;
608 while (dnsRecord
!= NULL
) {
609 if ((dnsRecord
->Flags
.S
.Section
== DnsSectionAnswer
) && (dnsRecord
->wType
== DNS_TYPE_MX
)) {
610 MXRecord
* record
= new MXRecord();
611 record
->hostName
= PString(dnsRecord
->Data
.MX
.pNameExchange
);
612 record
->preference
= dnsRecord
->Data
.MX
.wPreference
;
614 // see if any A records match this hostname
615 PDNS_RECORD aRecord
= results
;
616 while (aRecord
!= NULL
) {
617 if ((dnsRecord
->Flags
.S
.Section
== DnsSectionAddtional
) && (dnsRecord
->wType
== DNS_TYPE_A
)) {
618 record
->hostAddress
= PIPSocket::Address(dnsRecord
->Data
.A
.IpAddress
);
621 aRecord
= aRecord
->pNext
;
624 // if no A record found, then get address the hard way
626 PIPSocket::GetHostAddress(record
->hostName
, record
->hostAddress
);
628 // add record to the list
629 recordList
.Append(record
);
631 dnsRecord
= dnsRecord
->pNext
;
635 DnsRecordListFree(results
, DnsFreeRecordList
);
637 return recordList
.GetSize() != 0;
640 ///////////////////////////////////////////////////////
642 void PDNS::MXRecordList::PrintOn(ostream
& strm
) const
645 for (i
= 0; i
< GetSize(); i
++)
646 strm
<< (*this)[i
] << endl
;
649 PDNS::MXRecord
* PDNS::MXRecordList::GetFirst()
652 for (i
= 0; i
< GetSize(); i
++)
653 (*this)[i
].used
= FALSE
;
660 PDNS::MXRecord
* PDNS::MXRecordList::GetNext()
665 if (lastIndex
>= GetSize())
668 return (PDNS::MXRecord
*)GetAt(lastIndex
++);
675 // End Of File ///////////////////////////////////////////////////////////////