xref: /titanic_52/usr/src/cmd/cmd-inet/usr.lib/mdnsd/anonymous.c (revision 5ffb0c9b03b5149ff4f5821a62be4a52408ada2a)
1 /* -*- Mode: C; tab-width: 4 -*-
2  *
3  * Copyright (c) 2012 Apple Computer, Inc. All rights reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #include "mDNSEmbeddedAPI.h"
19 #include "CryptoAlg.h"
20 #include "anonymous.h"
21 #include "DNSCommon.h"
22 
23 // Define ANONYMOUS_DISABLED to remove all the anonymous functionality
24 // and use the stub functions implemented later in this file.
25 
26 #ifndef ANONYMOUS_DISABLED
27 
28 #define ANON_NSEC3_ITERATIONS        1
29 
30 mDNSlocal mDNSBool InitializeNSEC3Record(ResourceRecord *rr, const mDNSu8 *AnonData, int len, mDNSu32 salt)
31 {
32     const mDNSu8 *ptr;
33     rdataNSEC3 *nsec3 = (rdataNSEC3 *)rr->rdata->u.data;
34     mDNSu8 *tmp, *nxt;
35     unsigned short iter = ANON_NSEC3_ITERATIONS;
36     int hlen;
37     const mDNSu8 hashName[NSEC3_MAX_HASH_LEN];
38 
39     // Construct the RDATA first and construct the owner name based on that.
40     ptr = (const mDNSu8 *)&salt;
41     debugf("InitializeNSEC3Record: %x%x%x%x, name %##s", ptr[0], ptr[1], ptr[2], ptr[3], rr->name->c);
42 
43     // Set the RDATA
44     nsec3->alg = SHA1_DIGEST_TYPE;
45     nsec3->flags = 0;
46     nsec3->iterations = swap16(iter);
47     nsec3->saltLength = 4;
48     tmp = (mDNSu8 *)&nsec3->salt;
49     *tmp++ = ptr[0];
50     *tmp++ = ptr[1];
51     *tmp++ = ptr[2];
52     *tmp++ = ptr[3];
53 
54     // hashLength, nxt, bitmap
55     *tmp++ = SHA1_HASH_LENGTH;    // hash length
56     nxt = tmp;
57     tmp += SHA1_HASH_LENGTH;
58     *tmp++ = 0; // window number
59     *tmp++ = NSEC_MCAST_WINDOW_SIZE; // window length
60     mDNSPlatformMemZero(tmp, NSEC_MCAST_WINDOW_SIZE);
61     tmp[kDNSType_PTR >> 3] |= 128 >> (kDNSType_PTR & 7);
62 
63     // Hash the base service name + salt + AnonData
64     if (!NSEC3HashName(rr->name, nsec3, AnonData, len, hashName, &hlen))
65     {
66         LogMsg("InitializeNSEC3Record: NSEC3HashName failed for ##s", rr->name->c);
67         return mDNSfalse;
68     }
69     if (hlen != SHA1_HASH_LENGTH)
70     {
71         LogMsg("InitializeNSEC3Record: hlen wrong %d", hlen);
72         return mDNSfalse;
73     }
74     mDNSPlatformMemCopy(nxt, hashName, hlen);
75 
76     return mDNStrue;
77 }
78 
79 mDNSlocal ResourceRecord *ConstructNSEC3Record(const domainname *service, const mDNSu8 *AnonData, int len, mDNSu32 salt)
80 {
81     ResourceRecord *rr;
82     int dlen;
83     domainname *name;
84 
85     // We are just allocating an RData which has StandardAuthRDSize
86     if (StandardAuthRDSize < MCAST_NSEC3_RDLENGTH)
87     {
88         LogMsg("ConstructNSEC3Record: StandardAuthRDSize %d smaller than MCAST_NSEC3_RDLENGTH %d", StandardAuthRDSize, MCAST_NSEC3_RDLENGTH);
89         return mDNSNULL;
90     }
91 
92     dlen = DomainNameLength(service);
93 
94     // Allocate space for the name and RData.
95     rr = mDNSPlatformMemAllocate(sizeof(ResourceRecord) + dlen + sizeof(RData));
96     if (!rr)
97         return mDNSNULL;
98     name = (domainname *)((mDNSu8 *)rr + sizeof(ResourceRecord));
99     rr->RecordType        = kDNSRecordTypePacketAuth;
100     rr->InterfaceID       = mDNSInterface_Any;
101     rr->name              = (const domainname *)name;
102     rr->rrtype            = kDNSType_NSEC3;
103     rr->rrclass           = kDNSClass_IN;
104     rr->rroriginalttl     = kStandardTTL;
105     rr->rDNSServer        = mDNSNULL;
106     rr->rdlength          = MCAST_NSEC3_RDLENGTH;
107     rr->rdestimate        = MCAST_NSEC3_RDLENGTH;
108     rr->rdata             = (RData *)((mDNSu8 *)rr->name + dlen);
109 
110     AssignDomainName(name, service);
111     if (!InitializeNSEC3Record(rr, AnonData, len, salt))
112     {
113         mDNSPlatformMemFree(rr);
114         return mDNSNULL;
115     }
116     return rr;
117 }
118 
119 mDNSlocal ResourceRecord *CopyNSEC3ResourceRecord(AnonymousInfo *si, const ResourceRecord *rr)
120 {
121     int len;
122     domainname *name;
123     ResourceRecord *nsec3rr;
124 
125     if (rr->rdlength < MCAST_NSEC3_RDLENGTH)
126     {
127         LogMsg("CopyNSEC3ResourceRecord: rdlength %d smaller than MCAST_NSEC3_RDLENGTH %d", rr->rdlength, MCAST_NSEC3_RDLENGTH);
128         return mDNSNULL;
129     }
130     // Allocate space for the name and the rdata along with the ResourceRecord
131     len = DomainNameLength(rr->name);
132     nsec3rr = mDNSPlatformMemAllocate(sizeof(ResourceRecord) + len + sizeof(RData));
133     if (!nsec3rr)
134         return mDNSNULL;
135 
136     *nsec3rr = *rr;
137     name = (domainname *)((mDNSu8 *)nsec3rr + sizeof(ResourceRecord));
138     nsec3rr->name = (const domainname *)name;
139     AssignDomainName(name, rr->name);
140 
141     nsec3rr->rdata = (RData *)((mDNSu8 *)nsec3rr->name + len);
142     mDNSPlatformMemCopy(nsec3rr->rdata->u.data, rr->rdata->u.data, rr->rdlength);
143 
144     si->nsec3RR = nsec3rr;
145 
146     return nsec3rr;
147 }
148 
149 // When a service is started or a browse is started with the Anonymous data, we allocate a new random
150 // number and based on that allocate a new NSEC3 resource record whose hash is a function of random number (salt) and
151 // the anonymous data.
152 //
153 // If we receive a packet with the NSEC3 option, we need to cache that along with the resource record so that we can
154 // check against the question to see whether it answers them or not. In that case, we pass the "rr" that we received.
155 mDNSexport  AnonymousInfo *AllocateAnonInfo(const domainname *service, const mDNSu8 *data, int len, const ResourceRecord *rr)
156 {
157     AnonymousInfo *ai;
158     ai = (AnonymousInfo *)mDNSPlatformMemAllocate(sizeof(AnonymousInfo));
159     if (!ai)
160     {
161         return mDNSNULL;
162     }
163     mDNSPlatformMemZero(ai, sizeof(AnonymousInfo));
164     if (rr)
165     {
166         if (!CopyNSEC3ResourceRecord(ai, rr))
167         {
168             mDNSPlatformMemFree(ai);
169             return mDNSNULL;
170         }
171         return ai;
172     }
173     ai->salt = mDNSRandom(0xFFFFFFFF);
174     ai->AnonData = mDNSPlatformMemAllocate(len);
175     if (!ai->AnonData)
176     {
177         mDNSPlatformMemFree(ai);
178         return mDNSNULL;
179     }
180     ai->AnonDataLen = len;
181     mDNSPlatformMemCopy(ai->AnonData, data, len);
182     ai->nsec3RR = ConstructNSEC3Record(service, data, len, ai->salt);
183     if (!ai->nsec3RR)
184     {
185         mDNSPlatformMemFree(ai);
186         return mDNSNULL;
187     }
188     return ai;
189 }
190 
191 mDNSexport void FreeAnonInfo(AnonymousInfo *ai)
192 {
193     if (ai->nsec3RR)
194         mDNSPlatformMemFree(ai->nsec3RR);
195     if (ai->AnonData)
196         mDNSPlatformMemFree(ai->AnonData);
197     mDNSPlatformMemFree(ai);
198 }
199 
200 mDNSexport void ReInitAnonInfo(AnonymousInfo **AnonInfo, const domainname *name)
201 {
202     if (*AnonInfo)
203     {
204         AnonymousInfo *ai = *AnonInfo;
205         *AnonInfo = AllocateAnonInfo(name, ai->AnonData, ai->AnonDataLen, mDNSNULL);
206         if (!(*AnonInfo))
207             *AnonInfo = ai;
208         else
209             FreeAnonInfo(ai);
210     }
211 }
212 
213 // This function should be used only if you know that the question and
214 // the resource record belongs to the same set. The main usage is
215 // in ProcessQuery where we find the question to be part of the same
216 // set as the resource record, but it needs the AnonData to be
217 // initialized so that it can walk the cache records to see if they
218 // answer the question.
219 mDNSexport void SetAnonData(DNSQuestion *q, ResourceRecord *rr, mDNSBool ForQuestion)
220 {
221     if (!q->AnonInfo || !rr->AnonInfo)
222     {
223         LogMsg("SetAnonData: question %##s(%p), rr %##s(%p), NULL", q->qname.c, q->AnonInfo, rr->name->c, rr->AnonInfo);
224         return;
225     }
226 
227     debugf("SetAnonData: question %##s(%p), rr %##s(%p)", q->qname.c, q->AnonInfo, rr->name->c, rr->AnonInfo);
228     if (ForQuestion)
229     {
230         if (!q->AnonInfo->AnonData)
231         {
232             q->AnonInfo->AnonData = mDNSPlatformMemAllocate(rr->AnonInfo->AnonDataLen);
233             if (!q->AnonInfo->AnonData)
234                 return;
235         }
236         mDNSPlatformMemCopy(q->AnonInfo->AnonData, rr->AnonInfo->AnonData, rr->AnonInfo->AnonDataLen);
237         q->AnonInfo->AnonDataLen = rr->AnonInfo->AnonDataLen;
238     }
239     else
240     {
241         if (!rr->AnonInfo->AnonData)
242         {
243             rr->AnonInfo->AnonData = mDNSPlatformMemAllocate(q->AnonInfo->AnonDataLen);
244             if (!rr->AnonInfo->AnonData)
245                 return;
246         }
247         mDNSPlatformMemCopy(rr->AnonInfo->AnonData, q->AnonInfo->AnonData, q->AnonInfo->AnonDataLen);
248         rr->AnonInfo->AnonDataLen = q->AnonInfo->AnonDataLen;
249     }
250 }
251 
252 // returns -1 if the caller should ignore the result
253 // returns 1 if the record answers the question
254 // returns 0 if the record does not answer the question
255 mDNSexport int AnonInfoAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q)
256 {
257     mDNSexport mDNS mDNSStorage;
258     ResourceRecord *nsec3RR;
259     int i;
260     AnonymousInfo *qai, *rai;
261     mDNSu8 *AnonData;
262     int AnonDataLen;
263     rdataNSEC3 *nsec3;
264     int hlen;
265     const mDNSu8 hashName[NSEC3_MAX_HASH_LEN];
266     int nxtLength;
267     mDNSu8 *nxtName;
268 
269     debugf("AnonInfoAnswersQuestion: question qname %##s", q->qname.c);
270 
271     // Currently only PTR records can have anonymous information
272     if (q->qtype != kDNSType_PTR)
273     {
274         return -1;
275     }
276 
277     // We allow anonymous questions to be answered by both normal services (without the
278     // anonymous information) and anonymous services that are part of the same set. And
279     // normal questions discover normal services and all anonymous services.
280     //
281     // The three cases have been enumerated clearly even though they all behave the
282     // same way.
283     if (!q->AnonInfo)
284     {
285         debugf("AnonInfoAnswersQuestion: not a anonymous type question");
286         if (!rr->AnonInfo)
287         {
288             // case 1
289             return -1;
290         }
291         else
292         {
293             // case 2
294             debugf("AnonInfoAnswersQuestion: Question %##s not answered using anonymous record %##s", q->qname.c, rr->name->c);
295             return -1;
296         }
297     }
298     else
299     {
300         // case 3
301         if (!rr->AnonInfo)
302         {
303             debugf("AnonInfoAnswersQuestion: not a anonymous type record");
304             return -1;
305         }
306     }
307 
308     // case 4: We have the anonymous information both in the question and the record. We need
309     // two sets of information to validate.
310     //
311     // 1) Anonymous data that identifies the set/group
312     // 2) NSEC3 record that contains the hash and the salt
313     //
314     // If the question is a remote one, it does not have the anonymous information to validate (just
315     // the NSEC3 record) and hence the anonymous data should come from the local resource record. If the
316     // question is local, it can come from either of them and if there is a mismatch between the
317     // question and record, it won't validate.
318 
319     qai = q->AnonInfo;
320     rai = rr->AnonInfo;
321 
322     if (qai->AnonData && rai->AnonData)
323     {
324         // Before a cache record is created, if there is a matching question i.e., part
325         // of the same set, then when the cache is created we also set the anonymous
326         // information. Otherwise, the cache record contains just the NSEC3 record and we
327         // won't be here for that case.
328         //
329         // It is also possible that a local question is matched against the local AuthRecord
330         // as that is also the case for which the AnonData would be non-NULL for both.
331         // We match questions against AuthRecords (rather than the cache) for LocalOnly case and
332         // to see whether a .local query should be suppressed or not. The latter never happens
333         // because PTR queries are never suppressed.
334 
335         // If they don't belong to the same anonymous set, then no point in validating.
336         if ((qai->AnonDataLen != rai->AnonDataLen) ||
337             mDNSPlatformMemCmp(qai->AnonData, rai->AnonData, qai->AnonDataLen) != 0)
338         {
339             debugf("AnonInfoAnswersQuestion: AnonData mis-match for record  %s question %##s ",
340                 RRDisplayString(&mDNSStorage, rr), q->qname.c);
341             return 0;
342         }
343         // AnonData matches i.e they belong to the same group and the same service.
344         LogInfo("AnonInfoAnswersQuestion: Answering qname %##s, rname %##s, without validation", q->qname.c,
345             rr->name->c);
346         return 1;
347     }
348     else
349     {
350         debugf("AnonInfoAnswersQuestion: question %p, record %p", qai->AnonData, rai->AnonData);
351     }
352 
353     if (qai->AnonData)
354     {
355         // If there is AnonData, then this is a local question. The
356         // NSEC3 RR comes from the resource record which could be part
357         // of the cache or local auth record. The cache entry could
358         // be from a remote host or created when we heard our own
359         // announcements. In any case, we use that to see if it matches
360         // the question.
361         AnonData = qai->AnonData;
362         AnonDataLen = qai->AnonDataLen;
363         nsec3RR = rai->nsec3RR;
364     }
365     else
366     {
367         // Remote question or hearing our own question back
368         AnonData = rai->AnonData;
369         AnonDataLen = rai->AnonDataLen;
370         nsec3RR = qai->nsec3RR;
371     }
372 
373     if (!AnonData || !nsec3RR)
374     {
375         // AnonData can be NULL for the cache entry and if we are hearing our own question back, AnonData is NULL for
376         // that too and we can end up here for that case.
377         debugf("AnonInfoAnswersQuestion: AnonData %p or nsec3RR %p, NULL for question %##s, record %s", AnonData, nsec3RR,
378             q->qname.c, RRDisplayString(&mDNSStorage, rr));
379         return 0;
380     }
381     debugf("AnonInfoAnswersQuestion: Validating question %##s, ResourceRecord %s", q->qname.c, RRDisplayString(&mDNSStorage, nsec3RR));
382 
383 
384     nsec3 = (rdataNSEC3 *)nsec3RR->rdata->u.data;
385 
386     if (!NSEC3HashName(nsec3RR->name, nsec3, AnonData, AnonDataLen, hashName, &hlen))
387     {
388         LogMsg("AnonInfoAnswersQuestion: NSEC3HashName failed for ##s", nsec3RR->name->c);
389         return mDNSfalse;
390     }
391     if (hlen != SHA1_HASH_LENGTH)
392     {
393         LogMsg("AnonInfoAnswersQuestion: hlen wrong %d", hlen);
394         return mDNSfalse;
395     }
396 
397     NSEC3Parse(nsec3RR, mDNSNULL, &nxtLength, &nxtName, mDNSNULL, mDNSNULL);
398 
399     if (hlen != nxtLength)
400     {
401         LogMsg("AnonInfoAnswersQuestion: ERROR!! hlen %d not same as nxtLength %d", hlen, nxtLength);
402         return mDNSfalse;
403     }
404 
405     for (i = 0; i < nxtLength; i++)
406     {
407         if (nxtName[i] != hashName[i])
408         {
409             debugf("AnonInfoAnswersQuestion: mismatch output %x, digest %x, i %d", nxtName[i+1], hashName[i], i);
410             return 0;
411         }
412     }
413     LogInfo("AnonInfoAnswersQuestion: ResourceRecord %s matched question %##s (%s)", RRDisplayString(&mDNSStorage, nsec3RR), q->qname.c, DNSTypeName(q->qtype));
414     return 1;
415 }
416 
417 // Find a matching NSEC3 record for the name. We parse the questions and the records in the packet in order.
418 // Similarly we also parse the NSEC3 records in order and this mapping to the questions and records
419 // respectively.
420 mDNSlocal CacheRecord *FindMatchingNSEC3ForName(mDNS *const m, CacheRecord **nsec3, const domainname *name)
421 {
422     CacheRecord *cr;
423     CacheRecord **prev = nsec3;
424 
425     (void) m;
426 
427     for (cr = *nsec3; cr; cr = cr->next)
428     {
429         if (SameDomainName(cr->resrec.name, name))
430         {
431             debugf("FindMatchingNSEC3ForName: NSEC3 record %s matched %##s", CRDisplayString(m, cr), name->c);
432             *prev = cr->next;
433             cr->next = mDNSNULL;
434             return cr;
435         }
436         prev = &cr->next;
437     }
438     return mDNSNULL;
439 }
440 
441 mDNSexport void InitializeAnonInfoForQuestion(mDNS *const m, CacheRecord **McastNSEC3Records, DNSQuestion *q)
442 {
443     CacheRecord *nsec3CR;
444 
445     if (q->qtype != kDNSType_PTR)
446         return;
447 
448     nsec3CR = FindMatchingNSEC3ForName(m, McastNSEC3Records, &q->qname);
449     if (nsec3CR)
450     {
451         q->AnonInfo = AllocateAnonInfo(mDNSNULL, mDNSNULL, 0, &nsec3CR->resrec);
452         if (q->AnonInfo)
453         {
454             debugf("InitializeAnonInfoForQuestion: Found a matching NSEC3 record %s, for %##s (%s)",
455                 RRDisplayString(m, q->AnonInfo->nsec3RR), q->qname.c, DNSTypeName(q->qtype));
456         }
457         ReleaseCacheRecord(m, nsec3CR);
458     }
459 }
460 
461 mDNSexport void InitializeAnonInfoForCR(mDNS *const m, CacheRecord **McastNSEC3Records, CacheRecord *cr)
462 {
463     CacheRecord *nsec3CR;
464 
465     if (!(*McastNSEC3Records))
466         return;
467 
468     // If already initialized or not a PTR type, we don't have to do anything
469     if (cr->resrec.AnonInfo || cr->resrec.rrtype != kDNSType_PTR)
470         return;
471 
472     nsec3CR = FindMatchingNSEC3ForName(m, McastNSEC3Records, cr->resrec.name);
473     if (nsec3CR)
474     {
475         cr->resrec.AnonInfo = AllocateAnonInfo(mDNSNULL, mDNSNULL, 0, &nsec3CR->resrec);
476         if (cr->resrec.AnonInfo)
477         {
478             debugf("InitializeAnonInfoForCR: Found a matching NSEC3 record %s, for %##s (%s)",
479                 RRDisplayString(m, cr->resrec.AnonInfo->nsec3RR), cr->resrec.name->c,
480                 DNSTypeName(cr->resrec.rrtype));
481         }
482         ReleaseCacheRecord(m, nsec3CR);
483     }
484 }
485 
486 mDNSexport mDNSBool IdenticalAnonInfo(AnonymousInfo *a1, AnonymousInfo *a2)
487 {
488     // if a1 is NULL and a2 is not NULL AND vice-versa
489     // return false as there is a change.
490     if ((a1 != mDNSNULL) != (a2 != mDNSNULL))
491         return mDNSfalse;
492 
493     // Both could be NULL or non-NULL
494     if (a1 && a2)
495     {
496         // The caller already verified that the owner name is the same.
497         // Check whether the RData is same.
498         if (!IdenticalSameNameRecord(a1->nsec3RR, a2->nsec3RR))
499         {
500             debugf("IdenticalAnonInfo: nsec3RR mismatch");
501             return mDNSfalse;
502         }
503     }
504     return mDNStrue;
505 }
506 
507 mDNSexport void CopyAnonInfoForCR(mDNS *const m, CacheRecord *crto, CacheRecord *crfrom)
508 {
509     AnonymousInfo *aifrom = crfrom->resrec.AnonInfo;
510     AnonymousInfo *aito = crto->resrec.AnonInfo;
511 
512     (void) m;
513 
514     if (!aifrom)
515         return;
516 
517     if (aito)
518     {
519         crto->resrec.AnonInfo = aifrom;
520         FreeAnonInfo(aito);
521         crfrom->resrec.AnonInfo = mDNSNULL;
522     }
523     else
524     {
525         FreeAnonInfo(aifrom);
526         crfrom->resrec.AnonInfo = mDNSNULL;
527     }
528 }
529 
530 #else // !ANONYMOUS_DISABLED
531 
532 mDNSexport void ReInitAnonInfo(AnonymousInfo **si, const domainname *name)
533 {
534 	(void)si;
535 	(void)name;
536 }
537 
538 mDNSexport AnonymousInfo * AllocateAnonInfo(const domainname *service, const mDNSu8 *AnonData, int len, const ResourceRecord *rr)
539 {
540 	(void)service;
541 	(void)AnonData;
542 	(void)len;
543 	(void)rr;
544 
545 	return mDNSNULL;
546 }
547 
548 mDNSexport void FreeAnonInfo(AnonymousInfo *ai)
549 {
550 	(void)ai;
551 }
552 
553 mDNSexport void SetAnonData(DNSQuestion *q, ResourceRecord *rr, mDNSBool ForQuestion)
554 {
555 	(void)q;
556 	(void)rr;
557 	(void)ForQuestion;
558 }
559 
560 mDNSexport int AnonInfoAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q)
561 {
562 	(void)rr;
563 	(void)q;
564 
565 	return mDNSfalse;
566 }
567 
568 mDNSexport void InitializeAnonInfoForQuestion(mDNS *const m, CacheRecord **McastNSEC3Records, DNSQuestion *q)
569 {
570 	(void)m;
571 	(void)McastNSEC3Records;
572 	(void)q;
573 }
574 
575 mDNSexport void InitializeAnonInfoForCR(mDNS *const m, CacheRecord **McastNSEC3Records, CacheRecord *cr)
576 {
577 	(void)m;
578 	(void)McastNSEC3Records;
579 	(void)cr;
580 }
581 
582 mDNSexport void CopyAnonInfoForCR(mDNS *const m, CacheRecord *crto, CacheRecord *crfrom)
583 {
584 	(void)m;
585 	(void)crto;
586 	(void)crfrom;
587 }
588 
589 mDNSexport mDNSBool IdenticalAnonInfo(AnonymousInfo *a1, AnonymousInfo *a2)
590 {
591 	(void)a1;
592 	(void)a2;
593 
594 	return mDNStrue;
595 }
596 
597 #endif // !ANONYMOUS_DISABLED
598