xref: /illumos-gate/usr/src/common/smbsrv/smb_netbios_util.c (revision bbf6f00c25b6a2bed23c35eac6d62998ecdb338c)
1da6c28aaSamw /*
2da6c28aaSamw  * CDDL HEADER START
3da6c28aaSamw  *
4da6c28aaSamw  * The contents of this file are subject to the terms of the
5da6c28aaSamw  * Common Development and Distribution License (the "License").
6da6c28aaSamw  * You may not use this file except in compliance with the License.
7da6c28aaSamw  *
8da6c28aaSamw  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9da6c28aaSamw  * or http://www.opensolaris.org/os/licensing.
10da6c28aaSamw  * See the License for the specific language governing permissions
11da6c28aaSamw  * and limitations under the License.
12da6c28aaSamw  *
13da6c28aaSamw  * When distributing Covered Code, include this CDDL HEADER in each
14da6c28aaSamw  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15da6c28aaSamw  * If applicable, add the following below this CDDL HEADER, with the
16da6c28aaSamw  * fields enclosed by brackets "[]" replaced with your own identifying
17da6c28aaSamw  * information: Portions Copyright [yyyy] [name of copyright owner]
18da6c28aaSamw  *
19da6c28aaSamw  * CDDL HEADER END
20da6c28aaSamw  */
21da6c28aaSamw /*
22*bbf6f00cSJordan Brown  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23da6c28aaSamw  * Use is subject to license terms.
24da6c28aaSamw  */
25da6c28aaSamw 
26da6c28aaSamw #ifdef _KERNEL
27da6c28aaSamw #include <sys/types.h>
28da6c28aaSamw #include <sys/sunddi.h>
29da6c28aaSamw #else
30da6c28aaSamw #include <string.h>
31da6c28aaSamw #endif
32da6c28aaSamw #include <smbsrv/string.h>
33da6c28aaSamw #include <smbsrv/netbios.h>
34da6c28aaSamw 
35da6c28aaSamw static int domainname_is_valid(char *domain_name);
36da6c28aaSamw 
37da6c28aaSamw /*
38da6c28aaSamw  * Routines than support name compression.
39da6c28aaSamw  *
40da6c28aaSamw  *   The NetBIOS name representation in all NetBIOS packets (for NAME,
41da6c28aaSamw  *   SESSION, and DATAGRAM services) is defined in the Domain Name
42da6c28aaSamw  *   Service RFC 883[3] as "compressed" name messages.  This format is
43da6c28aaSamw  *   called "second-level encoding" in the section entitled
44da6c28aaSamw  *   "Representation of NetBIOS Names" in the Concepts and Methods
45da6c28aaSamw  *   document.
46da6c28aaSamw  *
47da6c28aaSamw  *   For ease of description, the first two paragraphs from page 31,
48da6c28aaSamw  *   the section titled "Domain name representation and compression",
49da6c28aaSamw  *   of RFC 883 are replicated here:
50da6c28aaSamw  *
51da6c28aaSamw  *        Domain names messages are expressed in terms of a sequence
52da6c28aaSamw  *        of labels.  Each label is represented as a one octet length
53da6c28aaSamw  *        field followed by that number of octets.  Since every domain
54da6c28aaSamw  *        name ends with the null label of the root, a compressed
55da6c28aaSamw  *        domain name is terminated by a length byte of zero.  The
56da6c28aaSamw  *        high order two bits of the length field must be zero, and
57da6c28aaSamw  *        the remaining six bits of the length field limit the label
58da6c28aaSamw  *        to 63 octets or less.
59da6c28aaSamw  *
60da6c28aaSamw  *        To simplify implementations, the total length of label
61da6c28aaSamw  *        octets and label length octets that make up a domain name is
62da6c28aaSamw  *        restricted to 255 octets or less.
63da6c28aaSamw  *
64da6c28aaSamw  *   The following is the uncompressed representation of the NetBIOS name
65da6c28aaSamw  *   "FRED ", which is the 4 ASCII characters, F, R, E, D, followed by 12
66da6c28aaSamw  *   space characters (0x20).  This name has the SCOPE_ID: "NETBIOS.COM"
67da6c28aaSamw  *
68da6c28aaSamw  *           EGFCEFEECACACACACACACACACACACACA.NETBIOS.COM
69da6c28aaSamw  *
70da6c28aaSamw  *   This uncompressed representation of names is called "first-level
71da6c28aaSamw  *   encoding" in the section entitled "Representation of NetBIOS Names"
72da6c28aaSamw  *   in the Concepts and Methods document.
73da6c28aaSamw  *
74da6c28aaSamw  *   The following is a pictographic representation of the compressed
75da6c28aaSamw  *   representation of the previous uncompressed Domain Name
76da6c28aaSamw  *   representation.
77da6c28aaSamw  *
78da6c28aaSamw  *                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
79da6c28aaSamw  *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
80da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
81da6c28aaSamw  *   |      0x20     |    E (0x45)   |    G (0x47)   |    F (0x46)   |
82da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
83da6c28aaSamw  *   |    C (0x43)   |    E (0x45)   |    F (0x46)   |    E (0x45)   |
84da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
85da6c28aaSamw  *   |    E (0x45)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
86da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
87da6c28aaSamw  *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
88da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
89da6c28aaSamw  *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
90da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
91da6c28aaSamw  *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
92da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
93da6c28aaSamw  *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
94da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
95da6c28aaSamw  *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
96da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
97da6c28aaSamw  *   |    A (0X41)   |      0x07     |    N (0x4E)   |    E (0x45)   |
98da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
99da6c28aaSamw  *   |    T (0x54)   |    B (0x42)   |    I (0x49)   |    O (0x4F)   |
100da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
101da6c28aaSamw  *   |    S (0x53)   |      0x03     |    C (0x43)   |    O (0x4F)   |
102da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
103da6c28aaSamw  *   |    M (0x4D)   |      0x00     |
104da6c28aaSamw  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
105da6c28aaSamw  *
106da6c28aaSamw  *   Each section of a domain name is called a label [7 (page 31)].  A
107da6c28aaSamw  *   label can be a maximum of 63 bytes.  The first byte of a label in
108da6c28aaSamw  *   compressed representation is the number of bytes in the label.  For
109da6c28aaSamw  *   the above example, the first 0x20 is the number of bytes in the
110da6c28aaSamw  *   left-most label, EGFCEFEECACACACACACACACACACACACA, of the domain
111da6c28aaSamw  *   name.  The bytes following the label length count are the characters
112da6c28aaSamw  *   of the label.  The following labels are in sequence after the first
113da6c28aaSamw  *   label, which is the encoded NetBIOS name, until a zero (0x00) length
114da6c28aaSamw  *   count.  The zero length count represents the root label, which is
115da6c28aaSamw  *   always null.
116da6c28aaSamw  *
117da6c28aaSamw  *   A label length count is actually a 6-bit field in the label length
118da6c28aaSamw  *   field.  The most significant 2 bits of the field, bits 7 and 6, are
119da6c28aaSamw  *   flags allowing an escape from the above compressed representation.
120da6c28aaSamw  *   If bits 7 and 6 are both set (11), the following 14 bits are an
121da6c28aaSamw  *   offset pointer into the full message to the actual label string from
122da6c28aaSamw  *   another domain name that belongs in this name.  This label pointer
123da6c28aaSamw  *   allows for a further compression of a domain name in a packet.
124da6c28aaSamw  *
125da6c28aaSamw  *   NetBIOS implementations can only use label string pointers in Name
126da6c28aaSamw  *   Service packets.  They cannot be used in Session or Datagram Service
127da6c28aaSamw  *   packets.
128da6c28aaSamw  *
129da6c28aaSamw  *   The other two possible values for bits 7 and 6 (01 and 10) of a label
130da6c28aaSamw  *   length field are reserved for future use by RFC 883[2 (page 32)].
131da6c28aaSamw  *
132da6c28aaSamw  *   Note that the first octet of a compressed name must contain one of
133da6c28aaSamw  *   the following bit patterns.  (An "x" indicates a bit whose value may
134da6c28aaSamw  *   be either 0 or 1.):
135da6c28aaSamw  *
136da6c28aaSamw  *           00100000 -  Netbios name, length must be 32 (decimal)
137da6c28aaSamw  *           11xxxxxx -  Label string pointer
138da6c28aaSamw  *           10xxxxxx -  Reserved
139da6c28aaSamw  *           01xxxxxx -  Reserved
140da6c28aaSamw  */
141da6c28aaSamw 
142da6c28aaSamw /*
143da6c28aaSamw  * netbios_first_level_name_encode
144da6c28aaSamw  *
145da6c28aaSamw  * Put test description here.
146da6c28aaSamw  *
147da6c28aaSamw  * Inputs:
148da6c28aaSamw  *	char *	in	-> Name to encode
149da6c28aaSamw  *	char *	out	-> Buffer to encode into.
150da6c28aaSamw  *	int	length	-> # of bytes to encode.
151da6c28aaSamw  *
152da6c28aaSamw  * Returns:
153da6c28aaSamw  *	Nothing
154da6c28aaSamw  */
155da6c28aaSamw int
156da6c28aaSamw netbios_first_level_name_encode(unsigned char *name, unsigned char *scope,
157da6c28aaSamw     unsigned char *out, int max_out)
158da6c28aaSamw {
159da6c28aaSamw 	unsigned char	ch, len;
160da6c28aaSamw 	unsigned char	 *in;
161da6c28aaSamw 	unsigned char	 *lp;
162da6c28aaSamw 	unsigned char	 *op = out;
163da6c28aaSamw 
164da6c28aaSamw 	if (max_out < 0x21)
165da6c28aaSamw 		return (-1);
166da6c28aaSamw 
167da6c28aaSamw 	in = name;
168da6c28aaSamw 	*op++ = 0x20;
169da6c28aaSamw 	for (len = 0; len < NETBIOS_NAME_SZ; len++) {
170da6c28aaSamw 		ch = *in++;
171da6c28aaSamw 		*op++ = 'A' + ((ch >> 4) & 0xF);
172da6c28aaSamw 		*op++ = 'A' + ((ch) & 0xF);
173da6c28aaSamw 	}
174da6c28aaSamw 
175da6c28aaSamw 	max_out -= 0x21;
176da6c28aaSamw 
177da6c28aaSamw 	in = scope;
178da6c28aaSamw 	len = 0;
179da6c28aaSamw 	lp = op++;
180da6c28aaSamw 	while (((ch = *in++) != 0) && (max_out-- > 1)) {
181da6c28aaSamw 		if (ch == 0) {
182da6c28aaSamw 			if ((*lp = len) != 0)
183da6c28aaSamw 				*op++ = 0;
184da6c28aaSamw 			break;
185da6c28aaSamw 		}
186da6c28aaSamw 		if (ch == '.') {
187da6c28aaSamw 			*lp = len;
188da6c28aaSamw 			lp = op++;
189da6c28aaSamw 			len = 0;
190da6c28aaSamw 		} else {
191da6c28aaSamw 			*op++ = ch;
192da6c28aaSamw 			len++;
193da6c28aaSamw 		}
194da6c28aaSamw 	}
195da6c28aaSamw 	*lp = len;
196da6c28aaSamw 	if (len != 0)
197da6c28aaSamw 		*op = 0;
198da6c28aaSamw 
199da6c28aaSamw 	/*LINTED E_PTRDIFF_OVERFLOW*/
200da6c28aaSamw 	return (op - out);
201da6c28aaSamw }
202da6c28aaSamw 
203da6c28aaSamw /*
204da6c28aaSamw  * smb_first_level_name_decode
205da6c28aaSamw  *
206da6c28aaSamw  * The null terminated string "in" is the name to decode. The output
207da6c28aaSamw  * is placed in the name_entry structure "name".
208da6c28aaSamw  *
209da6c28aaSamw  * The scope field is a series of length designated labels as described
210da6c28aaSamw  * in the "Domain name representation and compression" section of RFC883.
211da6c28aaSamw  * The two high order two bits of the length field must be zero, the
212da6c28aaSamw  * remaining six bits contain the field length. The total length of the
213da6c28aaSamw  * domain name is restricted to 255 octets but note that the trailing
214da6c28aaSamw  * root label and its dot are not printed. When converting the labels,
215da6c28aaSamw  * the length fields are replaced by dots.
216da6c28aaSamw  *
217da6c28aaSamw  * Returns the number of bytes scanned or -1 to indicate an error.
218da6c28aaSamw  */
219da6c28aaSamw int
220da6c28aaSamw netbios_first_level_name_decode(char *in, char *name, char *scope)
221da6c28aaSamw {
222da6c28aaSamw 	unsigned int	length, bytes;
223da6c28aaSamw 	char		c1, c2;
224da6c28aaSamw 	char		*cp;
225da6c28aaSamw 	char		*out;
226da6c28aaSamw 
227da6c28aaSamw 	cp = in;
228da6c28aaSamw 
229da6c28aaSamw 	if ((length = *cp++) != 0x20) {
230da6c28aaSamw 		return (-1);
231da6c28aaSamw 	}
232da6c28aaSamw 
233da6c28aaSamw 	out = name;
234da6c28aaSamw 	while (length > 0) {
235da6c28aaSamw 		c1 = *cp++;
236da6c28aaSamw 		c2 = *cp++;
237da6c28aaSamw 
238da6c28aaSamw 		if ('A' <= c1 && c1 <= 'P' && 'A' <= c2 && c2 <= 'P') {
239da6c28aaSamw 			c1 -= 'A';
240da6c28aaSamw 			c2 -= 'A';
241da6c28aaSamw 			*out++ = (c1 << 4) | (c2);
242da6c28aaSamw 		} else {
243da6c28aaSamw 			return (-1);		/* conversion error */
244da6c28aaSamw 		}
245da6c28aaSamw 		length -= 2;
246da6c28aaSamw 	}
247da6c28aaSamw 
248da6c28aaSamw 	out = scope;
249da6c28aaSamw 	bytes = 0;
250da6c28aaSamw 	for (length = *cp++; length != 0; length = *cp++) {
251da6c28aaSamw 		if ((length & 0xc0) != 0x00) {
252da6c28aaSamw 			/*
253da6c28aaSamw 			 * This is a pointer or a reserved field. If it's
254da6c28aaSamw 			 * a pointer (16-bits) we have to skip the next byte.
255da6c28aaSamw 			 */
256da6c28aaSamw 			if ((length & 0xc0) == 0xc0) {
257da6c28aaSamw 				cp++;
258da6c28aaSamw 				continue;
259da6c28aaSamw 			}
260da6c28aaSamw 		}
261da6c28aaSamw 
262da6c28aaSamw 		/*
263da6c28aaSamw 		 * Replace the length with a '.', except for the first one.
264da6c28aaSamw 		 */
265da6c28aaSamw 		if (out != scope) {
266da6c28aaSamw 			*out++ = '.';
267da6c28aaSamw 			bytes++;
268da6c28aaSamw 		}
269da6c28aaSamw 
270da6c28aaSamw 		while (length-- > 0) {
271da6c28aaSamw 			if (bytes++ >= (NETBIOS_DOMAIN_NAME_MAX - 1)) {
272da6c28aaSamw 				return (-1);
273da6c28aaSamw 			}
274da6c28aaSamw 			*out++ = *cp++;
275da6c28aaSamw 		}
276da6c28aaSamw 	}
277da6c28aaSamw 	*out = 0;
278da6c28aaSamw 
279da6c28aaSamw 	/*
280da6c28aaSamw 	 * We are supposed to preserve all 8-bits of the domain name
281da6c28aaSamw 	 * but due to the single byte representation in the name cache
282da6c28aaSamw 	 * and UTF-8 encoding everywhere else, we restrict domain names
283da6c28aaSamw 	 * to Appendix 1 - Domain Name Syntax Specification in RFC883.
284da6c28aaSamw 	 */
285da6c28aaSamw 	if (domainname_is_valid(scope))	{
286*bbf6f00cSJordan Brown 		(void) smb_strupr(scope);
287da6c28aaSamw 		/*LINTED E_PTRDIFF_OVERFLOW*/
288da6c28aaSamw 		return (cp - in);
289da6c28aaSamw 	}
290da6c28aaSamw 
291da6c28aaSamw 	scope[0] = '\0';
292da6c28aaSamw 	return (-1);
293da6c28aaSamw }
294da6c28aaSamw 
295da6c28aaSamw /*
296da6c28aaSamw  * smb_netbios_name_isvalid
297da6c28aaSamw  *
298da6c28aaSamw  * This function is provided to be used by session service
299da6c28aaSamw  * which runs in kernel in order to hide name_entry definition.
300da6c28aaSamw  *
301da6c28aaSamw  * It returns the decoded name in the provided buffer as 'out'
302da6c28aaSamw  * if it's not null.
303da6c28aaSamw  *
304da6c28aaSamw  * Returns 0 if decode fails, 1 if it succeeds.
305da6c28aaSamw  */
306da6c28aaSamw int
307da6c28aaSamw netbios_name_isvalid(char *in, char *out)
308da6c28aaSamw {
309da6c28aaSamw 	char name[NETBIOS_NAME_SZ];
310da6c28aaSamw 	char scope[NETBIOS_DOMAIN_NAME_MAX];
311da6c28aaSamw 
312da6c28aaSamw 	if (netbios_first_level_name_decode(in, name, scope) < 0)
313da6c28aaSamw 		return (0);
314da6c28aaSamw 
315da6c28aaSamw 	if (out)
316da6c28aaSamw 		(void) strlcpy(out, name, NETBIOS_NAME_SZ);
317da6c28aaSamw 
318da6c28aaSamw 	return (1);
319da6c28aaSamw }
320da6c28aaSamw 
321da6c28aaSamw /*
322da6c28aaSamw  * Characters that we allow in DNS domain names, in addition to
323da6c28aaSamw  * alphanumeric characters. This is not quite consistent with
324da6c28aaSamw  * RFC883. This is global so that it can be patched if there is
325da6c28aaSamw  * a need to change the valid characters in the field.
326da6c28aaSamw  */
327da6c28aaSamw unsigned char *dns_allowed = (unsigned char *)"-_";
328da6c28aaSamw 
329da6c28aaSamw /*
330da6c28aaSamw  * dns_is_allowed
331da6c28aaSamw  *
332da6c28aaSamw  * Check the dns_allowed characters and return true (1) if the character
333da6c28aaSamw  * is in the table. Otherwise return false (0).
334da6c28aaSamw  */
335da6c28aaSamw static int
336da6c28aaSamw dns_is_allowed(unsigned char c)
337da6c28aaSamw {
338da6c28aaSamw 	unsigned char *p = dns_allowed;
339da6c28aaSamw 
340da6c28aaSamw 	while (*p) {
341da6c28aaSamw 		if (c == *p++)
342da6c28aaSamw 			return (1);
343da6c28aaSamw 	}
344da6c28aaSamw 
345da6c28aaSamw 	return (0);
346da6c28aaSamw }
347da6c28aaSamw 
348da6c28aaSamw 
349da6c28aaSamw /*
350da6c28aaSamw  * domainname_is_valid
351da6c28aaSamw  *
352da6c28aaSamw  * Check the specified domain name for mostly compliance with RFC883
353da6c28aaSamw  * Appendix 1. Names may contain alphanumeric characters, hyphens,
354da6c28aaSamw  * underscores and dots. The first character after a dot must be an
355da6c28aaSamw  * alphabetic character. RFC883 doesn't mention underscores but we
356da6c28aaSamw  * allow it due to common use, and we don't check that labels end
357da6c28aaSamw  * with an alphanumeric character.
358da6c28aaSamw  *
359da6c28aaSamw  * Returns true (1) if the name is valid. Otherwise returns false (0).
360da6c28aaSamw  */
361da6c28aaSamw static int
362da6c28aaSamw domainname_is_valid(char *domain_name)
363da6c28aaSamw {
364da6c28aaSamw 	char *name;
365da6c28aaSamw 	int first_char = 1;
366da6c28aaSamw 
367da6c28aaSamw 	if (domain_name == 0)
368da6c28aaSamw 		return (0);
369da6c28aaSamw 
370da6c28aaSamw 	for (name = domain_name; *name != 0; ++name) {
371da6c28aaSamw 		if (*name == '.') {
372da6c28aaSamw 			first_char = 1;
373da6c28aaSamw 			continue;
374da6c28aaSamw 		}
375da6c28aaSamw 
376da6c28aaSamw 		if (first_char)	{
377*bbf6f00cSJordan Brown 			if (smb_isalpha_ascii(*name) == 0)
378da6c28aaSamw 				return (0);
379da6c28aaSamw 
380da6c28aaSamw 			first_char = 0;
381da6c28aaSamw 			continue;
382da6c28aaSamw 		}
383da6c28aaSamw 
384*bbf6f00cSJordan Brown 		if (smb_isalnum_ascii(*name) || dns_is_allowed(*name))
385da6c28aaSamw 			continue;
386da6c28aaSamw 
387da6c28aaSamw 		return (0);
388da6c28aaSamw 	}
389da6c28aaSamw 
390da6c28aaSamw 	return (1);
391da6c28aaSamw }
392