-/*
+/* -*- Mode: C; tab-width: 4 -*-
+ *
* Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
Change History (most recent first):
$Log: DNSCommon.c,v $
+Revision 1.96.2.1 2006/10/31 02:50:16 cheshire
+<rdar://problem/4683163> mDNSResponder insufficiently defensive against malformed browsing PTR responses
+
+Revision 1.96 2006/03/10 21:51:42 cheshire
+<rdar://problem/4111464> After record update, old record sometimes remains in cache
+Split out SameRDataBody() into a separate routine so it can be called from other code
+
+Revision 1.95 2006/03/08 22:43:11 cheshire
+Use "localdomain" symbol instead of literal string
+
+Revision 1.94 2006/03/02 21:59:55 cheshire
+<rdar://problem/4395331> Spurious warning "GetLargeResourceRecord: m->rec appears to be already in use"
+Improve sanity checks & debugging support in GetLargeResourceRecord()
+
+Revision 1.93 2006/03/02 20:30:47 cheshire
+Improved GetRRDisplayString to also show priority, weight, and port for SRV records
+
+Revision 1.92 2005/09/16 21:06:49 cheshire
+Use mDNS_TimeNow_NoLock macro, instead of writing "mDNSPlatformRawTime() + m->timenow_adjust" all over the place
+
+Revision 1.91 2005/07/10 22:10:37 cheshire
+The getOptRdata routine implicitly assumes the destination ResourceRecord is large enough to
+hold MaximumRDSize bytes, but its parameter was a generic ResourceRecord, which need not be that
+large. Changing the parameter to a LargeCacheRecord makes it clearer what the routine requires.
+
+Revision 1.90 2005/03/21 00:33:51 shersche
+<rdar://problem/4021486> Fix build warnings on Win32 platform
+
+Revision 1.89 2005/03/17 18:59:38 ksekar
+<rdar://problem/4012279> Properly parse multiple LLQ Options per packet on Windows
+
+Revision 1.88 2005/03/16 00:42:32 ksekar
+<rdar://problem/4012279> Long-lived queries not working on Windows
+
+Revision 1.87 2005/02/25 04:21:00 cheshire
+<rdar://problem/4015377> mDNS -F returns the same domain multiple times with different casing
+
+Revision 1.86 2005/02/18 00:43:12 cheshire
+<rdar://problem/4010245> mDNSResponder should auto-truncate service names that are too long
+
+Revision 1.85 2005/02/10 22:35:17 cheshire
+<rdar://problem/3727944> Update name
+
+Revision 1.84 2005/02/03 00:44:38 cheshire
+<rdar://problem/3986663> DNSServiceUpdateRecord returns kDNSServiceErr_Invalid when rdlen=0, rdata=NULL
+
Revision 1.83 2005/01/27 22:57:55 cheshire
Fix compile errors on gcc4
<rdar://problem/3913653> Wide-Area Goodbyes broken
Revision 1.73 2004/12/07 22:49:06 cheshire
-<rdar://problem/3908850> BIND doesn't like zero-length rdata
+<rdar://problem/3908850> BIND doesn't allow zero-length TXT records
Revision 1.72 2004/12/06 21:15:20 ksekar
<rdar://problem/3884386> mDNSResponder crashed in CheckServiceRegistrations
ustrcpy macro to DNSCommon.c using mDNSPlatformStrCopy().
Revision 1.42 2004/08/10 23:19:14 ksekar
-<rdar://problem/3722542>: DNS Extension daemon for Wide Area Rendezvous
+<rdar://problem/3722542>: DNS Extension daemon for Wide Area Service Discovery
Moved routines/constants to allow extern access for garbage collection daemon
Revision 1.41 2004/08/10 01:10:01 cheshire
}
}
+// Note slight bug: this code uses the rdlength from the ResourceRecord object, to display
+// the rdata from the RDataBody object. Sometimes this could be the wrong length -- but as
+// long as this routine is only used for debugging messages, it probably isn't a big problem.
mDNSexport char *GetRRDisplayString_rdb(const ResourceRecord *rr, RDataBody *rd, char *buffer)
{
char *ptr = buffer;
case kDNSType_TXT: mDNS_snprintf(buffer+length, 79-length, "%#s", rd->txt.c); break;
case kDNSType_AAAA: mDNS_snprintf(buffer+length, 79-length, "%.16a", &rd->ipv6); break;
- case kDNSType_SRV: mDNS_snprintf(buffer+length, 79-length, "%##s", rd->srv.target.c); break;
+ case kDNSType_SRV: mDNS_snprintf(buffer+length, 79-length, "%u %u %u %##s",
+ rd->srv.priority, rd->srv.weight, mDNSVal16(rd->srv.port), rd->srv.target.c); break;
default: mDNS_snprintf(buffer+length, 79-length, "RDLen %d: %s", rr->rdlength, rd->data); break;
}
for (ptr = buffer; *ptr; ptr++) if (*ptr < ' ') *ptr='.';
for (i=0; i < (int)sizeof(SubTypeLabel); i++) *dst++ = SubTypeLabel[i];
type = (domainname *)s1;
- // Special support for queries done by older versions of "Rendezvous Browser"
+ // Special support for queries done by some third-party network monitoring software
// For these queries, we retract the "._sub" we just added between the subtype and the main type
if (SameDomainName((domainname*)s0, (domainname*)"\x09_services\x07_dns-sd\x04_udp") ||
SameDomainName((domainname*)s0, (domainname*)"\x09_services\x05_mdns\x04_udp"))
src = type->c; // Put the service type into the domain name
len = *src;
- if (len < 2 || len >= 0x40 || (len > 15 && !SameDomainName(domain, (domainname*)"\x05" "local")))
+ if (len < 2 || len >= 0x40 || (len > 15 && !SameDomainName(domain, &localdomain)))
{
errormsg="Application protocol name must be underscore plus 1-14 characters. See <http://www.dns-sd.org/ServiceTypes.html>";
goto fail;
return(mDNSNULL);
}
+// A service name has the form: instance.application-protocol.transport-protocol.domain
+// DeconstructServiceName is currently fairly forgiving: It doesn't try to enforce character
+// set or length limits for the protocol names, and the final domain is allowed to be empty.
+// However, if the given FQDN doesn't contain at least three labels,
+// DeconstructServiceName will reject it and return mDNSfalse.
mDNSexport mDNSBool DeconstructServiceName(const domainname *const fqdn,
domainlabel *const name, domainname *const type, domainname *const domain)
{
const mDNSu8 *max = fqdn->c + MAX_DOMAIN_NAME;
mDNSu8 *dst;
- dst = name->c; // Extract the service name from the domain name
+ dst = name->c; // Extract the service name
len = *src;
- if (len >= 0x40) { debugf("DeconstructServiceName: service name too long"); return(mDNSfalse); }
+ if (!len) { debugf("DeconstructServiceName: FQDN empty!"); return(mDNSfalse); }
+ if (len >= 0x40) { debugf("DeconstructServiceName: Instance name too long"); return(mDNSfalse); }
for (i=0; i<=len; i++) *dst++ = *src++;
- dst = type->c; // Extract the service type from the domain name
+ dst = type->c; // Extract the service type
len = *src;
- if (len >= 0x40) { debugf("DeconstructServiceName: service type too long"); return(mDNSfalse); }
+ if (!len) { debugf("DeconstructServiceName: FQDN contains only one label!"); return(mDNSfalse); }
+ if (len >= 0x40) { debugf("DeconstructServiceName: Application protocol name too long"); return(mDNSfalse); }
for (i=0; i<=len; i++) *dst++ = *src++;
len = *src;
- if (len >= 0x40) { debugf("DeconstructServiceName: service type too long"); return(mDNSfalse); }
+ if (!len) { debugf("DeconstructServiceName: FQDN contains only two labels!"); return(mDNSfalse); }
+ if (len >= 0x40) { debugf("DeconstructServiceName: Transport protocol name too long"); return(mDNSfalse); }
for (i=0; i<=len; i++) *dst++ = *src++;
- *dst++ = 0; // Put the null root label on the end of the service type
+ *dst++ = 0; // Put terminator on the end of service type
- dst = domain->c; // Extract the service domain from the domain name
+ dst = domain->c; // Extract the service domain
while (*src)
{
len = *src;
if (len >= 0x40)
- { debugf("DeconstructServiceName: service domain label too long"); return(mDNSfalse); }
+ { debugf("DeconstructServiceName: Label in service domain too long"); return(mDNSfalse); }
if (src + 1 + len + 1 >= max)
- { debugf("DeconstructServiceName: service domain too long"); return(mDNSfalse); }
+ { debugf("DeconstructServiceName: Total service domain too long"); return(mDNSfalse); }
for (i=0; i<=len; i++) *dst++ = *src++;
}
*dst++ = 0; // Put the null root label on the end
return(mDNStrue);
}
+// Notes on UTF-8:
+// 0xxxxxxx represents a 7-bit ASCII value from 0x00 to 0x7F
+// 10xxxxxx is a continuation byte of a multi-byte character
+// 110xxxxx is the first byte of a 2-byte character (11 effective bits; values 0x 80 - 0x 800-1)
+// 1110xxxx is the first byte of a 3-byte character (16 effective bits; values 0x 800 - 0x 10000-1)
+// 11110xxx is the first byte of a 4-byte character (21 effective bits; values 0x 10000 - 0x 200000-1)
+// 111110xx is the first byte of a 5-byte character (26 effective bits; values 0x 200000 - 0x 4000000-1)
+// 1111110x is the first byte of a 6-byte character (31 effective bits; values 0x4000000 - 0x80000000-1)
+//
+// UTF-16 surrogate pairs are used in UTF-16 to encode values larger than 0xFFFF.
+// Although UTF-16 surrogate pairs are not supposed to appear in legal UTF-8, we want to be defensive
+// about that too. (See <http://www.unicode.org/faq/utf_bom.html#34>, "What are surrogates?")
+// The first of pair is a UTF-16 value in the range 0xD800-0xDBFF (11101101 1010xxxx 10xxxxxx in UTF-8),
+// and the second is a UTF-16 value in the range 0xDC00-0xDFFF (11101101 1011xxxx 10xxxxxx in UTF-8).
+
+mDNSexport mDNSu32 TruncateUTF8ToLength(mDNSu8 *string, mDNSu32 length, mDNSu32 max)
+ {
+ if (length > max)
+ {
+ mDNSu8 c1 = string[max]; // First byte after cut point
+ mDNSu8 c2 = (max+1 < length) ? string[max+1] : 0xB0; // Second byte after cut point
+ length = max; // Trim length down
+ while (length > 0)
+ {
+ // Check if the byte right after the chop point is a UTF-8 continuation byte,
+ // or if the character right after the chop point is the second of a UTF-16 surrogate pair.
+ // If so, then we continue to chop more bytes until we get to a legal chop point.
+ mDNSBool continuation = ((c1 & 0xC0) == 0x80);
+ mDNSBool secondsurrogate = (c1 == 0xED && (c2 & 0xF0) == 0xB0);
+ if (!continuation && !secondsurrogate) break;
+ c2 = c1;
+ c1 = string[--length];
+ }
+ // Having truncated characters off the end of our string, also cut off any residual white space
+ while (length > 0 && string[length-1] <= ' ') length--;
+ }
+ return(length);
+ }
+
// Returns true if a rich text label ends in " (nnn)", or if an RFC 1034
// name ends in "-nnn", where n is some decimal number.
mDNSexport mDNSBool LabelContainsSuffix(const domainlabel *const name, const mDNSBool RichText)
while (val >= divisor * 10) { divisor *= 10; chars++; }
- if (name->c[0] > (mDNSu8)(MAX_DOMAIN_LABEL - chars))
- {
- name->c[0] = (mDNSu8)(MAX_DOMAIN_LABEL - chars);
- // If the following character is a UTF-8 continuation character,
- // we just chopped a multi-byte UTF-8 character in the middle, so strip back to a safe truncation point
- while (name->c[0] > 0 && (name->c[name->c[0]+1] & 0xC0) == 0x80) name->c[0]--;
- }
+ name->c[0] = (mDNSu8) TruncateUTF8ToLength(name->c+1, name->c[0], MAX_DOMAIN_LABEL - chars);
if (RichText) { name->c[++name->c[0]] = ' '; name->c[++name->c[0]] = '('; }
else { name->c[++name->c[0]] = '-'; }
return(sum);
}
-mDNSexport mDNSBool SameRData(const ResourceRecord *const r1, const ResourceRecord *const r2)
+// r1 has to be a full ResourceRecord including rrtype and rdlength
+// r2 is just a bare RDataBody, which MUST be the same rrtype and rdlength as r1
+mDNSexport mDNSBool SameRDataBody(const ResourceRecord *const r1, const RDataBody *const r2)
{
- if (r1->rrtype != r2->rrtype) return(mDNSfalse);
- if (r1->rdlength != r2->rdlength) return(mDNSfalse);
- if (r1->rdatahash != r2->rdatahash) return(mDNSfalse);
- if (r1->rdnamehash != r2->rdnamehash) return(mDNSfalse);
switch(r1->rrtype)
{
case kDNSType_CNAME:// Same as PTR
- case kDNSType_PTR: return(SameDomainName(&r1->rdata->u.name, &r2->rdata->u.name));
+ case kDNSType_PTR: return(SameDomainName(&r1->rdata->u.name, &r2->name));
- case kDNSType_SRV: return(mDNSBool)( r1->rdata->u.srv.priority == r2->rdata->u.srv.priority &&
- r1->rdata->u.srv.weight == r2->rdata->u.srv.weight &&
- r1->rdata->u.srv.port.NotAnInteger == r2->rdata->u.srv.port.NotAnInteger &&
- SameDomainName(&r1->rdata->u.srv.target, &r2->rdata->u.srv.target) );
+ case kDNSType_SRV: return(mDNSBool)( r1->rdata->u.srv.priority == r2->srv.priority &&
+ r1->rdata->u.srv.weight == r2->srv.weight &&
+ r1->rdata->u.srv.port.NotAnInteger == r2->srv.port.NotAnInteger &&
+ SameDomainName(&r1->rdata->u.srv.target, &r2->srv.target) );
- default: return(mDNSPlatformMemSame(r1->rdata->u.data, r2->rdata->u.data, r1->rdlength));
+ default: return(mDNSPlatformMemSame(r1->rdata->u.data, r2->data, r1->rdlength));
}
}
+mDNSexport mDNSBool SameRData(const ResourceRecord *const r1, const ResourceRecord *const r2)
+ {
+ if (r1->rrtype != r2->rrtype) return(mDNSfalse);
+ if (r1->rdlength != r2->rdlength) return(mDNSfalse);
+ if (r1->rdatahash != r2->rdatahash) return(mDNSfalse);
+ return(SameRDataBody(r1, &r2->rdata->u));
+ }
+
mDNSexport mDNSBool SameResourceRecord(ResourceRecord *r1, ResourceRecord *r2)
{
return (r1->namehash == r2->namehash &&
mDNSexport mDNSBool ValidateRData(const mDNSu16 rrtype, const mDNSu16 rdlength, const RData *const rd)
{
mDNSu16 len;
- // Some (or perhaps all) versions of BIND named (name daemon) don't allow updates
- // with zero-length rdata, so for consistency we don't allow them for mDNS either.
- // Otherwise we risk having applications that work with mDNS but not with uDNS.
- if (!rdlength) return(mDNSfalse);
switch(rrtype)
{
case kDNSType_MR: // Same as PTR
//case kDNSType_NULL not checked (no specified format, so always valid)
//case kDNSType_WKS not checked
- case kDNSType_PTR: len = DomainNameLength(&rd->u.name);
+ case kDNSType_PTR: if (!rdlength) return(mDNSfalse);
+ len = DomainNameLength(&rd->u.name);
return(len <= MAX_DOMAIN_NAME && rdlength == len);
case kDNSType_HINFO:// Same as TXT (roughly)
case kDNSType_MINFO:// Same as TXT (roughly)
- case kDNSType_TXT: {
+ case kDNSType_TXT: if (!rdlength) return(mDNSfalse); // TXT record has to be at least one byte (RFC 1035)
+ {
const mDNSu8 *ptr = rd->u.txt.c;
const mDNSu8 *end = rd->u.txt.c + rdlength;
while (ptr < end) ptr += 1 + ptr[0];
case kDNSType_AAAA: return(rdlength == sizeof(mDNSv6Addr));
- case kDNSType_MX: len = DomainNameLength(&rd->u.mx.exchange);
+ case kDNSType_MX: if (!rdlength) return(mDNSfalse);
+ len = DomainNameLength(&rd->u.mx.exchange);
return(len <= MAX_DOMAIN_NAME && rdlength == 2+len);
- case kDNSType_SRV: len = DomainNameLength(&rd->u.srv.target);
+ case kDNSType_SRV: if (!rdlength) return(mDNSfalse);
+ len = DomainNameLength(&rd->u.srv.target);
return(len <= MAX_DOMAIN_NAME && rdlength == 6+len);
default: return(mDNStrue); // Allow all other types without checking
nput += 2 * sizeof(mDNSu16);
if (opt->opt == kDNSOpt_LLQ)
{
- if (ptr + sizeof(LLQOptData) > limit) goto space_err;
+ if (ptr + LLQ_OPTLEN > limit) goto space_err;
ptr = putVal16(ptr, opt->OptData.llq.vers);
ptr = putVal16(ptr, opt->OptData.llq.llqOp);
ptr = putVal16(ptr, opt->OptData.llq.err);
mDNSPlatformMemCopy(opt->OptData.llq.id, ptr, 8); // 8-byte id
ptr += 8;
ptr = putVal32(ptr, opt->OptData.llq.lease);
- nput += sizeof(LLQOptData);
+ nput += LLQ_OPTLEN;
}
else if (opt->opt == kDNSOpt_Lease)
{
return val;
}
-mDNSlocal const mDNSu8 *getOptRdata(const mDNSu8 *ptr, const mDNSu8 *limit, ResourceRecord *rr, mDNSu16 pktRDLen)
+mDNSlocal const mDNSu8 *getOptRdata(const mDNSu8 *ptr, const mDNSu8 *const limit, LargeCacheRecord *const cr, mDNSu16 pktRDLen)
{
int nread = 0;
- rdataOpt *opt;
-
- while (nread < pktRDLen)
+ ResourceRecord *const rr = &cr->r.resrec;
+ rdataOpt *opt = (rdataOpt *)rr->rdata->u.data;
+
+ while (nread < pktRDLen && (mDNSu8 *)opt < rr->rdata->u.data + MaximumRDSize - sizeof(rdataOpt))
{
- opt = (rdataOpt *)(rr->rdata->u.data + nread);
// space for opt + optlen
if (nread + (2 * sizeof(mDNSu16)) > rr->rdata->MaxRDLength) goto space_err;
opt->opt = getVal16(&ptr);
nread += 2 * sizeof(mDNSu16);
if (opt->opt == kDNSOpt_LLQ)
{
- if ((unsigned)(limit - ptr) < sizeof(LLQOptData)) goto space_err;
+ if ((unsigned)(limit - ptr) < LLQ_OPTLEN) goto space_err;
opt->OptData.llq.vers = getVal16(&ptr);
opt->OptData.llq.llqOp = getVal16(&ptr);
opt->OptData.llq.err = getVal16(&ptr);
if (opt->OptData.llq.lease > 0x70000000UL / mDNSPlatformOneSecond)
opt->OptData.llq.lease = 0x70000000UL / mDNSPlatformOneSecond;
ptr += sizeof(mDNSOpaque32);
- nread += sizeof(LLQOptData);
+ nread += LLQ_OPTLEN;
}
else if (opt->opt == kDNSOpt_Lease)
{
nread += sizeof(mDNSs32);
}
else { LogMsg("ERROR: getOptRdata - unknown opt %d", opt->opt); return mDNSNULL; }
+ opt++; // increment pointer into rdatabody
}
rr->rdlength = pktRDLen;
opt->RecordType = kDNSRecordTypeKnownUnique; // to avoid warnings in other layers
opt->rrtype = kDNSType_OPT;
- opt->rdlength = LEASE_OPT_SIZE;
- opt->rdestimate = LEASE_OPT_SIZE;
+ opt->rdlength = LEASE_OPT_RDLEN;
+ opt->rdestimate = LEASE_OPT_RDLEN;
optRD = &rr.resrec.rdata->u.opt;
optRD->opt = kDNSOpt_Lease;
target = GetRRDomainNameTarget(rr);
rr->rdlength = GetRDLength(rr, mDNSfalse);
rr->rdestimate = GetRDLength(rr, mDNStrue);
- rr->rdatahash = RDataHashValue(rr->rdlength, &rr->rdata->u);
- rr->rdnamehash = target ? DomainNameHashValue(target) : 0;
+ rr->rdatahash = target ? DomainNameHashValue(target) : RDataHashValue(rr->rdlength, &rr->rdata->u);
}
mDNSexport const mDNSu8 *skipDomainName(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *const end)
CacheRecord *rr = &largecr->r;
mDNSu16 pktrdlength;
- if (largecr == &m->rec && rr->resrec.RecordType)
- LogMsg("GetLargeResourceRecord: m->rec appears to be already in use");
+ if (largecr == &m->rec && largecr->r.resrec.RecordType)
+ LogMsg("GetLargeResourceRecord: m->rec appears to be already in use for %s", CRDisplayString(m, &largecr->r));
rr->next = mDNSNULL;
- rr->resrec.RecordType = RecordType;
rr->resrec.name = &largecr->namestorage;
rr->NextInKAList = mDNSNULL;
// us to look at. If we decide to copy it into the cache, then we'll update m->NextCacheCheck accordingly.
pktrdlength = (mDNSu16)((mDNSu16)ptr[8] << 8 | ptr[9]);
if (ptr[2] & (kDNSClass_UniqueRRSet >> 8))
- rr->resrec.RecordType |= kDNSRecordTypePacketUniqueMask;
+ RecordType |= kDNSRecordTypePacketUniqueMask;
ptr += 10;
if (ptr + pktrdlength > end) { debugf("GetResourceRecord: RDATA exceeds end of packet"); return(mDNSNULL); }
end = ptr + pktrdlength; // Adjust end to indicate the end of the rdata for this resource record
rr->resrec.rdata->u.soa.min = (mDNSu32) ((mDNSu32)ptr[0x10] << 24 | (mDNSu32)ptr[0x11] << 16 | (mDNSu32)ptr[0x12] << 8 | ptr[0x13]);
break;
- case kDNSType_OPT: getOptRdata(ptr, end, &rr->resrec, pktrdlength); break;
+ case kDNSType_OPT: getOptRdata(ptr, end, largecr, pktrdlength); break;
default: if (pktrdlength > rr->resrec.rdata->MaxRDLength)
{
rr->resrec.namehash = DomainNameHashValue(rr->resrec.name);
SetNewRData(&rr->resrec, mDNSNULL, 0);
+ // Success! Now fill in RecordType to show this record contains valid data
+ rr->resrec.RecordType = RecordType;
return(ptr + pktrdlength);
}
if (m->mDNS_busy == 0)
{
if (m->timenow)
- LogMsg("mDNS_Lock: m->timenow already set (%ld/%ld)", m->timenow, mDNSPlatformRawTime() + m->timenow_adjust);
- m->timenow = mDNSPlatformRawTime() + m->timenow_adjust;
+ LogMsg("mDNS_Lock: m->timenow already set (%ld/%ld)", m->timenow, mDNS_TimeNow_NoLock(m));
+ m->timenow = mDNS_TimeNow_NoLock(m);
if (m->timenow == 0) m->timenow = 1;
}
else if (m->timenow == 0)
{
LogMsg("mDNS_Lock: m->mDNS_busy is %ld but m->timenow not set", m->mDNS_busy);
- m->timenow = mDNSPlatformRawTime() + m->timenow_adjust;
+ m->timenow = mDNS_TimeNow_NoLock(m);
if (m->timenow == 0) m->timenow = 1;
}