1 /*
2  * enum.cxx
3  *
4  * Portable Windows Library
5  *
6  * Copyright (C) 2004 Post Increment
7  *
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/
12  *
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.
17  *
18  * The Original Code is Portable Windows Library.
19  *
20  * The Initial Developer of the Original Code is Post Increment
21  *
22  * Contributor(s): ______________________________________.
23  *
24  * $Revision: 24534 $
25  * $Author: rjongbloed $
26  * $Date: 2010-06-23 06:14:27 -0500 (Wed, 23 Jun 2010) $
27  */
28 
29 #ifdef __GNUC__
30 #pragma implementation "enum.h"
31 #endif
32 
33 #include <ptlib.h>
34 #include <ptclib/pdns.h>
35 #include <ptclib/enum.h>
36 
37 #define new PNEW
38 
39 
40 #if P_DNS
41 
42 #ifdef  _WIN32
43 #define PATH_SEP   ";"
44 #else
45 #define PATH_SEP   ":"
46 #endif
47 
48 static const char * PWLIB_ENUM_PATH = "PWLIB_ENUM_PATH";
49 
50 ///////////////////////////////////////////////////////////////////////
51 
Compare(const PObject & obj) const52 PObject::Comparison PDNS::NAPTRRecord::Compare(const PObject & obj) const
53 {
54   const NAPTRRecord * other = dynamic_cast<const NAPTRRecord *>(&obj);
55 
56   if (other == NULL)
57     return LessThan;
58 
59   if (order < other->order)
60     return LessThan;
61   else if (order > other->order)
62     return GreaterThan;
63 
64   if (preference < other->preference)
65     return LessThan;
66   else if (preference > other->preference)
67     return GreaterThan;
68 
69   return EqualTo;
70 }
71 
PrintOn(ostream & strm) const72 void PDNS::NAPTRRecord::PrintOn(ostream & strm) const
73 {
74   strm << "order=" << order << ", "
75        << "preference=" << preference << ", "
76        << "flags=" << flags << ", "
77        << "service=" << service << ", "
78        << "regex=" << regex << ", "
79        << "replacement=" << replacement;
80 }
81 
82 ///////////////////////////////////////////////////////////////////////
83 
84 struct NAPTR_DNS {
85   PUInt16b order;
86   PUInt16b preference;
87 
88   char info[1];
89 
GetFlagsBaseNAPTR_DNS90   char * GetFlagsBase() const       { return (char *)&info; }
GetFlagsLenNAPTR_DNS91   int GetFlagsLen() const           { return (int)GetFlagsBase()[0]; }
92 
GetServiceBaseNAPTR_DNS93   char * GetServiceBase() const     { return GetFlagsBase() + 1 + GetFlagsLen(); }
GetServiceLenNAPTR_DNS94   int GetServiceLen() const         { return (int)GetServiceBase()[0]; }
95 
GetRegexBaseNAPTR_DNS96   char * GetRegexBase() const       { return GetServiceBase() + 1 + GetServiceLen(); }
GetRegexLenNAPTR_DNS97   int GetRegexLen() const           { return (int)GetRegexBase()[0]; }
98 
GetReplacementBaseNAPTR_DNS99   char * GetReplacementBase() const { return GetRegexBase() + 1 + GetRegexLen(); }
GetReplacementLenNAPTR_DNS100   int GetReplacementLen() const     { return (int)GetReplacementBase()[0]; }
101 
GetFlagsNAPTR_DNS102   PString GetFlags() const          { return PString(GetFlagsBase()+1,       GetFlagsLen()); }
GetServiceNAPTR_DNS103   PString GetService() const        { return PString(GetServiceBase()+1,     GetServiceLen()); }
GetRegexNAPTR_DNS104   PString GetRegex() const          { return PString(GetRegexBase()+1,       GetRegexLen()); }
GetReplacementNAPTR_DNS105   PString GetReplacement() const    { return PString(GetReplacementBase()+1, GetReplacementLen()); }
106 };
107 
108 
ResolveNAPTR(PDNS_RECORD dnsRecord,PDNS::NAPTRRecord & record)109 void ResolveNAPTR(PDNS_RECORD dnsRecord, PDNS::NAPTRRecord & record)
110 {
111 #ifdef _WIN32
112   if (PProcess::IsOSVersion(6)) {
113     DNS_NAPTR_DATA * naptr = (DNS_NAPTR_DATA *)&dnsRecord->Data;
114     record.order       = naptr->wOrder;
115     record.preference  = naptr->wPreference;
116     record.flags       = naptr->pFlags;
117     record.service     = naptr->pService;
118     record.regex       = naptr->pRegularExpression;
119     record.replacement = naptr->pReplacement;
120   }
121   else
122 #endif
123   {
124     NAPTR_DNS * naptr = (NAPTR_DNS *)&dnsRecord->Data;
125     record.order       = naptr->order;
126     record.preference  = naptr->preference;
127     record.flags       = naptr->GetFlags();
128     record.service     = naptr->GetService();
129     record.regex       = naptr->GetRegex();
130     record.replacement = naptr->GetReplacement();
131   }
132 }
133 
134 
HandleDNSRecord(PDNS_RECORD dnsRecord,PDNS_RECORD)135 PDNS::NAPTRRecord * PDNS::NAPTRRecordList::HandleDNSRecord(PDNS_RECORD dnsRecord, PDNS_RECORD /*results*/)
136 {
137   PDNS::NAPTRRecord * record = NULL;
138 
139   if (
140       (dnsRecord->Flags.S.Section == DnsSectionAnswer) &&
141       (dnsRecord->wType == DNS_TYPE_NAPTR)
142       ) {
143     record = new NAPTRRecord();
144 
145 	ResolveNAPTR(dnsRecord, *record);
146 
147   }
148 
149   return record;
150 }
151 
152 
PrintOn(ostream & strm) const153 void PDNS::NAPTRRecordList::PrintOn(ostream & strm) const
154 {
155   PINDEX i;
156   for (i = 0; i < GetSize(); i++)
157     strm << (*this)[i] << endl;
158 }
159 
GetFirst(const char * service)160 PDNS::NAPTRRecord * PDNS::NAPTRRecordList::GetFirst(const char * service)
161 {
162   if (GetSize() == 0)
163     return NULL;
164 
165   currentPos   = 0;
166   lastOrder = operator[](0).order;
167   orderLocked = PFalse;
168 
169   return GetNext(service);
170 }
171 
GetNext(const char * service)172 PDNS::NAPTRRecord * PDNS::NAPTRRecordList::GetNext(const char * service)
173 {
174   if (GetSize() == 0)
175     return NULL;
176 
177   while (currentPos < GetSize()) {
178 
179     NAPTRRecord & record = operator[](currentPos);
180 
181     // once we have a match, we cannot look at higher order records
182     // and note that the list is already sorted by preference
183     if (orderLocked && lastOrder != record.order)
184       return NULL;
185 
186     else {
187       currentPos++;
188       lastOrder   = record.order;
189       if (record.order == lastOrder) {
190         if ((service == NULL) || (record.service *= service)) {
191           orderLocked = PTrue;
192           return &record;
193         }
194       }
195     }
196   }
197 
198   return NULL;
199 }
200 
ApplyRegex(const PString & orig,const PString & regexStr)201 static PString ApplyRegex(const PString & orig, const PString & regexStr)
202 {
203   // must have at least 3 delimiters and two chars of text
204   if (regexStr.GetLength() < 5) {
205     PTRACE(1, "ENUM\tregex is too short: " << regexStr);
206     return PString::Empty();
207   }
208 
209   // first char in the regex is always the delimiter
210   char delimiter = regexStr[0];
211 
212   // break the string into match and replace strings by looking for non-escaped delimiters
213   PString strings[2];
214   PINDEX strNum = 0;
215   PINDEX pos = 1;
216   PINDEX start = pos;
217   for (pos = 1; strNum < 2 && pos < regexStr.GetLength(); pos++) {
218     if (regexStr[pos] == '\\')
219       pos++;
220     else if (regexStr[pos] == delimiter) {
221       strings[strNum] = regexStr(start, pos-1);
222       strNum++;
223       pos++;
224       start = pos;
225     }
226   }
227 
228   // make sure we have some strings
229   // CRS: this construct avoids a gcc crash with gcc 3.5-20040704/
230   // when using the following:
231   // if (strings[0].IsEmpty() || strings[1].IsEmpty()) {
232   PString & str1 = strings[0];
233   PString & str2 = strings[1];
234   if (str1.IsEmpty() || str2.IsEmpty()) {
235     PTRACE(1, "ENUM\tregex does not parse into two string: " << regexStr);
236     return PString::Empty();
237   }
238 
239   // get the flags
240   PString flags;
241   if (strNum == 2 && pos < regexStr.GetLength()-1) {
242     pos++;
243     flags = regexStr.Mid(pos+1).ToLower();
244   }
245 
246   // construct the regular expression
247   PRegularExpression regex;
248   int regexFlags = PRegularExpression::Extended;
249   if (flags.Find('i') != P_MAX_INDEX)
250     regexFlags += PRegularExpression::IgnoreCase;
251   if (!regex.Compile(strings[0], regexFlags)) {
252     PTRACE(1, "ENUM\tregex does not compile : " << regexStr);
253     return PString();
254   }
255 
256   // apply the regular expression to the original string
257   PIntArray starts(10), ends(10);
258   if (!regex.Execute(orig, starts, ends)) {
259     PTRACE(1, "ENUM\tregex does not execute : " << regexStr);
260     return PString();
261   }
262 
263   // replace variables in the second string
264   PString value = strings[1];
265   for (pos = 0; pos < value.GetLength(); pos++) {
266     if (value[pos] == '\\' && pos < value.GetLength()-1) {
267       int var = value[pos+1]-'1'+1;
268       PString str;
269       if (var >= 0 && var < starts.GetSize() && var < ends.GetSize())
270         str = orig(starts[var], ends[var]);
271       value = value.Left(pos) + str + value.Mid(pos+2);
272     }
273   }
274 
275   return value;
276 }
277 
GetENUMServers()278 static PStringArray & GetENUMServers()
279 {
280   static const char * defaultDomains[] = { "e164.org","e164.arpa"};
281   static PStringArray servers(
282           sizeof(defaultDomains)/sizeof(defaultDomains[0]),
283           defaultDomains
284   );
285   return servers;
286 }
287 
GetENUMServerMutex()288 static PMutex & GetENUMServerMutex()
289 {
290   static PMutex mutex;
291   return mutex;
292 }
293 
SetENUMServers(const PStringArray & servers)294 void PDNS::SetENUMServers(const PStringArray & servers)
295 {
296      PWaitAndSignal m(GetENUMServerMutex());
297      GetENUMServers() = servers;
298 }
299 
ENUMLookup(const PString & e164,const PString & service,PString & dn)300 PBoolean PDNS::ENUMLookup(const PString & e164,
301       const PString & service,PString & dn)
302 {
303   PWaitAndSignal m(GetENUMServerMutex());
304   PStringArray domains;
305   char * env = ::getenv(PWLIB_ENUM_PATH);
306   if (env == NULL)
307     domains += GetENUMServers();
308   else
309     domains += PString(env).Tokenise(PATH_SEP);
310 
311   return PDNS::ENUMLookup(e164, service, domains, dn);
312 }
313 
InternalENUMLookup(const PString & e164,const PString & service,PDNS::NAPTRRecordList & records,PString & returnStr)314 static PBoolean InternalENUMLookup(const PString & e164, const PString & service, PDNS::NAPTRRecordList & records, PString & returnStr)
315 {
316   PBoolean result = PFalse;
317 
318   // get the first record that matches the service.
319   PDNS::NAPTRRecord * rec = records.GetFirst(service);
320 
321   do {
322 
323     // if no more records that match this service, then fail
324     if (rec == NULL)
325       break;
326 
327     // process the flags
328     PBoolean handled  = PFalse;
329     PBoolean terminal = PTrue;
330 
331     for (PINDEX f = 0; !handled && f < rec->flags.GetLength(); ++f) {
332       switch (tolower(rec->flags[f])) {
333 
334         // do an SRV lookup
335         case 's':
336           terminal = PTrue;
337           handled = PFalse;
338           break;
339 
340         // do an A lookup
341         case 'a':
342           terminal = PTrue;
343           handled = PFalse;
344           break;
345 
346         // apply regex and do the lookup
347         case 'u':
348           returnStr = ApplyRegex(e164, rec->regex);
349           result   = PTrue;
350           terminal = PTrue;
351           handled  = PTrue;
352           break;
353 
354         // handle in a protocol specific way - not supported
355         case 'p':
356           handled = PFalse;
357           break;
358 
359         default:
360           handled = PFalse;
361       }
362     }
363 
364     // if no flags were accepted, then unlock the order on the record and get the next record
365     if (!handled) {
366       records.UnlockOrder();
367       rec = records.GetNext(service);
368       continue;
369     }
370 
371     // if this was a terminal lookup, finish now
372     if (terminal)
373       break;
374 
375   } while (!result);
376 
377   return result;
378 }
379 
ENUMLookup(const PString & _e164,const PString & service,const PStringArray & enumSpaces,PString & returnStr)380 PBoolean PDNS::ENUMLookup(
381         const PString & _e164,
382         const PString & service,
383    const PStringArray & enumSpaces,
384               PString & returnStr
385 )
386 {
387   PString e164 = _e164;
388 
389   if (e164[0] != '+')
390     e164 = PString('+') + e164;
391 
392   ////////////////////////////////////////////////////////
393   // convert to domain name as per RFC 2916
394 
395   // remove all non-digits
396   PINDEX pos = 1;
397   while (pos < e164.GetLength()) {
398     if (isdigit(e164[pos]))
399       pos++;
400     else
401       e164 = e164.Left(pos) + e164.Mid(pos+1);
402   }
403 
404   // reverse the order of the digits, and add "." in between each digit
405   PString domain;
406   for (pos = 1; pos < e164.GetLength(); pos++) {
407     if (!domain.IsEmpty())
408       domain = PString('.') + domain;
409     domain = PString(e164[pos]) + domain;
410   }
411 
412   for (PINDEX i = 0; i < enumSpaces.GetSize(); i++) {
413 
414     PDNS::NAPTRRecordList records;
415 
416     // do the initial lookup - if no answer then the lookup failed
417     if (!PDNS::GetRecords(domain + "." + enumSpaces[i], records))
418       continue;
419 
420     if (InternalENUMLookup(e164, service, records, returnStr))
421       return PTrue;
422   }
423 
424   return PFalse;
425 }
426 
427 ////////////////////////////////////////////////////////////////////////
428 
429 static const char * PWLIB_RDS_PATH = "PWLIB_RDS_PATH";
430 
GetRDSServers()431 static PStringArray & GetRDSServers()
432 {
433   static const char * defaultDomains[] = {"rds.voxgratia.org"};
434   static PStringArray servers(
435           sizeof(defaultDomains)/sizeof(defaultDomains[0]),
436           defaultDomains
437   );
438   return servers;
439 }
440 
RewriteDomain(const PString & original,PDNS::NAPTRRecordList & records,PString & returnStr)441 static PBoolean RewriteDomain(const PString & original, PDNS::NAPTRRecordList & records, PString & returnStr)
442 {
443    PBoolean result = PFalse;
444 
445   // get the first record that matches the service.
446   PDNS::NAPTRRecord * rec = records.GetFirst();
447 
448   do {
449 
450     // if no more records that match this service, then fail
451     if (rec == NULL)
452       break;
453 
454     // process the flags
455     PBoolean handled  = PFalse;
456 
457 	// General domain rewrites has no flag
458     if (rec->flags.IsEmpty()) {
459         returnStr = ApplyRegex(original, rec->regex);
460 		if (returnStr.GetLength() > 0) {
461             result   = PTrue;
462             handled  = PTrue;
463 			break;
464 		}
465 	} else {
466 	   break;   // We have other types of records which we don't want.
467 	}
468 
469     // if no flags were accepted, then unlock the order on the record and get the next record
470     if (!handled) {
471       records.UnlockOrder();
472       rec = records.GetNext();
473       continue;
474     }
475 
476   } while (!result);
477 
478   return result;
479 }
480 
InternalRDSLookup(const PString & rds,const PString & service,PDNS::NAPTRRecordList & records,PString & returnStr)481 static PBoolean InternalRDSLookup(const PString & rds, const PString & service, PDNS::NAPTRRecordList & records, PString & returnStr)
482 {
483   PBoolean result = PFalse;
484 
485   // get the first record that matches the service.
486   PDNS::NAPTRRecord * rec = records.GetFirst(service);
487 
488   do {
489 
490     // if no more records that match this service, then fail
491     if (rec == NULL)
492       break;
493 
494     // process the flags
495     PBoolean handled  = PFalse;
496     PBoolean terminal = PTrue;
497 
498     for (PINDEX f = 0; !handled && f < rec->flags.GetLength(); ++f) {
499       switch (tolower(rec->flags[f])) {
500 
501         // do an SRV lookup
502         case 's':
503 		  // apply regex and do the lookup
504           returnStr = ApplyRegex(rds, rec->regex);
505           result   = PTrue;
506           terminal = PTrue;
507           handled  = PTrue;
508           break;
509 
510         case 'a':            // A lookup
511         case 'u':            // U Lookup
512           terminal = PTrue;
513           handled = PFalse;
514           break;
515 
516         case 'p':           // P specific
517 		default:
518           handled = PFalse;
519           break;
520       }
521     }
522 
523     // if no flags were accepted, then unlock the order on the record and get the next record
524     if (!handled) {
525       records.UnlockOrder();
526       rec = records.GetNext(service);
527       continue;
528     }
529 
530     // if this was a terminal lookup, finish now
531     if (terminal)
532       break;
533 
534   } while (!result);
535 
536   return result;
537 }
538 
539 
GetRDSServerMutex()540 static PMutex & GetRDSServerMutex()
541 {
542   static PMutex mutex;
543   return mutex;
544 }
545 
SetRDSServers(const PStringArray & servers)546 void PDNS::SetRDSServers(const PStringArray & servers)
547 {
548      PWaitAndSignal m(GetRDSServerMutex());
549      GetRDSServers() = servers;
550 }
551 
RDSLookup(const PURL & url,const PString & service,PStringList & dn)552 PBoolean PDNS::RDSLookup(const PURL & url,
553       const PString & service,PStringList & dn)
554 {
555   PWaitAndSignal m(GetRDSServerMutex());
556   PStringArray domains;
557   char * env = ::getenv(PWLIB_RDS_PATH);
558   if (env == NULL)
559     domains += GetRDSServers();
560   else
561     domains += PString(env).Tokenise(PATH_SEP);
562 
563   return PDNS::RDSLookup(url, service, domains, dn);
564 }
565 
RDSLookup(const PURL & url,const PString & service,const PStringArray & naptrSpaces,PStringList & returnStr)566 PBoolean PDNS::RDSLookup(
567         const PURL & url,
568         const PString & service,
569    const PStringArray & naptrSpaces,
570          PStringList & returnStr
571 )
572 {
573 
574   for (PINDEX i = 0; i < naptrSpaces.GetSize(); i++) {
575 
576     PDNS::NAPTRRecordList records;
577 
578     // do the initial lookup - if no answer then no URN RDS records for that domain
579     if (!PDNS::GetRecords(naptrSpaces[i], records))
580       continue;
581 
582 	// Do a universal domain rewrite Ref: RFC 2915 sect 7.1
583     PString newURL = PString();
584 	if (!RewriteDomain(url.AsString(), records, newURL))
585 	  continue;
586 
587 	// Retrieve the NAPTR records associated with that rewritten domain.
588 	PDNS::NAPTRRecordList subrecords;
589     if (!PDNS::GetRecords(newURL, subrecords))
590         continue;
591 
592 	// Retrieve the SRV records for the service
593     PString srvRecord = PString();
594 	if (!InternalRDSLookup(url.AsString(),service,subrecords,srvRecord))
595 	    continue;
596 
597 	// Should be in the form "_h323ls._udp.mydomain.com";
598 	// Need to find the second "." to retrieve the service record type
599     PINDEX dot = 0;
600 	for (PINDEX i = 0;  i < 2; i++) {
601 	   dot = srvRecord.Find('.',dot+1);
602 	}
603 
604 	// Rewrite the userName
605 	PString finaluser = url.GetScheme() + ":" + url.GetUserName() + "@" + srvRecord.Mid(dot+1);
606 	// Retrieve the service record type
607 	PString srvrec = srvRecord.Left(dot+1);
608 
609 	// Lookup the SRV record for the hosted domain.
610 	PStringList retStr;
611 	if (!PDNS::LookupSRV(finaluser,srvrec,retStr))
612 	    continue;
613 
614 	if (retStr.GetSize() > 0) {   // We have found records
615 		returnStr = retStr;
616 	    return PTrue;
617 	}
618   }
619 
620   return PFalse;
621 }
622 
623 
624 #endif
625 
626 // End of File ///////////////////////////////////////////////////////////////
627