xref: /illumos-gate/usr/src/lib/smbsrv/libsmb/common/smb_util.c (revision 29bd28862cfb8abbd3a0f0a4b17e08bbc3652836)
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 <sys/mnttab.h>
34 #include <tiuser.h>
35 #include <netconfig.h>
36 #include <netdir.h>
37 #include <sys/systeminfo.h>
38 #include <sys/utsname.h>
39 #include <libzfs.h>
40 #include <smbsrv/string.h>
41 #include <smbsrv/libsmb.h>
42 
43 static uint_t smb_make_mask(char *, uint_t);
44 static boolean_t smb_netmatch(struct netbuf *, char *);
45 static boolean_t smb_netgroup_match(struct nd_hostservlist *, char *, int);
46 
47 extern  int __multi_innetgr();
48 extern int __netdir_getbyaddr_nosrv(struct netconfig *,
49     struct nd_hostservlist **, struct netbuf *);
50 
51 #define	C2H(c)		"0123456789ABCDEF"[(c)]
52 #define	H2C(c)    (((c) >= '0' && (c) <= '9') ? ((c) - '0') :     \
53 	((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) :         \
54 	((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) :         \
55 	'\0')
56 #define	DEFAULT_SBOX_SIZE		256
57 
58 /*
59  *
60  * hexdump
61  *
62  * Simple hex dump display function. Displays nbytes of buffer in hex and
63  * printable format. Non-printing characters are shown as '.'. It is safe
64  * to pass a null pointer. Each line begins with the offset. If nbytes is
65  * 0, the line will be blank except for the offset. Example output:
66  *
67  * 00000000  54 68 69 73 20 69 73 20 61 20 70 72 6F 67 72 61  This is a progra
68  * 00000010  6D 20 74 65 73 74 2E 00                          m test..
69  *
70  */
71 void
72 hexdump_offset(unsigned char *buffer, int nbytes, unsigned long *start)
73 {
74 	static char *hex = "0123456789ABCDEF";
75 	int i, count;
76 	int offset;
77 	unsigned char *p;
78 	char ascbuf[64];
79 	char hexbuf[64];
80 	char *ap = ascbuf;
81 	char *hp = hexbuf;
82 
83 	if ((p = buffer) == NULL)
84 		return;
85 
86 	offset = *start;
87 
88 	*ap = '\0';
89 	*hp = '\0';
90 	count = 0;
91 
92 	for (i = 0; i < nbytes; ++i) {
93 		if (i && (i % 16) == 0) {
94 			smb_tracef("%06X %s  %s", offset, hexbuf, ascbuf);
95 			ap = ascbuf;
96 			hp = hexbuf;
97 			count = 0;
98 			offset += 16;
99 		}
100 
101 		ap += sprintf(ap, "%c",
102 		    (*p >= 0x20 && *p < 0x7F) ? *p : '.');
103 		hp += sprintf(hp, " %c%c",
104 		    hex[(*p >> 4) & 0x0F], hex[(*p & 0x0F)]);
105 		++p;
106 		++count;
107 	}
108 
109 	if (count) {
110 		smb_tracef("%06X %-48s  %s", offset, hexbuf, ascbuf);
111 		offset += count;
112 	}
113 
114 	*start = offset;
115 }
116 
117 void
118 hexdump(unsigned char *buffer, int nbytes)
119 {
120 	unsigned long start = 0;
121 
122 	hexdump_offset(buffer, nbytes, &start);
123 }
124 
125 /*
126  * bintohex
127  *
128  * Converts the given binary data (srcbuf) to
129  * its equivalent hex chars (hexbuf).
130  *
131  * hexlen should be at least twice as srclen.
132  * if hexbuf is not big enough returns 0.
133  * otherwise returns number of valid chars in
134  * hexbuf which is srclen * 2.
135  */
136 size_t
137 bintohex(const char *srcbuf, size_t srclen,
138     char *hexbuf, size_t hexlen)
139 {
140 	size_t outlen;
141 	char c;
142 
143 	outlen = srclen << 1;
144 
145 	if (hexlen < outlen)
146 		return (0);
147 
148 	while (srclen-- > 0) {
149 		c = *srcbuf++;
150 		*hexbuf++ = C2H(c & 0xF);
151 		*hexbuf++ = C2H((c >> 4) & 0xF);
152 	}
153 
154 	return (outlen);
155 }
156 
157 /*
158  * hextobin
159  *
160  * Converts hex to binary.
161  *
162  * Assuming hexbuf only contains hex digits (chars)
163  * this function convert every two bytes of hexbuf
164  * to one byte and put it in dstbuf.
165  *
166  * hexlen should be an even number.
167  * dstlen should be at least half of hexlen.
168  *
169  * Returns 0 if sizes are not correct, otherwise
170  * returns the number of converted bytes in dstbuf
171  * which is half of hexlen.
172  */
173 size_t
174 hextobin(const char *hexbuf, size_t hexlen,
175     char *dstbuf, size_t dstlen)
176 {
177 	size_t outlen;
178 
179 	if ((hexlen % 2) != 0)
180 		return (0);
181 
182 	outlen = hexlen >> 1;
183 	if (dstlen < outlen)
184 		return (0);
185 
186 	while (hexlen > 0) {
187 		*dstbuf = H2C(*hexbuf) & 0x0F;
188 		hexbuf++;
189 		*dstbuf++ |= (H2C(*hexbuf) << 4) & 0xF0;
190 		hexbuf++;
191 
192 		hexlen -= 2;
193 	}
194 
195 	return (outlen);
196 }
197 
198 /*
199  * Trim leading and trailing characters in the set defined by class
200  * from a buffer containing a null-terminated string.
201  * For example, if the input buffer contained "ABtext23" and class
202  * contains "ABC123", the buffer will contain "text" on return.
203  *
204  * This function modifies the contents of buf in place and returns
205  * a pointer to buf.
206  */
207 char *
208 strtrim(char *buf, const char *class)
209 {
210 	char *p = buf;
211 	char *q = buf;
212 
213 	if (buf == NULL)
214 		return (NULL);
215 
216 	p += strspn(p, class);
217 
218 	if (p != buf) {
219 		while ((*q = *p++) != '\0')
220 			++q;
221 	}
222 
223 	while (q != buf) {
224 		--q;
225 		if (strspn(q, class) == 0)
226 			return (buf);
227 		*q = '\0';
228 	}
229 
230 	return (buf);
231 }
232 
233 /*
234  * Strip the characters in the set defined by class from a buffer
235  * containing a null-terminated string.
236  * For example, if the input buffer contained "XYA 1textZ string3"
237  * and class contains "123XYZ", the buffer will contain "A text string"
238  * on return.
239  *
240  * This function modifies the contents of buf in place and returns
241  * a pointer to buf.
242  */
243 char *
244 strstrip(char *buf, const char *class)
245 {
246 	char *p = buf;
247 	char *q = buf;
248 
249 	if (buf == NULL)
250 		return (NULL);
251 
252 	while (*p) {
253 		p += strspn(p, class);
254 		*q++ = *p++;
255 	}
256 
257 	*q = '\0';
258 	return (buf);
259 }
260 
261 /*
262  * trim_whitespace
263  *
264  * Trim leading and trailing whitespace chars (as defined by isspace)
265  * from a buffer. Example; if the input buffer contained "  text  ",
266  * it will contain "text", when we return. We assume that the buffer
267  * contains a null terminated string. A pointer to the buffer is
268  * returned.
269  */
270 char *
271 trim_whitespace(char *buf)
272 {
273 	char *p = buf;
274 	char *q = buf;
275 
276 	if (buf == NULL)
277 		return (NULL);
278 
279 	while (*p && isspace(*p))
280 		++p;
281 
282 	while ((*q = *p++) != 0)
283 		++q;
284 
285 	if (q != buf) {
286 		while ((--q, isspace(*q)) != 0)
287 			*q = '\0';
288 	}
289 
290 	return (buf);
291 }
292 
293 /*
294  * randomize
295  *
296  * Randomize the contents of the specified buffer.
297  */
298 void
299 randomize(char *data, unsigned len)
300 {
301 	unsigned dwlen = len / 4;
302 	unsigned remlen = len % 4;
303 	unsigned tmp;
304 	unsigned i; /*LINTED E_BAD_PTR_CAST_ALIGN*/
305 	unsigned *p = (unsigned *)data;
306 
307 	for (i = 0; i < dwlen; ++i)
308 		*p++ = random();
309 
310 	if (remlen) {
311 		tmp = random();
312 		(void) memcpy(p, &tmp, remlen);
313 	}
314 }
315 
316 /*
317  * This is the hash mechanism used to encrypt passwords for commands like
318  * SamrSetUserInformation. It uses a 256 byte s-box.
319  */
320 void
321 rand_hash(
322     unsigned char *data,
323     size_t datalen,
324     unsigned char *key,
325     size_t keylen)
326 {
327 	unsigned char sbox[DEFAULT_SBOX_SIZE];
328 	unsigned char tmp;
329 	unsigned char index_i = 0;
330 	unsigned char index_j = 0;
331 	unsigned char j = 0;
332 	int i;
333 
334 	for (i = 0; i < DEFAULT_SBOX_SIZE; ++i)
335 		sbox[i] = (unsigned char)i;
336 
337 	for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) {
338 		j += (sbox[i] + key[i % keylen]);
339 
340 		tmp = sbox[i];
341 		sbox[i] = sbox[j];
342 		sbox[j] = tmp;
343 	}
344 
345 	for (i = 0; i < datalen; ++i) {
346 		index_i++;
347 		index_j += sbox[index_i];
348 
349 		tmp = sbox[index_i];
350 		sbox[index_i] = sbox[index_j];
351 		sbox[index_j] = tmp;
352 
353 		tmp = sbox[index_i] + sbox[index_j];
354 		data[i] = data[i] ^ sbox[tmp];
355 	}
356 }
357 
358 /*
359  * smb_chk_hostaccess
360  *
361  * Determine whether an access list grants rights to a particular host.
362  * We match on aliases of the hostname as well as on the canonical name.
363  * Names in the access list may be either hosts or netgroups;  they're
364  * not distinguished syntactically.  We check for hosts first because
365  * it's cheaper (just M*N strcmp()s), then try netgroups.
366  *
367  * Currently this function always returns B_TRUE for ipv6 until
368  * the underlying functions support ipv6
369  *
370  * Function returns:
371  *	-1 for "all"
372  *	0 not found
373  *	1 found
374  *
375  */
376 int
377 smb_chk_hostaccess(smb_inaddr_t *ipaddr, char *access_list)
378 {
379 	int nentries;
380 	char *gr;
381 	char *lasts;
382 	char *host;
383 	int off;
384 	int i;
385 	int netgroup_match;
386 	int response;
387 	struct nd_hostservlist *clnames;
388 	struct in_addr inaddr;
389 	struct sockaddr_in sa;
390 	struct netbuf buf;
391 	struct netconfig *config;
392 
393 	if (ipaddr->a_family == AF_INET6)
394 		return (B_TRUE);
395 
396 	inaddr.s_addr = ipaddr->a_ipv4;
397 
398 	/*
399 	 * If no access list - then it's "all"
400 	 */
401 	if (access_list == NULL || *access_list == '\0' ||
402 	    strcmp(access_list, "*") == 0)
403 		return (-1);
404 
405 	nentries = 0;
406 
407 	sa.sin_family = AF_INET;
408 	sa.sin_port = 0;
409 	sa.sin_addr = inaddr;
410 
411 	buf.len = buf.maxlen = sizeof (sa);
412 	buf.buf = (char *)&sa;
413 
414 	config = getnetconfigent("tcp");
415 	if (config == NULL)
416 		return (1);
417 
418 	if (__netdir_getbyaddr_nosrv(config, &clnames, &buf)) {
419 		freenetconfigent(config);
420 		return (0);
421 	}
422 	freenetconfigent(config);
423 
424 	for (gr = strtok_r(access_list, ":", &lasts);
425 	    gr != NULL; gr = strtok_r(NULL, ":", &lasts)) {
426 
427 		/*
428 		 * If the list name has a '-' prepended
429 		 * then a match of the following name
430 		 * implies failure instead of success.
431 		 */
432 		if (*gr == '-') {
433 			response = 0;
434 			gr++;
435 		} else {
436 			response = 1;
437 		}
438 
439 		/*
440 		 * The following loops through all the
441 		 * client's aliases.  Usually it's just one name.
442 		 */
443 		for (i = 0; i < clnames->h_cnt; i++) {
444 			host = clnames->h_hostservs[i].h_host;
445 			/*
446 			 * If the list name begins with a dot then
447 			 * do a domain name suffix comparison.
448 			 * A single dot matches any name with no
449 			 * suffix.
450 			 */
451 			if (*gr == '.') {
452 				if (*(gr + 1) == '\0') {  /* single dot */
453 					if (strchr(host, '.') == NULL)
454 						return (response);
455 				} else {
456 					off = strlen(host) - strlen(gr);
457 					if (off > 0 &&
458 					    strcasecmp(host + off, gr) == 0) {
459 						return (response);
460 					}
461 				}
462 			} else {
463 
464 				/*
465 				 * If the list name begins with an at
466 				 * sign then do a network comparison.
467 				 */
468 				if (*gr == '@') {
469 					if (smb_netmatch(&buf, gr + 1))
470 						return (response);
471 				} else {
472 					/*
473 					 * Just do a hostname match
474 					 */
475 					if (strcasecmp(gr, host) == 0)
476 						return (response);
477 				}
478 			}
479 		}
480 
481 		nentries++;
482 	}
483 
484 	netgroup_match = smb_netgroup_match(clnames, access_list, nentries);
485 
486 	return (netgroup_match);
487 }
488 
489 /*
490  * smb_make_mask
491  *
492  * Construct a mask for an IPv4 address using the @<dotted-ip>/<len>
493  * syntax or use the default mask for the IP address.
494  */
495 static uint_t
496 smb_make_mask(char *maskstr, uint_t addr)
497 {
498 	uint_t mask;
499 	uint_t bits;
500 
501 	/*
502 	 * If the mask is specified explicitly then
503 	 * use that value, e.g.
504 	 *
505 	 *    @109.104.56/28
506 	 *
507 	 * otherwise assume a mask from the zero octets
508 	 * in the least significant bits of the address, e.g.
509 	 *
510 	 *   @109.104  or  @109.104.0.0
511 	 */
512 	if (maskstr) {
513 		bits = atoi(maskstr);
514 		mask = bits ? ~0 << ((sizeof (struct in_addr) * NBBY) - bits)
515 		    : 0;
516 		addr &= mask;
517 	} else {
518 		if ((addr & IN_CLASSA_HOST) == 0)
519 			mask = IN_CLASSA_NET;
520 		else if ((addr & IN_CLASSB_HOST) == 0)
521 			mask = IN_CLASSB_NET;
522 		else if ((addr & IN_CLASSC_HOST) == 0)
523 			mask = IN_CLASSC_NET;
524 		else
525 			mask = IN_CLASSE_NET;
526 	}
527 
528 	return (mask);
529 }
530 
531 /*
532  * smb_netmatch
533  *
534  * Check to see if the address in the netbuf matches the "net"
535  * specified by name.  The format of "name" can be:
536  *	fully qualified domain name
537  *	dotted IP address
538  *	dotted IP address followed by '/<len>'
539  *	See sharen_nfs(1M) for details.
540  */
541 
542 static boolean_t
543 smb_netmatch(struct netbuf *nb, char *name)
544 {
545 	uint_t claddr;
546 	struct netent n, *np;
547 	char *mp, *p;
548 	uint_t addr, mask;
549 	int i;
550 	char buff[256];
551 
552 	/*
553 	 * Check if it's an IPv4 addr
554 	 */
555 	if (nb->len != sizeof (struct sockaddr_in))
556 		return (B_FALSE);
557 
558 	(void) memcpy(&claddr,
559 	    /* LINTED pointer alignment */
560 	    &((struct sockaddr_in *)nb->buf)->sin_addr.s_addr,
561 	    sizeof (struct in_addr));
562 	claddr = ntohl(claddr);
563 
564 	mp = strchr(name, '/');
565 	if (mp)
566 		*mp++ = '\0';
567 
568 	if (isdigit(*name)) {
569 		/*
570 		 * Convert a dotted IP address
571 		 * to an IP address. The conversion
572 		 * is not the same as that in inet_addr().
573 		 */
574 		p = name;
575 		addr = 0;
576 		for (i = 0; i < 4; i++) {
577 			addr |= atoi(p) << ((3-i) * 8);
578 			p = strchr(p, '.');
579 			if (p == NULL)
580 				break;
581 			p++;
582 		}
583 	} else {
584 		/*
585 		 * Turn the netname into
586 		 * an IP address.
587 		 */
588 		np = getnetbyname_r(name, &n, buff, sizeof (buff));
589 		if (np == NULL) {
590 			return (B_FALSE);
591 		}
592 		addr = np->n_net;
593 	}
594 
595 	mask = smb_make_mask(mp, addr);
596 	return ((claddr & mask) == addr);
597 }
598 
599 /*
600  * smb_netgroup_match
601  *
602  * Check whether any of the hostnames in clnames are
603  * members (or non-members) of the netgroups in glist.
604  * Since the innetgr lookup is rather expensive, the
605  * result is cached. The cached entry is valid only
606  * for VALID_TIME seconds.  This works well because
607  * typically these lookups occur in clusters when
608  * a client is mounting.
609  *
610  * Note that this routine establishes a host membership
611  * in a list of netgroups - we've no idea just which
612  * netgroup in the list it is a member of.
613  *
614  * glist is a character array containing grc strings
615  * representing netgroup names (optionally prefixed
616  * with '-'). Each string is ended with '\0'  and
617  * followed immediately by the next string.
618  */
619 static boolean_t
620 smb_netgroup_match(struct nd_hostservlist *clnames, char  *glist, int grc)
621 {
622 	char **grl;
623 	char *gr;
624 	int nhosts = clnames->h_cnt;
625 	char *host;
626 	int i, j, n;
627 	boolean_t response;
628 	boolean_t belong = B_FALSE;
629 	static char *domain = NULL;
630 
631 	if (domain == NULL) {
632 		int	ssize;
633 
634 		domain = malloc(SYS_NMLN);
635 		if (domain == NULL)
636 			return (B_FALSE);
637 
638 		ssize = sysinfo(SI_SRPC_DOMAIN, domain, SYS_NMLN);
639 		if (ssize > SYS_NMLN) {
640 			free(domain);
641 			domain = malloc(ssize);
642 			if (domain == NULL)
643 				return (B_FALSE);
644 			ssize = sysinfo(SI_SRPC_DOMAIN, domain, ssize);
645 		}
646 		/* Check for error in syscall or NULL domain name */
647 		if (ssize <= 1)
648 			return (B_FALSE);
649 	}
650 
651 	grl = calloc(grc, sizeof (char *));
652 	if (grl == NULL)
653 		return (B_FALSE);
654 
655 	for (i = 0, gr = glist; i < grc && !belong; ) {
656 		/*
657 		 * If the netgroup name has a '-' prepended
658 		 * then a match of this name implies a failure
659 		 * instead of success.
660 		 */
661 		response = (*gr != '-') ? B_TRUE : B_FALSE;
662 
663 		/*
664 		 * Subsequent names with or without a '-' (but no mix)
665 		 * can be grouped together for a single check.
666 		 */
667 		for (n = 0; i < grc; i++, n++, gr += strlen(gr) + 1) {
668 			if ((response && *gr == '-') ||
669 			    (!response && *gr != '-'))
670 				break;
671 
672 			grl[n] = response ? gr : gr + 1;
673 		}
674 
675 		/*
676 		 * Check the netgroup for each
677 		 * of the hosts names (usually just one).
678 		 */
679 		for (j = 0; j < nhosts && !belong; j++) {
680 			host = clnames->h_hostservs[j].h_host;
681 			if (__multi_innetgr(n, grl, 1, &host, 0, NULL,
682 			    1, &domain))
683 				belong = B_TRUE;
684 		}
685 	}
686 
687 	free(grl);
688 	return (belong ? response : B_FALSE);
689 }
690 
691 /*
692  * Resolve the ZFS dataset from a path.
693  * Returns,
694  *	0  = On success.
695  *	-1 = Failure to open /etc/mnttab file or to get ZFS dataset.
696  */
697 int
698 smb_getdataset(const char *path, char *dataset, size_t len)
699 {
700 	char tmppath[MAXPATHLEN];
701 	char *cp;
702 	FILE *fp;
703 	struct mnttab mnttab;
704 	struct mnttab mntpref;
705 	int rc = -1;
706 
707 	if ((fp = fopen(MNTTAB, "r")) == NULL)
708 		return (-1);
709 
710 	(void) memset(&mnttab, '\0', sizeof (mnttab));
711 	(void) strlcpy(tmppath, path, MAXPATHLEN);
712 	cp = tmppath;
713 
714 	while (*cp != '\0') {
715 		resetmnttab(fp);
716 		(void) memset(&mntpref, '\0', sizeof (mntpref));
717 		mntpref.mnt_mountp = tmppath;
718 
719 		if (getmntany(fp, &mnttab, &mntpref) == 0) {
720 			if (mnttab.mnt_fstype == NULL)
721 				break;
722 
723 			if (strcmp(mnttab.mnt_fstype, "zfs") != 0)
724 				break;
725 			/*
726 			 * Ensure that there are no leading slashes
727 			 * (required for zfs_open).
728 			 */
729 			cp = mnttab.mnt_special;
730 			cp += strspn(cp, "/");
731 			(void) strlcpy(dataset, cp, len);
732 			rc = 0;
733 			break;
734 		}
735 
736 		if (strcmp(tmppath, "/") == 0)
737 			break;
738 
739 		if ((cp = strrchr(tmppath, '/')) == NULL)
740 			break;
741 
742 		/*
743 		 * The path has multiple components.
744 		 * Remove the last component and try again.
745 		 */
746 		*cp = '\0';
747 		if (tmppath[0] == '\0')
748 			(void) strcpy(tmppath, "/");
749 
750 		cp = tmppath;
751 	}
752 
753 	(void) fclose(fp);
754 	return (rc);
755 }
756 
757 /*
758  * Returns the hostname given the IP address.  Wrapper for getnameinfo.
759  */
760 int
761 smb_getnameinfo(smb_inaddr_t *ip, char *hostname, int hostlen, int flags)
762 {
763 	socklen_t salen;
764 	struct sockaddr_in6 sin6;
765 	struct sockaddr_in sin;
766 	void *sp;
767 
768 	if (ip->a_family == AF_INET) {
769 		salen = sizeof (struct sockaddr_in);
770 		sin.sin_family = ip->a_family;
771 		sin.sin_port = 0;
772 		sin.sin_addr.s_addr = ip->a_ipv4;
773 		sp = &sin;
774 	} else {
775 		salen = sizeof (struct sockaddr_in6);
776 		sin6.sin6_family = ip->a_family;
777 		sin6.sin6_port = 0;
778 		(void) memcpy(&sin6.sin6_addr.s6_addr, &ip->a_ipv6,
779 		    sizeof (sin6.sin6_addr.s6_addr));
780 		sp = &sin6;
781 	}
782 	return (getnameinfo((struct sockaddr *)sp, salen,
783 	    hostname, hostlen, NULL, 0, flags));
784 }
785 
786 smb_ulist_t *
787 smb_ulist_alloc(void)
788 {
789 	smb_ulist_t *ulist;
790 
791 	ulist = malloc(sizeof (smb_ulist_t));
792 	if (ulist != NULL) {
793 		ulist->ul_cnt = 0;
794 		ulist->ul_users = NULL;
795 	}
796 	return (ulist);
797 }
798 
799 void
800 smb_ulist_free(smb_ulist_t *ulist)
801 {
802 	if (ulist != NULL) {
803 		smb_ulist_cleanup(ulist);
804 		free(ulist);
805 	}
806 }
807 
808 void
809 smb_ulist_cleanup(smb_ulist_t *ulist)
810 {
811 	smb_opipe_context_t *ctx;
812 
813 	if (ulist->ul_users != NULL) {
814 		ctx = ulist->ul_users;
815 		while (ulist->ul_cnt != 0) {
816 			free(ctx->oc_domain);
817 			free(ctx->oc_account);
818 			free(ctx->oc_workstation);
819 			ctx->oc_domain = NULL;
820 			ctx->oc_account = NULL;
821 			ctx->oc_workstation = NULL;
822 			ulist->ul_cnt--;
823 			ctx++;
824 		}
825 		free(ulist->ul_users);
826 		ulist->ul_users = NULL;
827 	}
828 }
829