xref: /illumos-gate/usr/src/lib/smbsrv/libsmb/common/smb_util.c (revision 3d393ee6c37fa10ac512ed6d36109ad616dc7c1a)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <ctype.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <pthread.h>
31 #include <sys/varargs.h>
32 #include <sys/types.h>
33 #include <smbsrv/string.h>
34 #include <smbsrv/libsmb.h>
35 #include <tiuser.h>
36 #include <netconfig.h>
37 #include <netdir.h>
38 #include <sys/systeminfo.h>
39 #include <sys/utsname.h>
40 
41 static uint_t smb_make_mask(char *, uint_t);
42 static boolean_t smb_netmatch(struct netbuf *, char *);
43 static boolean_t smb_netgroup_match(struct nd_hostservlist *, char *, int);
44 
45 extern  int __multi_innetgr();
46 extern int __netdir_getbyaddr_nosrv(struct netconfig *,
47     struct nd_hostservlist **, struct netbuf *);
48 
49 #define	C2H(c)		"0123456789ABCDEF"[(c)]
50 #define	H2C(c)    (((c) >= '0' && (c) <= '9') ? ((c) - '0') :     \
51 	((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) :         \
52 	((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) :         \
53 	'\0')
54 #define	DEFAULT_SBOX_SIZE		256
55 
56 /*
57  *
58  * hexdump
59  *
60  * Simple hex dump display function. Displays nbytes of buffer in hex and
61  * printable format. Non-printing characters are shown as '.'. It is safe
62  * to pass a null pointer. Each line begins with the offset. If nbytes is
63  * 0, the line will be blank except for the offset. Example output:
64  *
65  * 00000000  54 68 69 73 20 69 73 20 61 20 70 72 6F 67 72 61  This is a progra
66  * 00000010  6D 20 74 65 73 74 2E 00                          m test..
67  *
68  */
69 void
70 hexdump_offset(unsigned char *buffer, int nbytes, unsigned long *start)
71 {
72 	static char *hex = "0123456789ABCDEF";
73 	int i, count;
74 	int offset;
75 	unsigned char *p;
76 	char ascbuf[64];
77 	char hexbuf[64];
78 	char *ap = ascbuf;
79 	char *hp = hexbuf;
80 
81 	if ((p = buffer) == NULL)
82 		return;
83 
84 	offset = *start;
85 
86 	*ap = '\0';
87 	*hp = '\0';
88 	count = 0;
89 
90 	for (i = 0; i < nbytes; ++i) {
91 		if (i && (i % 16) == 0) {
92 			smb_tracef("%06X %s  %s", offset, hexbuf, ascbuf);
93 			ap = ascbuf;
94 			hp = hexbuf;
95 			count = 0;
96 			offset += 16;
97 		}
98 
99 		ap += sprintf(ap, "%c",
100 		    (*p >= 0x20 && *p < 0x7F) ? *p : '.');
101 		hp += sprintf(hp, " %c%c",
102 		    hex[(*p >> 4) & 0x0F], hex[(*p & 0x0F)]);
103 		++p;
104 		++count;
105 	}
106 
107 	if (count) {
108 		smb_tracef("%06X %-48s  %s", offset, hexbuf, ascbuf);
109 		offset += count;
110 	}
111 
112 	*start = offset;
113 }
114 
115 void
116 hexdump(unsigned char *buffer, int nbytes)
117 {
118 	unsigned long start = 0;
119 
120 	hexdump_offset(buffer, nbytes, &start);
121 }
122 
123 /*
124  * bintohex
125  *
126  * Converts the given binary data (srcbuf) to
127  * its equivalent hex chars (hexbuf).
128  *
129  * hexlen should be at least twice as srclen.
130  * if hexbuf is not big enough returns 0.
131  * otherwise returns number of valid chars in
132  * hexbuf which is srclen * 2.
133  */
134 size_t
135 bintohex(const char *srcbuf, size_t srclen,
136     char *hexbuf, size_t hexlen)
137 {
138 	size_t outlen;
139 	char c;
140 
141 	outlen = srclen << 1;
142 
143 	if (hexlen < outlen)
144 		return (0);
145 
146 	while (srclen-- > 0) {
147 		c = *srcbuf++;
148 		*hexbuf++ = C2H(c & 0xF);
149 		*hexbuf++ = C2H((c >> 4) & 0xF);
150 	}
151 
152 	return (outlen);
153 }
154 
155 /*
156  * hextobin
157  *
158  * Converts hex to binary.
159  *
160  * Assuming hexbuf only contains hex digits (chars)
161  * this function convert every two bytes of hexbuf
162  * to one byte and put it in dstbuf.
163  *
164  * hexlen should be an even number.
165  * dstlen should be at least half of hexlen.
166  *
167  * Returns 0 if sizes are not correct, otherwise
168  * returns the number of converted bytes in dstbuf
169  * which is half of hexlen.
170  */
171 size_t
172 hextobin(const char *hexbuf, size_t hexlen,
173     char *dstbuf, size_t dstlen)
174 {
175 	size_t outlen;
176 
177 	if ((hexlen % 2) != 0)
178 		return (0);
179 
180 	outlen = hexlen >> 1;
181 	if (dstlen < outlen)
182 		return (0);
183 
184 	while (hexlen > 0) {
185 		*dstbuf = H2C(*hexbuf) & 0x0F;
186 		hexbuf++;
187 		*dstbuf++ |= (H2C(*hexbuf) << 4) & 0xF0;
188 		hexbuf++;
189 
190 		hexlen -= 2;
191 	}
192 
193 	return (outlen);
194 }
195 
196 /*
197  * Trim leading and trailing characters in the set defined by class
198  * from a buffer containing a null-terminated string.
199  * For example, if the input buffer contained "ABtext23" and class
200  * contains "ABC123", the buffer will contain "text" on return.
201  *
202  * This function modifies the contents of buf in place and returns
203  * a pointer to buf.
204  */
205 char *
206 strtrim(char *buf, const char *class)
207 {
208 	char *p = buf;
209 	char *q = buf;
210 
211 	if (buf == NULL)
212 		return (NULL);
213 
214 	p += strspn(p, class);
215 
216 	if (p != buf) {
217 		while ((*q = *p++) != '\0')
218 			++q;
219 	}
220 
221 	while (q != buf) {
222 		--q;
223 		if (strspn(q, class) == 0)
224 			return (buf);
225 		*q = '\0';
226 	}
227 
228 	return (buf);
229 }
230 
231 /*
232  * Strip the characters in the set defined by class from a buffer
233  * containing a null-terminated string.
234  * For example, if the input buffer contained "XYA 1textZ string3"
235  * and class contains "123XYZ", the buffer will contain "A text string"
236  * on return.
237  *
238  * This function modifies the contents of buf in place and returns
239  * a pointer to buf.
240  */
241 char *
242 strstrip(char *buf, const char *class)
243 {
244 	char *p = buf;
245 	char *q = buf;
246 
247 	if (buf == NULL)
248 		return (NULL);
249 
250 	while (*p) {
251 		p += strspn(p, class);
252 		*q++ = *p++;
253 	}
254 
255 	*q = '\0';
256 	return (buf);
257 }
258 
259 /*
260  * trim_whitespace
261  *
262  * Trim leading and trailing whitespace chars (as defined by isspace)
263  * from a buffer. Example; if the input buffer contained "  text  ",
264  * it will contain "text", when we return. We assume that the buffer
265  * contains a null terminated string. A pointer to the buffer is
266  * returned.
267  */
268 char *
269 trim_whitespace(char *buf)
270 {
271 	char *p = buf;
272 	char *q = buf;
273 
274 	if (buf == NULL)
275 		return (NULL);
276 
277 	while (*p && isspace(*p))
278 		++p;
279 
280 	while ((*q = *p++) != 0)
281 		++q;
282 
283 	if (q != buf) {
284 		while ((--q, isspace(*q)) != 0)
285 			*q = '\0';
286 	}
287 
288 	return (buf);
289 }
290 
291 /*
292  * randomize
293  *
294  * Randomize the contents of the specified buffer.
295  */
296 void
297 randomize(char *data, unsigned len)
298 {
299 	unsigned dwlen = len / 4;
300 	unsigned remlen = len % 4;
301 	unsigned tmp;
302 	unsigned i; /*LINTED E_BAD_PTR_CAST_ALIGN*/
303 	unsigned *p = (unsigned *)data;
304 
305 	for (i = 0; i < dwlen; ++i)
306 		*p++ = random();
307 
308 	if (remlen) {
309 		tmp = random();
310 		(void) memcpy(p, &tmp, remlen);
311 	}
312 }
313 
314 /*
315  * This is the hash mechanism used to encrypt passwords for commands like
316  * SamrSetUserInformation. It uses a 256 byte s-box.
317  */
318 void
319 rand_hash(
320     unsigned char *data,
321     size_t datalen,
322     unsigned char *key,
323     size_t keylen)
324 {
325 	unsigned char sbox[DEFAULT_SBOX_SIZE];
326 	unsigned char tmp;
327 	unsigned char index_i = 0;
328 	unsigned char index_j = 0;
329 	unsigned char j = 0;
330 	int i;
331 
332 	for (i = 0; i < DEFAULT_SBOX_SIZE; ++i)
333 		sbox[i] = (unsigned char)i;
334 
335 	for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) {
336 		j += (sbox[i] + key[i % keylen]);
337 
338 		tmp = sbox[i];
339 		sbox[i] = sbox[j];
340 		sbox[j] = tmp;
341 	}
342 
343 	for (i = 0; i < datalen; ++i) {
344 		index_i++;
345 		index_j += sbox[index_i];
346 
347 		tmp = sbox[index_i];
348 		sbox[index_i] = sbox[index_j];
349 		sbox[index_j] = tmp;
350 
351 		tmp = sbox[index_i] + sbox[index_j];
352 		data[i] = data[i] ^ sbox[tmp];
353 	}
354 }
355 
356 /*
357  * smb_chk_hostaccess
358  *
359  * Determine whether an access list grants rights to a particular host.
360  * We match on aliases of the hostname as well as on the canonical name.
361  * Names in the access list may be either hosts or netgroups;  they're
362  * not distinguished syntactically.  We check for hosts first because
363  * it's cheaper (just M*N strcmp()s), then try netgroups.
364  *
365  * Currently this function always returns B_TRUE for ipv6 until
366  * the underlying functions support ipv6
367  *
368  * Function returns:
369  *	-1 for "all"
370  *	0 not found
371  *	1 found
372  *
373  */
374 int
375 smb_chk_hostaccess(smb_inaddr_t *ipaddr, char *access_list)
376 {
377 	int nentries;
378 	char *gr;
379 	char *lasts;
380 	char *host;
381 	int off;
382 	int i;
383 	int netgroup_match;
384 	int response;
385 	struct nd_hostservlist *clnames;
386 	struct in_addr inaddr;
387 	struct sockaddr_in sa;
388 	struct netbuf buf;
389 	struct netconfig *config;
390 
391 	if (ipaddr->a_family == AF_INET6)
392 		return (B_TRUE);
393 
394 	inaddr.s_addr = ipaddr->a_ipv4;
395 
396 	/*
397 	 * If no access list - then it's "all"
398 	 */
399 	if (access_list == NULL || *access_list == '\0' ||
400 	    strcmp(access_list, "*") == 0)
401 		return (-1);
402 
403 	nentries = 0;
404 
405 	sa.sin_family = AF_INET;
406 	sa.sin_port = 0;
407 	sa.sin_addr = inaddr;
408 
409 	buf.len = buf.maxlen = sizeof (sa);
410 	buf.buf = (char *)&sa;
411 
412 	config = getnetconfigent("tcp");
413 	if (config == NULL)
414 		return (1);
415 
416 	if (__netdir_getbyaddr_nosrv(config, &clnames, &buf)) {
417 		freenetconfigent(config);
418 		return (0);
419 	}
420 	freenetconfigent(config);
421 
422 	for (gr = strtok_r(access_list, ":", &lasts);
423 	    gr != NULL; gr = strtok_r(NULL, ":", &lasts)) {
424 
425 		/*
426 		 * If the list name has a '-' prepended
427 		 * then a match of the following name
428 		 * implies failure instead of success.
429 		 */
430 		if (*gr == '-') {
431 			response = 0;
432 			gr++;
433 		} else {
434 			response = 1;
435 		}
436 
437 		/*
438 		 * The following loops through all the
439 		 * client's aliases.  Usually it's just one name.
440 		 */
441 		for (i = 0; i < clnames->h_cnt; i++) {
442 			host = clnames->h_hostservs[i].h_host;
443 			/*
444 			 * If the list name begins with a dot then
445 			 * do a domain name suffix comparison.
446 			 * A single dot matches any name with no
447 			 * suffix.
448 			 */
449 			if (*gr == '.') {
450 				if (*(gr + 1) == '\0') {  /* single dot */
451 					if (strchr(host, '.') == NULL)
452 						return (response);
453 				} else {
454 					off = strlen(host) - strlen(gr);
455 					if (off > 0 &&
456 					    strcasecmp(host + off, gr) == 0) {
457 						return (response);
458 					}
459 				}
460 			} else {
461 
462 				/*
463 				 * If the list name begins with an at
464 				 * sign then do a network comparison.
465 				 */
466 				if (*gr == '@') {
467 					if (smb_netmatch(&buf, gr + 1))
468 						return (response);
469 				} else {
470 					/*
471 					 * Just do a hostname match
472 					 */
473 					if (strcasecmp(gr, host) == 0)
474 						return (response);
475 				}
476 			}
477 		}
478 
479 		nentries++;
480 	}
481 
482 	netgroup_match = smb_netgroup_match(clnames, access_list, nentries);
483 
484 	return (netgroup_match);
485 }
486 
487 /*
488  * smb_make_mask
489  *
490  * Construct a mask for an IPv4 address using the @<dotted-ip>/<len>
491  * syntax or use the default mask for the IP address.
492  */
493 static uint_t
494 smb_make_mask(char *maskstr, uint_t addr)
495 {
496 	uint_t mask;
497 	uint_t bits;
498 
499 	/*
500 	 * If the mask is specified explicitly then
501 	 * use that value, e.g.
502 	 *
503 	 *    @109.104.56/28
504 	 *
505 	 * otherwise assume a mask from the zero octets
506 	 * in the least significant bits of the address, e.g.
507 	 *
508 	 *   @109.104  or  @109.104.0.0
509 	 */
510 	if (maskstr) {
511 		bits = atoi(maskstr);
512 		mask = bits ? ~0 << ((sizeof (struct in_addr) * NBBY) - bits)
513 		    : 0;
514 		addr &= mask;
515 	} else {
516 		if ((addr & IN_CLASSA_HOST) == 0)
517 			mask = IN_CLASSA_NET;
518 		else if ((addr & IN_CLASSB_HOST) == 0)
519 			mask = IN_CLASSB_NET;
520 		else if ((addr & IN_CLASSC_HOST) == 0)
521 			mask = IN_CLASSC_NET;
522 		else
523 			mask = IN_CLASSE_NET;
524 	}
525 
526 	return (mask);
527 }
528 
529 /*
530  * smb_netmatch
531  *
532  * Check to see if the address in the netbuf matches the "net"
533  * specified by name.  The format of "name" can be:
534  *	fully qualified domain name
535  *	dotted IP address
536  *	dotted IP address followed by '/<len>'
537  *	See sharen_nfs(1M) for details.
538  */
539 
540 static boolean_t
541 smb_netmatch(struct netbuf *nb, char *name)
542 {
543 	uint_t claddr;
544 	struct netent n, *np;
545 	char *mp, *p;
546 	uint_t addr, mask;
547 	int i;
548 	char buff[256];
549 
550 	/*
551 	 * Check if it's an IPv4 addr
552 	 */
553 	if (nb->len != sizeof (struct sockaddr_in))
554 		return (B_FALSE);
555 
556 	(void) memcpy(&claddr,
557 	    /* LINTED pointer alignment */
558 	    &((struct sockaddr_in *)nb->buf)->sin_addr.s_addr,
559 	    sizeof (struct in_addr));
560 	claddr = ntohl(claddr);
561 
562 	mp = strchr(name, '/');
563 	if (mp)
564 		*mp++ = '\0';
565 
566 	if (isdigit(*name)) {
567 		/*
568 		 * Convert a dotted IP address
569 		 * to an IP address. The conversion
570 		 * is not the same as that in inet_addr().
571 		 */
572 		p = name;
573 		addr = 0;
574 		for (i = 0; i < 4; i++) {
575 			addr |= atoi(p) << ((3-i) * 8);
576 			p = strchr(p, '.');
577 			if (p == NULL)
578 				break;
579 			p++;
580 		}
581 	} else {
582 		/*
583 		 * Turn the netname into
584 		 * an IP address.
585 		 */
586 		np = getnetbyname_r(name, &n, buff, sizeof (buff));
587 		if (np == NULL) {
588 			return (B_FALSE);
589 		}
590 		addr = np->n_net;
591 	}
592 
593 	mask = smb_make_mask(mp, addr);
594 	return ((claddr & mask) == addr);
595 }
596 
597 /*
598  * smb_netgroup_match
599  *
600  * Check whether any of the hostnames in clnames are
601  * members (or non-members) of the netgroups in glist.
602  * Since the innetgr lookup is rather expensive, the
603  * result is cached. The cached entry is valid only
604  * for VALID_TIME seconds.  This works well because
605  * typically these lookups occur in clusters when
606  * a client is mounting.
607  *
608  * Note that this routine establishes a host membership
609  * in a list of netgroups - we've no idea just which
610  * netgroup in the list it is a member of.
611  *
612  * glist is a character array containing grc strings
613  * representing netgroup names (optionally prefixed
614  * with '-'). Each string is ended with '\0'  and
615  * followed immediately by the next string.
616  */
617 static boolean_t
618 smb_netgroup_match(struct nd_hostservlist *clnames, char  *glist, int grc)
619 {
620 	char **grl;
621 	char *gr;
622 	int nhosts = clnames->h_cnt;
623 	char *host;
624 	int i, j, n;
625 	boolean_t response;
626 	boolean_t belong = B_FALSE;
627 	static char *domain = NULL;
628 
629 	if (domain == NULL) {
630 		int	ssize;
631 
632 		domain = malloc(SYS_NMLN);
633 		if (domain == NULL)
634 			return (B_FALSE);
635 
636 		ssize = sysinfo(SI_SRPC_DOMAIN, domain, SYS_NMLN);
637 		if (ssize > SYS_NMLN) {
638 			free(domain);
639 			domain = malloc(ssize);
640 			if (domain == NULL)
641 				return (B_FALSE);
642 			ssize = sysinfo(SI_SRPC_DOMAIN, domain, ssize);
643 		}
644 		/* Check for error in syscall or NULL domain name */
645 		if (ssize <= 1)
646 			return (B_FALSE);
647 	}
648 
649 	grl = calloc(grc, sizeof (char *));
650 	if (grl == NULL)
651 		return (B_FALSE);
652 
653 	for (i = 0, gr = glist; i < grc && !belong; ) {
654 		/*
655 		 * If the netgroup name has a '-' prepended
656 		 * then a match of this name implies a failure
657 		 * instead of success.
658 		 */
659 		response = (*gr != '-') ? B_TRUE : B_FALSE;
660 
661 		/*
662 		 * Subsequent names with or without a '-' (but no mix)
663 		 * can be grouped together for a single check.
664 		 */
665 		for (n = 0; i < grc; i++, n++, gr += strlen(gr) + 1) {
666 			if ((response && *gr == '-') ||
667 			    (!response && *gr != '-'))
668 				break;
669 
670 			grl[n] = response ? gr : gr + 1;
671 		}
672 
673 		/*
674 		 * Check the netgroup for each
675 		 * of the hosts names (usually just one).
676 		 */
677 		for (j = 0; j < nhosts && !belong; j++) {
678 			host = clnames->h_hostservs[j].h_host;
679 			if (__multi_innetgr(n, grl, 1, &host, 0, NULL,
680 			    1, &domain))
681 				belong = B_TRUE;
682 		}
683 	}
684 
685 	free(grl);
686 	return (belong ? response : B_FALSE);
687 }
688