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