summaryrefslogtreecommitdiff
path: root/usr/src/common/smbsrv/smb_netbios_util.c
blob: a0e0f480f1864e856894a4cbcda0cd88218dedd1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#ifdef _KERNEL
#include <sys/types.h>
#include <sys/sunddi.h>
#else
#include <string.h>
#endif
#include <smbsrv/string.h>
#include <smbsrv/netbios.h>

static int domainname_is_valid(char *domain_name);

/*
 * Routines than support name compression.
 *
 *   The NetBIOS name representation in all NetBIOS packets (for NAME,
 *   SESSION, and DATAGRAM services) is defined in the Domain Name
 *   Service RFC 883[3] as "compressed" name messages.  This format is
 *   called "second-level encoding" in the section entitled
 *   "Representation of NetBIOS Names" in the Concepts and Methods
 *   document.
 *
 *   For ease of description, the first two paragraphs from page 31,
 *   the section titled "Domain name representation and compression",
 *   of RFC 883 are replicated here:
 *
 *        Domain names messages are expressed in terms of a sequence
 *        of labels.  Each label is represented as a one octet length
 *        field followed by that number of octets.  Since every domain
 *        name ends with the null label of the root, a compressed
 *        domain name is terminated by a length byte of zero.  The
 *        high order two bits of the length field must be zero, and
 *        the remaining six bits of the length field limit the label
 *        to 63 octets or less.
 *
 *        To simplify implementations, the total length of label
 *        octets and label length octets that make up a domain name is
 *        restricted to 255 octets or less.
 *
 *   The following is the uncompressed representation of the NetBIOS name
 *   "FRED ", which is the 4 ASCII characters, F, R, E, D, followed by 12
 *   space characters (0x20).  This name has the SCOPE_ID: "NETBIOS.COM"
 *
 *           EGFCEFEECACACACACACACACACACACACA.NETBIOS.COM
 *
 *   This uncompressed representation of names is called "first-level
 *   encoding" in the section entitled "Representation of NetBIOS Names"
 *   in the Concepts and Methods document.
 *
 *   The following is a pictographic representation of the compressed
 *   representation of the previous uncompressed Domain Name
 *   representation.
 *
 *                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
 *    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
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |      0x20     |    E (0x45)   |    G (0x47)   |    F (0x46)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    C (0x43)   |    E (0x45)   |    F (0x46)   |    E (0x45)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    E (0x45)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    A (0x41)   |    C (0x43)   |    A (0x41)   |    C (0x43)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    A (0X41)   |      0x07     |    N (0x4E)   |    E (0x45)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    T (0x54)   |    B (0x42)   |    I (0x49)   |    O (0x4F)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    S (0x53)   |      0x03     |    C (0x43)   |    O (0x4F)   |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |    M (0x4D)   |      0x00     |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 *   Each section of a domain name is called a label [7 (page 31)].  A
 *   label can be a maximum of 63 bytes.  The first byte of a label in
 *   compressed representation is the number of bytes in the label.  For
 *   the above example, the first 0x20 is the number of bytes in the
 *   left-most label, EGFCEFEECACACACACACACACACACACACA, of the domain
 *   name.  The bytes following the label length count are the characters
 *   of the label.  The following labels are in sequence after the first
 *   label, which is the encoded NetBIOS name, until a zero (0x00) length
 *   count.  The zero length count represents the root label, which is
 *   always null.
 *
 *   A label length count is actually a 6-bit field in the label length
 *   field.  The most significant 2 bits of the field, bits 7 and 6, are
 *   flags allowing an escape from the above compressed representation.
 *   If bits 7 and 6 are both set (11), the following 14 bits are an
 *   offset pointer into the full message to the actual label string from
 *   another domain name that belongs in this name.  This label pointer
 *   allows for a further compression of a domain name in a packet.
 *
 *   NetBIOS implementations can only use label string pointers in Name
 *   Service packets.  They cannot be used in Session or Datagram Service
 *   packets.
 *
 *   The other two possible values for bits 7 and 6 (01 and 10) of a label
 *   length field are reserved for future use by RFC 883[2 (page 32)].
 *
 *   Note that the first octet of a compressed name must contain one of
 *   the following bit patterns.  (An "x" indicates a bit whose value may
 *   be either 0 or 1.):
 *
 *           00100000 -  Netbios name, length must be 32 (decimal)
 *           11xxxxxx -  Label string pointer
 *           10xxxxxx -  Reserved
 *           01xxxxxx -  Reserved
 */

/*
 * netbios_first_level_name_encode
 *
 * Put test description here.
 *
 * Inputs:
 *	char *	in	-> Name to encode
 *	char *	out	-> Buffer to encode into.
 *	int	length	-> # of bytes to encode.
 *
 * Returns:
 *	Nothing
 */
int
netbios_first_level_name_encode(unsigned char *name, unsigned char *scope,
    unsigned char *out, int max_out)
{
	unsigned char	ch, len;
	unsigned char	 *in;
	unsigned char	 *lp;
	unsigned char	 *op = out;

	if (max_out < 0x21)
		return (-1);

	in = name;
	*op++ = 0x20;
	for (len = 0; len < NETBIOS_NAME_SZ; len++) {
		ch = *in++;
		*op++ = 'A' + ((ch >> 4) & 0xF);
		*op++ = 'A' + ((ch) & 0xF);
	}

	max_out -= 0x21;

	in = scope;
	len = 0;
	lp = op++;
	while (((ch = *in++) != 0) && (max_out-- > 1)) {
		if (ch == 0) {
			if ((*lp = len) != 0)
				*op++ = 0;
			break;
		}
		if (ch == '.') {
			*lp = len;
			lp = op++;
			len = 0;
		} else {
			*op++ = ch;
			len++;
		}
	}
	*lp = len;
	if (len != 0)
		*op = 0;

	/*LINTED E_PTRDIFF_OVERFLOW*/
	return (op - out);
}

