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.12 2004/02/23 23:52:19 csoutheren
28 * Added pragmas to avoid every Windows application needing to include libs explicitly
30 * Revision 1.11 2004/01/03 03:37:53 csoutheren
31 * Fixed compile problem on Linux
33 * Revision 1.10 2004/01/03 03:10:42 csoutheren
34 * Fixed more problems with looking up SRV records, especially on Windows
36 * Revision 1.9 2004/01/02 13:22:04 csoutheren
37 * Fixed problem with extracting SRV records from DNS
39 * Revision 1.8 2003/11/02 15:52:58 shawn
40 * added arpa/nameser_compat.h for Mac OS X 10.3 (Panther)
42 * Revision 1.7 2003/04/28 23:57:40 robertj
43 * Fixed Solaris compatibility
45 * Revision 1.6 2003/04/22 23:21:37 craigs
46 * Swapped includes at request of Shawn Hsiao for compatiobility with MacOSX
48 * Revision 1.5 2003/04/16 14:21:12 craigs
49 * Added set of T_SRV for MacOS
51 * Revision 1.4 2003/04/16 08:00:19 robertj
52 * Windoes psuedo autoconf support
54 * Revision 1.3 2003/04/15 08:14:32 craigs
55 * Added single string form of GetSRVRecords
57 * Revision 1.2 2003/04/15 08:05:19 craigs
58 * Added Unix implementation
60 * Revision 1.1 2003/04/15 04:06:35 craigs
66 #pragma implementation "pdns.h"
71 #include <ptclib/pdns.h>
75 #include <ptclib/random.h>
81 #pragma comment(lib, P_DNS_LIBRARY)
82 #pragma comment(linker, "/delayload:dnsapi.dll")
83 #pragma comment(lib, "Delayimp.lib")
87 #define P_HAS_RESOLVER 1
90 #include <arpa/nameser.h>
92 #if defined(P_MACOSX) && (P_MACOSX >= 700)
93 #include <arpa/nameser_compat.h>
104 /////////////////////////////////////////////////
106 PDNS::Record::Record()
112 /////////////////////////////////////////////////
115 #ifdef P_HAS_RESOLVER
117 #define DNS_STATUS int
118 #define DNS_TYPE_SRV T_SRV
119 #define DNS_TYPE_MX T_MX
120 #define DNS_TYPE_A T_A
121 #define DnsFreeRecordList 0
122 #define DNS_QUERY_STANDARD 0
123 #define DNS_QUERY_BYPASS_CACHE 0
125 typedef struct _DnsAData
{
130 char pNameExchange
[MAXDNAME
];
136 char pNameHost
[MAXDNAME
];
139 typedef struct _DnsSRVData
{
141 char pNameTarget
[MAXDNAME
];
148 typedef struct _DnsRecordFlags
150 unsigned Section
: 2;
152 unsigned CharSet
: 2;
154 unsigned Reserved
: 24;
157 typedef enum _DnsSection
169 char pName
[MAXDNAME
];
174 DWORD DW
; // flags as DWORD
175 DNS_RECORD_FLAGS S
; // flags as structure
186 typedef DnsRecord
* PDNS_RECORD
;
188 static BOOL
GetDN(const BYTE
* reply
, const BYTE
* replyEnd
, BYTE
* & cp
, char * buff
)
190 int len
= dn_expand(reply
, replyEnd
, cp
, buff
, MAXDNAME
);
197 static BOOL
ProcessDNSRecords(
199 const BYTE
* replyEnd
,
204 PDNS_RECORD
* results
)
206 PDNS_RECORD lastRecord
= NULL
;
207 PDNS_RECORD newRecord
= NULL
;
209 PINDEX rrCount
= anCount
+ nsCount
+ arCount
;
214 for (i
= 0; i
< rrCount
; i
++) {
216 if (newRecord
== NULL
)
217 newRecord
= new DnsRecord
;
219 memset(newRecord
, 0, sizeof(DnsRecord
));
222 newRecord
->Flags
.S
.Section
= DnsSectionAnswer
;
223 else if (i
< nsCount
)
224 newRecord
->Flags
.S
.Section
= DnsSectionAuthority
;
225 else if (i
< arCount
)
226 newRecord
->Flags
.S
.Section
= DnsSectionAddtional
;
229 if (!GetDN(reply
, replyEnd
, cp
, newRecord
->pName
)) {
234 // get other common parts of the record
241 GETSHORT(dnsClass
, cp
);
245 newRecord
->wType
= type
;
253 GETSHORT(newRecord
->Data
.SRV
.wPriority
, data
);
254 GETSHORT(newRecord
->Data
.SRV
.wWeight
, data
);
255 GETSHORT(newRecord
->Data
.SRV
.wPort
, data
);
256 if (!GetDN(reply
, replyEnd
, data
, newRecord
->Data
.SRV
.pNameTarget
)) {
263 GETSHORT(newRecord
->Data
.MX
.wPreference
, data
);
264 if (!GetDN(reply
, replyEnd
, data
, newRecord
->Data
.MX
.pNameExchange
)) {
271 GETLONG(newRecord
->Data
.A
.IpAddress
, data
);
275 if (!GetDN(reply
, replyEnd
, data
, newRecord
->Data
.NS
.pNameHost
)) {
287 if (*results
== NULL
)
288 *results
= newRecord
;
290 newRecord
->pNext
= NULL
;
292 if (lastRecord
!= NULL
)
293 lastRecord
->pNext
= newRecord
;
295 lastRecord
= newRecord
;
305 void DnsRecordListFree(PDNS_RECORD rec
, int /* FreeType */)
307 while (rec
!= NULL
) {
308 PDNS_RECORD next
= rec
->pNext
;
314 DNS_STATUS
DnsQuery_A(const char * service
,
318 PDNS_RECORD
* results
,
333 int replyLen
= res_search(service
, C_IN
, requestType
, (BYTE
*)&reply
, sizeof(reply
));
338 BYTE
* replyStart
= reply
.buf
;
339 BYTE
* replyEnd
= reply
.buf
+ replyLen
;
340 BYTE
* cp
= reply
.buf
+ sizeof(HEADER
);
342 // ignore questions in response
344 for (i
= 0; i
< ntohs(reply
.hdr
.qdcount
); i
++) {
345 char qName
[MAXDNAME
];
346 if (!GetDN(replyStart
, replyEnd
, cp
, qName
))
351 if (!ProcessDNSRecords(replyStart
,
354 ntohs(reply
.hdr
.ancount
),
355 ntohs(reply
.hdr
.nscount
),
356 ntohs(reply
.hdr
.arcount
),
358 DnsRecordListFree(*results
, 0);
366 #endif // P_HAS_RESOLVER
369 /////////////////////////////////////////////////
371 PObject::Comparison
PDNS::SRVRecord::Compare(const PObject
& obj
) const
373 PDNS::SRVRecord
& other
= (PDNS::SRVRecord
&)obj
;
374 if (priority
< other
.priority
)
376 else if (priority
> other
.priority
)
378 if (weight
< other
.weight
)
380 else if (weight
> other
.weight
)
385 void PDNS::SRVRecord::PrintOn(ostream
& strm
) const
387 strm
<< "host=" << hostName
<< ":" << port
<< "(" << hostAddress
<< "), "
388 << "priority=" << priority
<< ", "
389 << "weight=" << weight
;
392 /////////////////////////////////////////////////
394 void PDNS::SRVRecordList::PrintOn(ostream
& strm
) const
397 for (i
= 0; i
< GetSize(); i
++)
398 strm
<< (*this)[i
] << endl
;
401 PDNS::SRVRecord
* PDNS::SRVRecordList::GetFirst()
406 // create a list of all prioities, to save time
413 WORD lastPri
= (*this)[0].priority
;
414 priList
[0] = lastPri
;
415 (*this)[0].used
= FALSE
;
416 for (i
= 1; i
< GetSize(); i
++) {
417 (*this)[i
].used
= FALSE
;
418 if ((*this)[i
].priority
!= lastPri
) {
419 priList
.SetSize(priPos
+1);
420 lastPri
= (*this)[i
].priority
;
421 priList
[priPos
] = lastPri
;
430 PDNS::SRVRecord
* PDNS::SRVRecordList::GetNext()
432 if (priList
.GetSize() == 0)
435 while (priPos
< priList
.GetSize()) {
437 WORD currentPri
= priList
[priPos
];
439 // find first record at current priority
441 for (firstPos
= 0; (firstPos
< GetSize()) && ((*this)[firstPos
].priority
!= currentPri
); firstPos
++)
443 if (firstPos
== GetSize())
446 // calculate total of all unused weights at this priority
447 unsigned totalWeight
= (*this)[firstPos
].weight
;
448 PINDEX i
= firstPos
+ 1;
450 while (i
< GetSize() && ((*this)[i
].priority
== currentPri
)) {
451 if (!(*this)[i
].used
) {
452 totalWeight
+= (*this)[i
].weight
;
457 // if no matches found, go to the next priority level
463 // selected the correct item
464 if (totalWeight
> 0) {
465 unsigned targetWeight
= PRandom::Number() % (totalWeight
+1);
467 for (i
= 0; i
< GetSize() && ((*this)[i
].priority
== currentPri
); i
++) {
468 if (!(*this)[i
].used
) {
469 totalWeight
+= (*this)[i
].weight
;
470 if (totalWeight
>= targetWeight
) {
471 (*this)[i
].used
= TRUE
;
478 // pick a random item at this priority
479 PINDEX j
= firstPos
+ ((count
== 0) ? 0 : (PRandom::Number() % count
) );
481 for (i
= 0; i
< GetSize() && ((*this)[i
].priority
== currentPri
); i
++) {
482 if (!(*this)[i
].used
) {
484 (*this)[i
].used
= TRUE
;
491 // go to the next priority level
498 ///////////////////////////////////////////////////////
500 BOOL
PDNS::GetSRVRecords(const PString
& _service
,
501 const PString
& type
,
502 const PString
& domain
,
503 SRVRecordList
& recordList
)
506 if (_service
.IsEmpty())
510 if (_service
[0] != '_')
511 service
= PString("_") + _service
;
515 service
+= PString("._") + type
+ "." + domain
;
517 return GetSRVRecords(service
, recordList
);
520 BOOL
PDNS::GetSRVRecords(const PString
& service
, PDNS::SRVRecordList
& recordList
)
522 recordList
.RemoveAll();
524 PDNS_RECORD results
= NULL
;
525 DNS_STATUS status
= DnsQuery_A((const char *)service
,
527 DNS_QUERY_STANDARD
| DNS_QUERY_BYPASS_CACHE
,
535 PDNS_RECORD dnsRecord
= results
;
536 while (dnsRecord
!= NULL
) {
538 (dnsRecord
->Flags
.S
.Section
== DnsSectionAnswer
) &&
539 (dnsRecord
->wType
== DNS_TYPE_SRV
) &&
540 (strcmp(dnsRecord
->Data
.SRV
.pNameTarget
, ".") != 0)
542 SRVRecord
* record
= new SRVRecord();
543 record
->hostName
= PString(dnsRecord
->Data
.SRV
.pNameTarget
);
544 record
->port
= results
->Data
.SRV
.wPort
;
545 record
->priority
= results
->Data
.SRV
.wPriority
;
546 record
->weight
= results
->Data
.SRV
.wWeight
;
548 // see if any A records match this hostname
549 PDNS_RECORD aRecord
= results
;
550 while (aRecord
!= NULL
) {
551 if ((dnsRecord
->Flags
.S
.Section
== DnsSectionAddtional
) && (dnsRecord
->wType
== DNS_TYPE_A
)) {
552 record
->hostAddress
= PIPSocket::Address(dnsRecord
->Data
.A
.IpAddress
);
555 aRecord
= aRecord
->pNext
;
558 // if no A record found, then get address the hard way
560 PIPSocket::GetHostAddress(record
->hostName
, record
->hostAddress
);
562 // add record to the list
563 recordList
.Append(record
);
565 dnsRecord
= dnsRecord
->pNext
;
569 DnsRecordListFree(results
, DnsFreeRecordList
);
571 return recordList
.GetSize() != 0;
574 ///////////////////////////////////////////////////////
576 PObject::Comparison
PDNS::MXRecord::Compare(const PObject
& obj
) const
578 PDNS::MXRecord
& other
= (PDNS::MXRecord
&)obj
;
579 if (preference
< other
.preference
)
581 else if (preference
> other
.preference
)
586 void PDNS::MXRecord::PrintOn(ostream
& strm
) const
588 strm
<< "host=" << hostName
<< "(" << hostAddress
<< "), "
589 << "preference=" << preference
;
592 ///////////////////////////////////////////////////////
594 BOOL
PDNS::GetMXRecords(const PString
& domain
, MXRecordList
& recordList
)
596 if (domain
.IsEmpty())
599 recordList
.RemoveAll();
601 PDNS_RECORD results
= NULL
;
602 DNS_STATUS status
= DnsQuery_A((const char *)domain
,
612 PDNS_RECORD dnsRecord
= results
;
613 while (dnsRecord
!= NULL
) {
614 if ((dnsRecord
->Flags
.S
.Section
== DnsSectionAnswer
) && (dnsRecord
->wType
== DNS_TYPE_MX
)) {
615 MXRecord
* record
= new MXRecord();
616 record
->hostName
= PString(dnsRecord
->Data
.MX
.pNameExchange
);
617 record
->preference
= dnsRecord
->Data
.MX
.wPreference
;
619 // see if any A records match this hostname
620 PDNS_RECORD aRecord
= results
;
621 while (aRecord
!= NULL
) {
622 if ((dnsRecord
->Flags
.S
.Section
== DnsSectionAddtional
) && (dnsRecord
->wType
== DNS_TYPE_A
)) {
623 record
->hostAddress
= PIPSocket::Address(dnsRecord
->Data
.A
.IpAddress
);
626 aRecord
= aRecord
->pNext
;
629 // if no A record found, then get address the hard way
631 PIPSocket::GetHostAddress(record
->hostName
, record
->hostAddress
);
633 // add record to the list
634 recordList
.Append(record
);
636 dnsRecord
= dnsRecord
->pNext
;
640 DnsRecordListFree(results
, DnsFreeRecordList
);
642 return recordList
.GetSize() != 0;
645 ///////////////////////////////////////////////////////
647 void PDNS::MXRecordList::PrintOn(ostream
& strm
) const
650 for (i
= 0; i
< GetSize(); i
++)
651 strm
<< (*this)[i
] << endl
;
654 PDNS::MXRecord
* PDNS::MXRecordList::GetFirst()
657 for (i
= 0; i
< GetSize(); i
++)
658 (*this)[i
].used
= FALSE
;
665 PDNS::MXRecord
* PDNS::MXRecordList::GetNext()
670 if (lastIndex
>= GetSize())
673 return (PDNS::MXRecord
*)GetAt(lastIndex
++);
680 // End Of File ///////////////////////////////////////////////////////////////