1/*
2 * Copyright (c) 2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/* NOTE:  This code is a *copy* of code that exists in SMB, AFP, ScreenSharing and CFNetwork.
25 * These routines are not exported and therefore have to be copied.
26 * This code should be removed when realy API / SPI exists
27 */
28
29#include <dns_sd.h>
30#include <CoreFoundation/CoreFoundation.h>
31
32#define MAX_DOMAIN_LABEL 63
33#define MAX_DOMAIN_NAME 255
34#define MAX_ESCAPED_DOMAIN_NAME 1005
35
36typedef struct { UInt8 c[ 64]; } domainlabel;   // One label: length byte and up to 63 characters
37typedef struct { UInt8 c[256]; } domainname;    // Up to 255 bytes of length-prefixed domainlabels
38
39
40// Returns length of a domain name INCLUDING the byte for the final null label
41// e.g. for the root label "." it returns one
42// For the FQDN "com." it returns 5 (length byte, three data bytes, final zero)
43// Legal results are 1 (just root label) to 255 (MAX_DOMAIN_NAME)
44// If the given domainname is invalid, result is 256 (MAX_DOMAIN_NAME+1)
45static UInt16 DomainNameLengthLimit(const domainname *const name, const UInt8 *limit) {
46    const UInt8 *src = name->c;
47    while (src < limit && *src <= MAX_DOMAIN_LABEL) {
48	if (*src == 0) return((UInt16)(src - name->c + 1));
49	src += 1 + *src;
50    }
51    return(MAX_DOMAIN_NAME+1);
52}
53
54#define DomainNameLength(name) DomainNameLengthLimit((name), (name)->c + MAX_DOMAIN_NAME + 1)
55#define mdnsIsDigit(X)     ((X) >= '0' && (X) <= '9')
56
57// AppendDNSNameString appends zero or more labels to an existing (possibly empty) domainname.
58// The C string is in conventional DNS syntax:
59// Textual labels, escaped as necessary using the usual DNS '\' notation, separated by dots.
60// If successful, AppendDNSNameString returns a pointer to the next unused byte
61// in the domainname bufer (i.e. the next byte after the terminating zero).
62// If unable to construct a legal domain name (i.e. label more than 63 bytes, or total more than 255 bytes)
63// AppendDNSNameString returns NULL.
64static UInt8 *AppendDNSNameString(domainname *const name, const char *cstring) {
65    const char *cstr = cstring;
66    UInt8 *ptr = name->c + DomainNameLength(name) - 1;              // Find end of current name
67    const UInt8 *const lim = name->c + MAX_DOMAIN_NAME - 1;         // Limit of how much we can add (not counting final zero)
68    while (*cstr && ptr < lim)                                      // While more characters, and space to put them...
69	    {
70	    UInt8 *lengthbyte = ptr++;                              // Record where the length is going to go
71	    if (*cstr == '.') { /* fprintf(stderr, "AppendDNSNameString: Illegal empty label in name \"%s\"", cstring); */ return(NULL); }
72	    while (*cstr && *cstr != '.' && ptr < lim)              // While we have characters in the label...
73		    {
74		    UInt8 c = (UInt8)*cstr++;                       // Read the character
75		    if (c == '\\')                                  // If escape character, check next character
76			    {
77			    c = (UInt8)*cstr++;                     // Assume we'll just take the next character
78			    if (mdnsIsDigit(c) && mdnsIsDigit(cstr[0]) && mdnsIsDigit(cstr[1])) {  // If three decimal digits,
79				    int v0 = c        - '0';        // then interpret as three-digit decimal
80				    int v1 = cstr[ 0] - '0';
81				    int v2 = cstr[ 1] - '0';
82				    int val = v0 * 100 + v1 * 10 + v2;
83				    if (val <= 255) { c = (UInt8)val; cstr += 2; }	// If valid three-digit decimal value, use it
84				    }
85			    }
86		    *ptr++ = c;                                     // Write the character
87		    }
88	    if (*cstr) cstr++;                                      // Skip over the trailing dot (if present)
89	    if (ptr - lengthbyte - 1 > MAX_DOMAIN_LABEL)            // If illegal label, abort
90		    return(NULL);
91	    *lengthbyte = (UInt8)(ptr - lengthbyte - 1);            // Fill in the length byte
92	    }
93
94    *ptr++ = 0;                                                     // Put the null root label on the end
95    if (*cstr) return(NULL);                                        // Failure: We didn't successfully consume all input
96    else return(ptr);                                               // Success: return new value of ptr
97}
98
99
100static char *ConvertDomainLabelToCString_withescape(const domainlabel *const label, char *ptr, char esc) {
101    const UInt8 *src = label->c;                             // Domain label we're reading
102    const UInt8 len = *src++;                                // Read length of this (non-null) label
103    const UInt8 *const end = src + len;                      // Work out where the label ends
104    if (len > MAX_DOMAIN_LABEL) return(NULL);                // If illegal label, abort
105    while (src < end) {                                      // While we have characters in the label
106	UInt8 c = *src++;
107	if (esc) {
108	    if (c == '.' || c == esc)                        // If character is a dot or the escape character
109		*ptr++ = esc;                                // Output escape character
110	    else if (c <= ' ') {                             // If non-printing ascii,
111                                                             // Output decimal escape sequence
112		*ptr++ = esc;
113		*ptr++ = (char)  ('0' + (c / 100)     );
114		*ptr++ = (char)  ('0' + (c /  10) % 10);
115		c      = (UInt8)('0' + (c      ) % 10);
116	    }
117	}
118	*ptr++ = (char)c;                                    // Copy the character
119    }
120    *ptr = 0;                                                // Null-terminate the string
121    return(ptr);                                             // and return
122}
123
124// Note: To guarantee that there will be no possible overrun, cstr must be at least MAX_ESCAPED_DOMAIN_NAME (1005 bytes)
125static char *ConvertDomainNameToCString_withescape(const domainname *const name, char *ptr, char esc) {
126    const UInt8 *src = name->c;                              // Domain name we're reading
127    const UInt8 *const max = name->c + MAX_DOMAIN_NAME;      // Maximum that's valid
128
129    if (*src == 0) *ptr++ = '.';                             // Special case: For root, just write a dot
130
131    while (*src) {                                           // While more characters in the domain name
132	if (src + 1 + *src >= max) return(NULL);
133	ptr = ConvertDomainLabelToCString_withescape((const domainlabel *)src, ptr, esc);
134	if (!ptr) return(NULL);
135	src += 1 + *src;
136	*ptr++ = '.';                                        // Write the dot after the label
137    }
138
139    *ptr++ = 0;                                              // Null-terminate the string
140    return(ptr);                                             // and return
141}
142
143
144#define ConvertDomainLabelToCString_unescaped(D,C)  ConvertDomainLabelToCString_withescape((D), (C), 0)
145#define ConvertDomainLabelToCString(D,C)            ConvertDomainLabelToCString_withescape((D), (C), '\\')
146#define ConvertDomainNameToCString_unescaped(D,C)   ConvertDomainNameToCString_withescape((D), (C), 0)
147#define ConvertDomainNameToCString(D,C)             ConvertDomainNameToCString_withescape((D), (C), '\\')
148
149
150// MakeDomainNameFromDNSNameString makes a native DNS-format domainname from a C string.
151// The C string is in conventional DNS syntax:
152// Textual labels, escaped as necessary using the usual DNS '\' notation, separated by dots.
153// If successful, MakeDomainNameFromDNSNameString returns a pointer to the next unused byte
154// in the domainname bufer (i.e. the next byte after the terminating zero).
155// If unable to construct a legal domain name (i.e. label more than 63 bytes, or total more than 255 bytes)
156// MakeDomainNameFromDNSNameString returns NULL.
157static UInt8 *MakeDomainNameFromDNSNameString(domainname *const name, const char *cstr) {
158    name->c[0] = 0;                                   // Make an empty domain name
159    return(AppendDNSNameString(name, cstr));          // And then add this string to it
160}
161
162
163#define ValidTransportProtocol(X) ( (X)[0] == 4 && (X)[1] == '_' && \
164	((((X)[2] | 0x20) == 'u' && ((X)[3] | 0x20) == 'd') || (((X)[2] | 0x20) == 't' && ((X)[3] | 0x20) == 'c')) && \
165	((X)[4] | 0x20) == 'p')
166
167// A service name has the form: instance.application-protocol.transport-protocol.domain
168// DeconstructServiceName is currently fairly forgiving: It doesn't try to enforce character
169// set or length limits for the protocol names, and the final domain is allowed to be empty.
170// However, if the given FQDN doesn't contain at least three labels,
171// DeconstructServiceName will reject it and return false.
172static Boolean DeconstructServiceName(const domainname *const fqdn, domainlabel *const name, domainname *const type, domainname *const domain) {
173    int i, len;
174    const UInt8 *src = fqdn->c;
175    const UInt8 *max = fqdn->c + MAX_DOMAIN_NAME;
176    UInt8 *dst;
177
178    dst = name->c;    // Extract the service name
179    len = *src;
180    if (!len)                         { /*fprintf(stderr, "DeconstructServiceName: FQDN empty!");*/                               return(false); }
181    if (len > MAX_DOMAIN_LABEL)       { /*fprintf(stderr, "DeconstructServiceName: Instance name too long");*/                    return(false); }
182    for (i=0; i<=len; i++) *dst++ = *src++;
183
184    dst = type->c;    // Extract the service type
185    len = *src;
186    if (!len)                         { /*fprintf(stderr, "DeconstructServiceName: FQDN contains only one label!");*/             return(false); }
187    if (len > MAX_DOMAIN_LABEL)       { /*fprintf(stderr, "DeconstructServiceName: Application protocol name too long");*/        return(false); }
188    if (src[1] != '_')                { /*fprintf(stderr, "DeconstructServiceName: No _ at start of application protocol");*/     return(false); }
189    for (i=0; i<=len; i++) *dst++ = *src++;
190
191    len = *src;
192    if (!len)                          { /*fprintf(stderr, "DeconstructServiceName: FQDN contains only two labels!");*/           return(false); }
193    if (!ValidTransportProtocol(src))  { /*fprintf(stderr, "DeconstructServiceName: Transport protocol must be _udp or _tcp");*/  return(false); }
194    for (i=0; i<=len; i++) *dst++ = *src++;
195    *dst++ = 0;       // Put terminator on the end of service type
196
197    dst = domain->c;  // Extract the service domain
198    while (*src) {
199	len = *src;
200	if (len > MAX_DOMAIN_LABEL)    { /*fprintf(stderr, "DeconstructServiceName: Label in service domain too long");*/         return(false); }
201	if (src + 1 + len + 1 >= max)  { /*fprintf(stderr, "DeconstructServiceName: Total service domain too long");*/            return(false); }
202	for (i=0; i<=len; i++) *dst++ = *src++;
203    }
204    *dst++ = 0;      // Put the null root label on the end
205
206    return(true);
207}
208
209
210static void mDNSServiceCallBack(
211								  DNSServiceRef serviceRef,
212								  DNSServiceFlags flags,
213								  uint32_t interface,
214								  DNSServiceErrorType errorCode,
215								  const char *fullname,
216								  const char *hostTarget,
217								  uint16_t	port,
218								  uint16_t	txtlen,
219								  const unsigned char *txtRecord,
220								  void *ctx
221								  )
222{
223	char **hostname = (char **)ctx;
224
225	if (errorCode == kDNSServiceErr_NoError && hostname) {
226		*hostname = strdup (hostTarget);
227	}
228}
229
230/*
231 * Some glue logic specifically for KerberosHelper, based on _CFNetServiceDeconstructServiceName
232 */
233
234Boolean
235_CFNetServiceDeconstructServiceName(CFStringRef inHostName, char **inHostNameString)
236{
237
238	Boolean result = false;
239	char serviceNameStr[MAX_ESCAPED_DOMAIN_NAME];
240	DNSServiceRef serviceRef = NULL;
241
242	if (CFStringGetCString(inHostName, serviceNameStr, MAX_ESCAPED_DOMAIN_NAME, kCFStringEncodingUTF8)) {
243		domainname domainName;
244
245		if (MakeDomainNameFromDNSNameString(&domainName, serviceNameStr)) {
246			domainlabel nameLabel;
247			domainname typeDomain;
248			domainname domainDomain;
249
250			if (DeconstructServiceName(&domainName, &nameLabel, &typeDomain, &domainDomain)) {
251				char namestr   [MAX_DOMAIN_LABEL+1];
252				char typestr   [MAX_ESCAPED_DOMAIN_NAME];
253				char domainstr [MAX_ESCAPED_DOMAIN_NAME];
254				DNSServiceErrorType error;
255
256				ConvertDomainLabelToCString_unescaped(&nameLabel, namestr);
257				ConvertDomainNameToCString(&typeDomain, typestr);
258				ConvertDomainNameToCString(&domainDomain, domainstr);
259
260				error = DNSServiceResolve (&serviceRef,
261										   0,	// No flags
262										   0,	// All network interfaces
263										   namestr,
264										   typestr,
265										   domainstr,	// domain
266										   (DNSServiceResolveReply) mDNSServiceCallBack,
267										   inHostNameString);
268
269				if (kDNSServiceErr_NoError != error) {
270					goto Error;
271				}
272
273				error = DNSServiceProcessResult(serviceRef);
274				if (kDNSServiceErr_NoError != error) {
275					goto Error;
276				}
277
278				result = true;
279			}
280		}
281	}
282Error:
283	if (serviceRef) { DNSServiceRefDeallocate(serviceRef); }
284
285	return result;
286}
287