/*
 * smb_first_level_name_decode
 *
 * The null terminated string "in" is the name to decode. The output
 * is placed in the name_entry structure "name".
 *
 * The scope field is a series of length designated labels as described
 * in the "Domain name representation and compression" section of RFC883.
 * The two high order two bits of the length field must be zero, the
 * remaining six bits contain the field length. The total length of the
 * domain name is restricted to 255 octets but note that the trailing
 * root label and its dot are not printed. When converting the labels,
 * the length fields are replaced by dots.
 *
 * Returns the number of bytes scanned or -1 to indicate an error.
 */
int
netbios_first_level_name_decode(char *in, char *name, char *scope)
{
	unsigned int	length, bytes;
	char		c1, c2;
	char		*cp;
	char		*out;

	cp = in;

	if ((length = *cp++) != 0x20) {
		return (-1);
	}

	out = name;
	while (length > 0) {
		c1 = *cp++;
		c2 = *cp++;

		if ('A' <= c1 && c1 <= 'P' && 'A' <= c2 && c2 <= 'P') {
			c1 -= 'A';
			c2 -= 'A';
			*out++ = (c1 << 4) | (c2);
		} else {
			return (-1);		/* conversion error */
		}
		length -= 2;
	}

	out = scope;
	bytes = 0;
	for (length = *cp++; length != 0; length = *cp++) {
		if ((length & 0xc0) != 0x00) {
			/*
			 * This is a pointer or a reserved field. If it's
			 * a pointer (16-bits) we have to skip the next byte.
			 */
			if ((length & 0xc0) == 0xc0) {
				cp++;
				continue;
			}
		}

		/*
		 * Replace the length with a '.', except for the first one.
		 */
		if (out != scope) {
			*out++ = '.';
			bytes++;
		}

		while (length-- > 0) {
			if (bytes++ >= (NETBIOS_DOMAIN_NAME_MAX - 1)) {
				return (-1);
			}
			*out++ = *cp++;
		}
	}
	*out = 0;

	/*
	 * We are supposed to preserve all 8-bits of the domain name
	 * but due to the single byte representation in the name cache
	 * and UTF-8 encoding everywhere else, we restrict domain names
	 * to Appendix 1 - Domain Name Syntax Specification in RFC883.
	 */
	if (domainname_is_valid(scope))	{
		(void) smb_strupr(scope);
		/*LINTED E_PTRDIFF_OVERFLOW*/
		return (cp - in);
	}

	scope[0] = '\0';
	return (-1);
}

/*
 * smb_netbios_name_isvalid
 *
 * This function is provided to be used by session service
 * which runs in kernel in order to hide name_entry definition.
 *
 * It returns the decoded name in the provided buffer as 'out'
 * if it's not null.
 *
 * Returns 0 if decode fails, 1 if it succeeds.
 */
int
netbios_name_isvalid(char *in, char *out)
{
	char name[NETBIOS_NAME_SZ];
	char scope[NETBIOS_DOMAIN_NAME_MAX];

	if (netbios_first_level_name_decode(in, name, scope) < 0)
		return (0);

	if (out)
		(void) strlcpy(out, name, NETBIOS_NAME_SZ);

	return (1);
}

/*
 * Characters that we allow in DNS domain names, in addition to
 * alphanumeric characters. This is not quite consistent with
 * RFC883. This is global so that it can be patched if there is
 * a need to change the valid characters in the field.
 */
unsigned char *dns_allowed = (unsigned char *)"-_";

/*
 * dns_is_allowed
 *
 * Check the dns_allowed characters and return true (1) if the character
 * is in the table. Otherwise return false (0).
 */
static int
dns_is_allowed(unsigned char c)
{
	unsigned char *p = dns_allowed;

	while (*p) {
		if (c == *p++)
			return (1);
	}

	return (0);
}


/*
 * domainname_is_valid
 *
 * Check the specified domain name for mostly compliance with RFC883
 * Appendix 1. Names may contain alphanumeric characters, hyphens,
 * underscores and dots. The first character after a dot must be an
 * alphabetic character. RFC883 doesn't mention underscores but we
 * allow it due to common use, and we don't check that labels end
 * with an alphanumeric character.
 *
 * Returns true (1) if the name is valid. Otherwise returns false (0).
 */
static int
domainname_is_valid(char *domain_name)
{
	char *name;
	int first_char = 1;

	if (domain_name == 0)
		return (0);

	for (name = domain_name; *name != 0; ++name) {
		if (*name == '.') {
			first_char = 1;
			continue;
		}

		if (first_char)	{
			if (smb_isalpha_ascii(*name) == 0)
				return (0);

			first_char = 0;
			continue;
		}

		if (smb_isalnum_ascii(*name) || dns_is_allowed(*name))
			continue;

		return (0);
	}

	return (1);
}