Added a parameter to semaphore constructor to avoid ambiguity
[pwlib.git] / src / ptclib / pdns.cxx
blob156362a4bfbd7d0ad6c400d43a8ca5c1267cf7e7
1 /*
2 * pdns.cxx
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
16 * under the License.
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.
26 * $Log$
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
58 * Initial version
62 #ifdef __GNUC__
63 #pragma implementation "pdns.h"
64 #endif
66 #include <ptlib.h>
68 #include <ptclib/pdns.h>
70 #if P_DNS
72 #include <ptclib/random.h>
74 #if defined(_WIN32)
76 #include <windns.h>
78 #pragma comment(lib, P_DNS_LIBRARY)
80 #else
82 #define P_HAS_RESOLVER 1
84 #if P_HAS_RESOLVER
85 #include <arpa/nameser.h>
86 #include <resolv.h>
87 #if defined(P_MACOSX) && (P_MACOSX >= 700)
88 #include <arpa/nameser_compat.h>
89 #endif
90 #endif
92 #endif
94 #ifndef T_SRV
95 #define T_SRV 33
96 #endif
99 /////////////////////////////////////////////////
101 PDNS::Record::Record()
103 used = FALSE;
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 {
121 DWORD IpAddress;
122 } DNS_A_DATA;
124 typedef struct {
125 char pNameExchange[MAXDNAME];
126 WORD wPreference;
128 } DNS_MX_DATA;
130 typedef struct {
131 char pNameHost[MAXDNAME];
132 } DNS_PTR_DATA;
134 typedef struct _DnsSRVData {
135 public:
136 char pNameTarget[MAXDNAME];
137 WORD wPriority;
138 WORD wWeight;
139 WORD wPort;
141 } DNS_SRV_DATA;
143 typedef struct _DnsRecordFlags
145 unsigned Section : 2;
146 unsigned Delete : 1;
147 unsigned CharSet : 2;
148 unsigned Unused : 3;
149 unsigned Reserved : 24;
150 } DNS_RECORD_FLAGS;
152 typedef enum _DnsSection
154 DnsSectionQuestion,
155 DnsSectionAnswer,
156 DnsSectionAuthority,
157 DnsSectionAddtional,
158 } DNS_SECTION;
161 class DnsRecord {
162 public:
163 DnsRecord * pNext;
164 char pName[MAXDNAME];
165 WORD wType;
166 WORD wDataLength;
168 union {
169 DWORD DW; // flags as DWORD
170 DNS_RECORD_FLAGS S; // flags as structure
171 } Flags;
173 union {
174 DNS_A_DATA A;
175 DNS_MX_DATA MX;
176 DNS_PTR_DATA NS;
177 DNS_SRV_DATA SRV;
178 } Data;
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);
186 if (len < 0)
187 return FALSE;
188 cp += len;
189 return TRUE;
192 static BOOL ProcessDNSRecords(
193 const BYTE * reply,
194 const BYTE * replyEnd,
195 BYTE * cp,
196 PINDEX anCount,
197 PINDEX nsCount,
198 PINDEX arCount,
199 PDNS_RECORD * results)
201 PDNS_RECORD lastRecord = NULL;
202 PDNS_RECORD newRecord = NULL;
204 PINDEX rrCount = anCount + nsCount + arCount;
205 nsCount += anCount;
206 arCount += nsCount;
208 PINDEX i;
209 for (i = 0; i < rrCount; i++) {
211 if (newRecord == NULL)
212 newRecord = new DnsRecord;
214 memset(newRecord, 0, sizeof(DnsRecord));
216 if (i < anCount)
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;
223 // get the name
224 if (!GetDN(reply, replyEnd, cp, newRecord->pName)) {
225 delete newRecord;
226 return FALSE;
229 // get other common parts of the record
230 WORD type;
231 WORD dnsClass;
232 DWORD ttl;
233 WORD dlen;
235 GETSHORT(type, cp);
236 GETSHORT(dnsClass, cp);
237 GETLONG (ttl, cp);
238 GETSHORT(dlen, cp);
240 newRecord->wType = type;
242 BYTE * data = cp;
243 cp += dlen;
244 BOOL ok = TRUE;
246 switch (type) {
247 case T_SRV:
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)) {
252 delete newRecord;
253 return FALSE;
255 break;
257 case T_MX:
258 GETSHORT(newRecord->Data.MX.wPreference, data);
259 if (!GetDN(reply, replyEnd, data, newRecord->Data.MX.pNameExchange)) {
260 delete newRecord;
261 return FALSE;
263 break;
265 case T_A:
266 GETLONG(newRecord->Data.A.IpAddress, data);
267 break;
269 case T_NS:
270 if (!GetDN(reply, replyEnd, data, newRecord->Data.NS.pNameHost)) {
271 delete newRecord;
272 return FALSE;
274 break;
276 default:
277 ok = FALSE;
278 break;
281 if (ok) {
282 if (*results == NULL)
283 *results = newRecord;
285 newRecord->pNext = NULL;
287 if (lastRecord != NULL)
288 lastRecord->pNext = newRecord;
290 lastRecord = newRecord;
291 newRecord = NULL;
295 delete newRecord;
297 return TRUE;
300 void DnsRecordListFree(PDNS_RECORD rec, int /* FreeType */)
302 while (rec != NULL) {
303 PDNS_RECORD next = rec->pNext;
304 delete rec;
305 rec = next;
309 DNS_STATUS DnsQuery_A(const char * service,
310 WORD requestType,
311 DWORD options,
312 void *,
313 PDNS_RECORD * results,
314 void *)
316 if (results == NULL)
317 return -1;
319 *results = NULL;
321 res_init();
323 union {
324 HEADER hdr;
325 BYTE buf[PACKETSZ];
326 } reply;
328 int replyLen = res_search(service, C_IN, requestType, (BYTE *)&reply, sizeof(reply));
330 if (replyLen < 1)
331 return -1;
333 BYTE * replyStart = reply.buf;
334 BYTE * replyEnd = reply.buf + replyLen;
335 BYTE * cp = reply.buf + sizeof(HEADER);
337 // ignore questions in response
338 unsigned i;
339 for (i = 0; i < ntohs(reply.hdr.qdcount); i++) {
340 char qName[MAXDNAME];
341 if (!GetDN(replyStart, replyEnd, cp, qName))
342 return -1;
343 cp += QFIXEDSZ;
346 if (!ProcessDNSRecords(replyStart,
347 replyEnd,
349 ntohs(reply.hdr.ancount),
350 ntohs(reply.hdr.nscount),
351 ntohs(reply.hdr.arcount),
352 results)) {
353 DnsRecordListFree(*results, 0);
354 return -1;
357 return 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)
370 return LessThan;
371 else if (priority > other.priority)
372 return GreaterThan;
373 if (weight < other.weight)
374 return LessThan;
375 else if (weight > other.weight)
376 return GreaterThan;
377 return EqualTo;
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
391 PINDEX i;
392 for (i = 0; i < GetSize(); i++)
393 strm << (*this)[i] << endl;
396 PDNS::SRVRecord * PDNS::SRVRecordList::GetFirst()
398 if (GetSize() == 0)
399 return NULL;
401 // create a list of all prioities, to save time
402 priPos = 0;
403 priList.SetSize(0);
405 PINDEX i;
406 if (GetSize() > 0) {
407 priList.SetSize(1);
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;
421 priPos = 0;
422 return GetNext();
425 PDNS::SRVRecord * PDNS::SRVRecordList::GetNext()
427 if (priList.GetSize() == 0)
428 return NULL;
430 while (priPos < priList.GetSize()) {
432 WORD currentPri = priList[priPos];
434 // find first record at current priority
435 PINDEX firstPos;
436 for (firstPos = 0; (firstPos < GetSize()) && ((*this)[firstPos].priority != currentPri); firstPos++)
438 if (firstPos == GetSize())
439 return NULL;
441 // calculate total of all unused weights at this priority
442 unsigned totalWeight = (*this)[firstPos].weight;
443 PINDEX i = firstPos + 1;
444 PINDEX count = 1;
445 while (i < GetSize() && ((*this)[i].priority == currentPri)) {
446 if (!(*this)[i].used) {
447 totalWeight += (*this)[i].weight;
448 count ++;
452 // if no matches found, go to the next priority level
453 if (count == 0) {
454 priPos++;
455 continue;
458 // selected the correct item
459 if (totalWeight > 0) {
460 unsigned targetWeight = PRandom::Number() % (totalWeight+1);
461 totalWeight = 0;
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;
467 return &(*this)[i];
473 // pick a random item at this priority
474 PINDEX j = firstPos + ((count == 0) ? 0 : (PRandom::Number() % count) );
475 count = 0;
476 for (i = 0; i < GetSize() && ((*this)[i].priority == currentPri); i++) {
477 if (!(*this)[i].used) {
478 if (count == j) {
479 (*this)[i].used = TRUE;
480 return &(*this)[i];
482 count++;
486 // go to the next priority level
487 priPos++;
490 return NULL;
493 ///////////////////////////////////////////////////////
495 BOOL PDNS::GetSRVRecords(const PString & _service,
496 const PString & type,
497 const PString & domain,
498 SRVRecordList & recordList)
501 if (_service.IsEmpty())
502 return FALSE;
504 PString service;
505 if (_service[0] != '_')
506 service = PString("_") + _service;
507 else
508 service = _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,
521 DNS_TYPE_SRV,
522 DNS_QUERY_STANDARD | DNS_QUERY_BYPASS_CACHE,
523 NULL,
524 &results,
525 NULL);
526 if (status != 0)
527 return FALSE;
529 // find SRV records
530 PDNS_RECORD dnsRecord = results;
531 while (dnsRecord != NULL) {
532 if (
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);
548 break;
550 aRecord = aRecord->pNext;
553 // if no A record found, then get address the hard way
554 if (aRecord == NULL)
555 PIPSocket::GetHostAddress(record->hostName, record->hostAddress);
557 // add record to the list
558 recordList.Append(record);
560 dnsRecord = dnsRecord->pNext;
563 if (results != NULL)
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)
575 return LessThan;
576 else if (preference > other.preference)
577 return GreaterThan;
578 return EqualTo;
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())
592 return FALSE;
594 recordList.RemoveAll();
596 PDNS_RECORD results = NULL;
597 DNS_STATUS status = DnsQuery_A((const char *)domain,
598 DNS_TYPE_MX,
599 DNS_QUERY_STANDARD,
600 NULL,
601 &results,
602 NULL);
603 if (status != 0)
604 return FALSE;
606 // find MX records
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);
619 break;
621 aRecord = aRecord->pNext;
624 // if no A record found, then get address the hard way
625 if (aRecord == NULL)
626 PIPSocket::GetHostAddress(record->hostName, record->hostAddress);
628 // add record to the list
629 recordList.Append(record);
631 dnsRecord = dnsRecord->pNext;
634 if (results != NULL)
635 DnsRecordListFree(results, DnsFreeRecordList);
637 return recordList.GetSize() != 0;
640 ///////////////////////////////////////////////////////
642 void PDNS::MXRecordList::PrintOn(ostream & strm) const
644 PINDEX i;
645 for (i = 0; i < GetSize(); i++)
646 strm << (*this)[i] << endl;
649 PDNS::MXRecord * PDNS::MXRecordList::GetFirst()
651 PINDEX i;
652 for (i = 0; i < GetSize(); i++)
653 (*this)[i].used = FALSE;
655 lastIndex = 0;
657 return GetNext();
660 PDNS::MXRecord * PDNS::MXRecordList::GetNext()
662 if (GetSize() == 0)
663 return NULL;
665 if (lastIndex >= GetSize())
666 return NULL;
668 return (PDNS::MXRecord *)GetAt(lastIndex++);
672 #endif // P_DNS
675 // End Of File ///////////////////////////////////////////////////////////////