xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/dfs.c (revision 861a91627796c35220e75654dac61e5707536dcd)
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 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <strings.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <dirent.h>
31 #include <pthread.h>
32 #include <syslog.h>
33 #include <sys/fs_reparse.h>
34 #include <uuid/uuid.h>
35 
36 #include <smbsrv/nterror.h>
37 #include <smbsrv/smb_dfs.h>
38 #include <smbsrv/smb_share.h>
39 #include <smbsrv/libsmb.h>
40 #include <smbsrv/libmlsvc.h>
41 #include <dfs.h>
42 
43 /*
44  * default timeout (TTL) values (in second) for root and link
45  */
46 #define	DFS_ROOT_TIMEOUT		300
47 #define	DFS_LINK_TIMEOUT		1800
48 
49 /*
50  * DFS link data format in reparse point
51  *
52  * ver:state:prop:timeout:guid:ntarget:cmntlen:comment
53  *    [[:tserver:tshare:tstate:pclass:prank]...]
54  */
55 #define	DFS_LINK_V1			1
56 #define	DFS_LINK_HDR_NFIELDS		7	/* # fields in header section */
57 #define	DFS_LINK_TRGT_NFIELDS		5	/* # fields for each target */
58 
59 #define	DFS_ROOT_XATTR			"SUNWdfs.rootinfo"
60 
61 #define	DFS_INFO_ALL			0
62 
63 /*
64  * Namespace cache
65  *
66  * Caches links' UNC and filesystem path where the key is the UNC path.
67  */
68 static smb_cache_t dfs_nscache;
69 static char dfs_nbname[NETBIOS_NAME_SZ];
70 
71 /*
72  * Lock for accessing root information (extended attribute)
73  */
74 static rwlock_t dfs_root_rwl;
75 
76 extern uint32_t srvsvc_shr_setdfsroot(smb_share_t *, boolean_t);
77 
78 /*
79  * Namespace functions
80  */
81 static boolean_t dfs_namespace_findlink(const char *, char *, char *, size_t);
82 static void *dfs_namespace_cache(void *);
83 
84 /*
85  * Root functions
86  */
87 static int dfs_root_add(const char *, dfs_info_t *);
88 static uint32_t dfs_root_remove(const char *);
89 static uint32_t dfs_root_encode(dfs_info_t *, char **, size_t *);
90 static uint32_t dfs_root_decode(dfs_info_t *, char *, size_t, uint32_t);
91 static uint32_t dfs_root_isvalidstate(uint32_t);
92 
93 static int dfs_root_xopen(const char *, int);
94 static void dfs_root_xclose(int);
95 static uint32_t dfs_root_xwrite(int, dfs_info_t *);
96 static uint32_t dfs_root_xread(int, dfs_info_t *, uint32_t);
97 
98 /*
99  * Link functions
100  */
101 static uint32_t dfs_link_encode(dfs_info_t *, char *, size_t);
102 static uint32_t dfs_link_decode(dfs_info_t *, char *, uint32_t);
103 static uint32_t dfs_link_commit(const char *, dfs_info_t *);
104 static boolean_t dfs_link_isvalidstate(uint32_t);
105 
106 /*
107  * Target functions
108  */
109 static void dfs_target_init(dfs_target_t *, const char *, const char *,
110     uint32_t);
111 static int dfs_target_find(dfs_target_t *, uint32_t, const char *,
112     const char *);
113 static boolean_t dfs_target_isvalidstate(uint32_t);
114 
115 /*
116  * Cache functions
117  */
118 static uint32_t dfs_cache_add_byunc(const char *, const char *, uint32_t);
119 static void dfs_cache_populate(const char *, const char *);
120 static int dfs_cache_cmp(const void *, const void *);
121 
122 /*
123  * Utility functions
124  */
125 static boolean_t dfs_path_isdir(const char *);
126 static uint32_t dfs_modinfo(uint32_t, dfs_info_t *, dfs_info_t *, uint32_t);
127 
128 /*
129  * DFS module initializationr:
130  *
131  * - creates the namespace cache
132  * - gets system's NetBIOS name
133  */
134 void
135 dfs_init(void)
136 {
137 	smb_domain_t di;
138 
139 	smb_cache_create(&dfs_nscache, 0, dfs_cache_cmp, free, bcopy,
140 	    sizeof (dfs_nscnode_t));
141 
142 	if (!smb_domain_lookup_type(SMB_DOMAIN_LOCAL, &di))
143 		return;
144 
145 	(void) strlcpy(dfs_nbname, di.di_nbname, NETBIOS_NAME_SZ);
146 }
147 
148 /*
149  * DFS module cleanup:
150  *
151  * - destroys the namespace cache
152  */
153 void
154 dfs_fini(void)
155 {
156 	smb_cache_destroy(&dfs_nscache);
157 }
158 
159 /*
160  * To successfully handle some of link/root requests, some
161  * file system operations need to be performed. These operations
162  * should take place on behalf of the connected user (typically
163  * Administrator) and to do so we need to have an infrastructure
164  * in place so that smbd can act as a client and sends request to
165  * the kernel. Right now, we lack this infrastructure, so we make
166  * a compromise by temporarily enabling some privileges for smbd
167  * to be able to fulfill various link/root requests.
168  */
169 void
170 dfs_setpriv(priv_op_t op)
171 {
172 	(void) priv_set(op, PRIV_EFFECTIVE,
173 	    PRIV_FILE_DAC_READ,
174 	    PRIV_FILE_DAC_WRITE,
175 	    PRIV_FILE_DAC_EXECUTE,
176 	    PRIV_FILE_DAC_SEARCH, NULL);
177 }
178 
179 /*
180  * ========================
181  * Namespace API (public)
182  * ========================
183  */
184 
185 /*
186  * Launches a thread to cache the specified namespace
187  */
188 void
189 dfs_namespace_load(const char *name)
190 {
191 	pthread_t thr;
192 	pthread_attr_t tattr;
193 	char *rootshr;
194 	int rc;
195 
196 	if ((rootshr = strdup(name)) == NULL) {
197 		syslog(LOG_ERR, "dfs: failed to load %s namespace (no memory)",
198 		    name);
199 		return;
200 	}
201 
202 	(void) pthread_attr_init(&tattr);
203 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
204 	rc = pthread_create(&thr, &tattr, dfs_namespace_cache, rootshr);
205 	(void) pthread_attr_destroy(&tattr);
206 
207 	if (rc != 0)
208 		syslog(LOG_ERR, "dfs: fail to loading %s namespace (%d)",
209 		    name, rc);
210 }
211 
212 /*
213  * Flushes the cache when a DFS root share is removed
214  */
215 void /*ARGSUSED*/
216 dfs_namespace_unload(const char *name)
217 {
218 	smb_cache_flush(&dfs_nscache);
219 }
220 
221 /*
222  * Returns the file system path for the given share if it
223  * is a DFS root share.
224  * If 'path' is NULL, this function only indicates whether
225  * or not the given share represents a DFS namespace
226  */
227 uint32_t
228 dfs_namespace_path(const char *name, char *path, size_t pathsz)
229 {
230 	smb_share_t si;
231 
232 	if (smb_shr_get((char *)name, &si) != NERR_Success)
233 		return (ERROR_NOT_FOUND);
234 
235 	if ((si.shr_flags & SMB_SHRF_DFSROOT) == 0)
236 		return (ERROR_NOT_FOUND);
237 
238 	if (path != NULL)
239 		(void) strlcpy(path, si.shr_path, pathsz);
240 
241 	return (ERROR_SUCCESS);
242 }
243 
244 /*
245  * Returns the number of DFS root shares i.e. the number
246  * of standalone namespaces.
247  */
248 uint32_t
249 dfs_namespace_count(void)
250 {
251 	smb_shriter_t shi;
252 	smb_share_t *si;
253 	uint32_t nroot = 0;
254 
255 	smb_shr_iterinit(&shi);
256 	while ((si = smb_shr_iterate(&shi)) != NULL) {
257 		if ((si->shr_flags & SMB_SHRF_DFSROOT) != 0)
258 			nroot++;
259 	}
260 
261 	return (nroot);
262 }
263 
264 /*
265  * Creates a DFS root with the given name and comment.
266  *
267  * This function does not create the root share, it
268  * should already exist.
269  */
270 uint32_t
271 dfs_namespace_add(const char *rootshr, const char *cmnt)
272 {
273 	dfs_info_t info;
274 	dfs_target_t t;
275 	smb_share_t si;
276 	uuid_t uuid;
277 	uint32_t status;
278 
279 	if (*rootshr == '\\') {
280 		/* Windows has a special case here! */
281 		return (ERROR_BAD_PATHNAME);
282 	}
283 
284 	if (smb_shr_get((char *)rootshr, &si) != NERR_Success)
285 		return (NERR_NetNameNotFound);
286 
287 	if (si.shr_flags & SMB_SHRF_DFSROOT) {
288 		/* Share is already a DFS root */
289 		return (ERROR_FILE_EXISTS);
290 	}
291 
292 	bzero(&info, sizeof (info));
293 	if (cmnt)
294 		(void) strlcpy(info.i_comment, cmnt, sizeof (info.i_comment));
295 	info.i_state = DFS_VOLUME_STATE_OK | DFS_VOLUME_FLAVOR_STANDALONE;
296 	info.i_timeout = DFS_ROOT_TIMEOUT;
297 	info.i_propflags = 0;
298 
299 	uuid_generate_random(uuid);
300 	uuid_unparse(uuid, info.i_guid);
301 
302 	dfs_target_init(&t, dfs_nbname, rootshr, DFS_STORAGE_STATE_ONLINE);
303 
304 	info.i_ntargets = 1;
305 	info.i_targets = &t;
306 
307 	if ((status = dfs_root_add(si.shr_path, &info)) != ERROR_SUCCESS)
308 		return (status);
309 
310 	status = srvsvc_shr_setdfsroot(&si, B_TRUE);
311 	if (status == ERROR_SUCCESS)
312 		(void) dfs_cache_add_byname(rootshr, NULL, DFS_OBJECT_ROOT);
313 
314 	return (status);
315 }
316 
317 /*
318  * Removes the namespace and all the links in it.
319  */
320 uint32_t
321 dfs_namespace_remove(const char *name)
322 {
323 	smb_cache_cursor_t cursor;
324 	dfs_nscnode_t nscnode;
325 	smb_share_t si;
326 	uint32_t status;
327 
328 	if (smb_shr_get((char *)name, &si) != NERR_Success)
329 		return (ERROR_NOT_FOUND);
330 
331 	if ((si.shr_flags & SMB_SHRF_DFSROOT) == 0)
332 		return (ERROR_NOT_FOUND);
333 
334 	if ((status = dfs_root_remove(si.shr_path)) != ERROR_SUCCESS)
335 		return (status);
336 
337 	status = srvsvc_shr_setdfsroot(&si, B_FALSE);
338 	if (status != ERROR_SUCCESS)
339 		syslog(LOG_WARNING, "dfs: failed to disable root share %s (%d)",
340 		    name, status);
341 
342 	smb_cache_iterinit(&dfs_nscache, &cursor);
343 
344 	while (smb_cache_iterate(&dfs_nscache, &cursor, &nscnode)) {
345 		if (nscnode.nsc_type == DFS_OBJECT_ROOT)
346 			continue;
347 		status = dfs_link_remove(nscnode.nsc_fspath, NULL, NULL);
348 		if (status != ERROR_SUCCESS)
349 			syslog(LOG_WARNING, "dfs: failed to remove %s (%d)",
350 			    nscnode.nsc_fspath, status);
351 	}
352 
353 	smb_cache_flush(&dfs_nscache);
354 
355 	/* TODO: remove empty dirs */
356 	return (ERROR_SUCCESS);
357 }
358 
359 /*
360  * ==================
361  * Root API (public)
362  * ==================
363  */
364 
365 /*
366  * Retrieves the information of the root specified by its path.
367  *
368  * Info level (1) only needs the UNC path which is not stored,
369  * it is constructed so the function will return without
370  * accessing the backend storage.
371  */
372 uint32_t
373 dfs_root_getinfo(const char *rootdir, dfs_info_t *info, uint32_t infolvl)
374 {
375 	uint32_t status = ERROR_INTERNAL_ERROR;
376 	int xfd;
377 
378 	bzero(info, sizeof (dfs_info_t));
379 	info->i_type = DFS_OBJECT_ROOT;
380 
381 	if (infolvl == 1)
382 		return (ERROR_SUCCESS);
383 
384 	(void) rw_rdlock(&dfs_root_rwl);
385 	if ((xfd = dfs_root_xopen(rootdir, O_RDONLY)) > 0) {
386 		status = dfs_root_xread(xfd, info, infolvl);
387 		dfs_root_xclose(xfd);
388 	}
389 	(void) rw_unlock(&dfs_root_rwl);
390 
391 	return (status);
392 }
393 
394 /*
395  * Sets the provided information for the specified root or root target.
396  * Root is specified by 'rootdir' and the target is specified by
397  * (t_server, t_share) pair. Only information items needed for given
398  * information level (infolvl) is valid in the passed DFS info structure
399  * 'info'.
400  */
401 uint32_t
402 dfs_root_setinfo(const char *rootdir, dfs_info_t *info, uint32_t infolvl)
403 {
404 	dfs_info_t curinfo;
405 	uint32_t status = ERROR_SUCCESS;
406 	int xfd;
407 
408 	(void) rw_wrlock(&dfs_root_rwl);
409 	if ((xfd = dfs_root_xopen(rootdir, O_RDWR)) < 0) {
410 		(void) rw_unlock(&dfs_root_rwl);
411 		return (ERROR_INTERNAL_ERROR);
412 	}
413 
414 	status = dfs_root_xread(xfd, &curinfo, DFS_INFO_ALL);
415 	if (status != ERROR_SUCCESS) {
416 		dfs_root_xclose(xfd);
417 		(void) rw_unlock(&dfs_root_rwl);
418 		return (status);
419 	}
420 
421 	status = dfs_modinfo(DFS_OBJECT_ROOT, &curinfo, info, infolvl);
422 	if (status == ERROR_SUCCESS)
423 		status = dfs_root_xwrite(xfd, &curinfo);
424 
425 	dfs_root_xclose(xfd);
426 	(void) rw_unlock(&dfs_root_rwl);
427 
428 	dfs_info_free(&curinfo);
429 	return (status);
430 }
431 
432 /*
433  * ==================
434  * Link API (public)
435  * ==================
436  */
437 
438 /*
439  * Gets the status of the given path as a link
440  */
441 uint32_t
442 dfs_link_stat(const char *path, uint32_t *stat)
443 {
444 	if (smb_reparse_stat(path, stat) != 0)
445 		return (ERROR_INTERNAL_ERROR);
446 
447 	switch (*stat) {
448 	case SMB_REPARSE_NOTFOUND:
449 		*stat = DFS_STAT_NOTFOUND;
450 		break;
451 	case SMB_REPARSE_NOTREPARSE:
452 		*stat = DFS_STAT_NOTLINK;
453 		break;
454 	case SMB_REPARSE_ISREPARSE:
455 		*stat = DFS_STAT_ISREPARSE;
456 		if (smb_reparse_svcget(path, DFS_REPARSE_SVCTYPE, NULL) == 0)
457 			*stat = DFS_STAT_ISDFS;
458 		break;
459 	default:
460 		*stat = DFS_STAT_UNKNOWN;
461 		break;
462 	}
463 
464 	return (ERROR_SUCCESS);
465 }
466 
467 /*
468  * Creates a new DFS link or adds a new target to an existing link
469  */
470 uint32_t
471 dfs_link_add(const char *path, const char *server, const char *share,
472     const char *cmnt, uint32_t flags, boolean_t *newlink)
473 {
474 	dfs_info_t info;
475 	dfs_target_t *t;
476 	int ntargets;
477 	uint32_t status;
478 	uint32_t stat;
479 
480 	*newlink = B_FALSE;
481 
482 	if ((status = dfs_link_stat(path, &stat)) != ERROR_SUCCESS)
483 		return (status);
484 
485 	switch (stat) {
486 	case DFS_STAT_NOTFOUND:
487 	case DFS_STAT_ISREPARSE:
488 		/* Create a new DFS link */
489 
490 		status = dfs_link_getinfo(NULL, &info, DFS_INFO_ALL);
491 		if (status != ERROR_SUCCESS)
492 			return (status);
493 
494 		(void) strlcpy(info.i_comment, (cmnt) ? cmnt : "",
495 		    sizeof (info.i_comment));
496 		*newlink = B_TRUE;
497 		break;
498 
499 	case DFS_STAT_ISDFS:
500 		/* Add a target to an existing link */
501 
502 		if (flags & DFS_ADD_VOLUME)
503 			return (ERROR_FILE_EXISTS);
504 
505 		status = dfs_link_getinfo(path, &info, DFS_INFO_ALL);
506 		if (status != ERROR_SUCCESS)
507 			return (status);
508 
509 		break;
510 
511 	case DFS_STAT_NOTLINK:
512 		/* specified path points to a non-reparse object */
513 		return (ERROR_FILE_EXISTS);
514 
515 	default:
516 		return (ERROR_INTERNAL_ERROR);
517 	}
518 
519 	/* checks to see if the target already exists */
520 	ntargets = info.i_ntargets;
521 	if (dfs_target_find(info.i_targets, ntargets, server, share) != -1) {
522 		dfs_info_free(&info);
523 		return (ERROR_FILE_EXISTS);
524 	}
525 
526 	/* add the new target */
527 	t = realloc(info.i_targets, (ntargets + 1) * sizeof (dfs_target_t));
528 	if (t == NULL) {
529 		dfs_info_free(&info);
530 		return (ERROR_NOT_ENOUGH_MEMORY);
531 	}
532 
533 	info.i_targets = t;
534 	dfs_target_init(&info.i_targets[ntargets], server, share,
535 	    DFS_STORAGE_STATE_ONLINE);
536 	info.i_ntargets++;
537 
538 	status = dfs_link_commit(path, &info);
539 
540 	dfs_info_free(&info);
541 	return (status);
542 }
543 
544 /*
545  * Removes a link or a link target from a DFS namespace. A link can be
546  * removed regardless of the number of targets associated with it.
547  *
548  * 'server' and 'share' parameters specify a target, so if they are NULL
549  * it means the link should be removed, otherwise the specified target
550  * is removed if found.
551  */
552 uint32_t
553 dfs_link_remove(const char *path, const char *server, const char *share)
554 {
555 	dfs_info_t info;
556 	uint32_t status, stat;
557 	int rc, idx;
558 
559 	if ((status = dfs_link_stat(path, &stat)) != ERROR_SUCCESS)
560 		return (status);
561 
562 	if (stat != DFS_STAT_ISDFS)
563 		return (ERROR_NOT_FOUND);
564 
565 	if (server == NULL && share == NULL) {
566 		/* remove the link */
567 		if (smb_reparse_svcdel(path, DFS_REPARSE_SVCTYPE) != 0)
568 			return (ERROR_INTERNAL_ERROR);
569 
570 		return (ERROR_SUCCESS);
571 	}
572 
573 	/* remove the specified target in the link */
574 
575 	status = dfs_link_getinfo(path, &info, DFS_INFO_ALL);
576 	if (status != ERROR_SUCCESS)
577 		return (status);
578 
579 	/* checks to see if the target exists */
580 	idx = dfs_target_find(info.i_targets, info.i_ntargets, server, share);
581 	if (idx != -1) {
582 		bcopy(&info.i_targets[idx + 1], &info.i_targets[idx],
583 		    (info.i_ntargets - idx - 1) * sizeof (dfs_target_t));
584 		info.i_ntargets--;
585 	} else {
586 		dfs_info_free(&info);
587 		return (ERROR_FILE_NOT_FOUND);
588 	}
589 
590 	if (info.i_ntargets == 0) {
591 		/* if last target, then remove the link */
592 		rc = smb_reparse_svcdel(path, DFS_REPARSE_SVCTYPE);
593 		status = (rc == 0) ? ERROR_SUCCESS : ERROR_INTERNAL_ERROR;
594 	} else {
595 		status = dfs_link_commit(path, &info);
596 	}
597 
598 	dfs_info_free(&info);
599 	return (status);
600 }
601 
602 /*
603  * Sets the provided information for the specified link or link target.
604  * Link is specified by 'path' and the target is specified by
605  * (t_server, t_share) pair. Only information items needed for given
606  * information level (infolvl) is valid in the passed DFS info structure
607  * 'info'.
608  */
609 uint32_t
610 dfs_link_setinfo(const char *path, dfs_info_t *info, uint32_t infolvl)
611 {
612 	dfs_info_t curinfo;
613 	uint32_t status;
614 
615 	status = dfs_link_getinfo(path, &curinfo, DFS_INFO_ALL);
616 	if (status != ERROR_SUCCESS)
617 		return (status);
618 
619 	status = dfs_modinfo(DFS_OBJECT_LINK, &curinfo, info, infolvl);
620 	if (status == ERROR_SUCCESS)
621 		status = dfs_link_commit(path, &curinfo);
622 
623 	dfs_info_free(&curinfo);
624 	return (status);
625 }
626 
627 /*
628  * Gets the DFS link info.
629  *
630  * If path is NULL, it just does some initialization.
631  *
632  * Info level (1) only needs the UNC path which is not
633  * stored, it is constructed so the function will return
634  * without accessing the backend storage.
635  */
636 uint32_t
637 dfs_link_getinfo(const char *path, dfs_info_t *info, uint32_t infolvl)
638 {
639 	char *link_data;
640 	uint32_t status;
641 	uuid_t uuid;
642 	int rc;
643 
644 	bzero(info, sizeof (dfs_info_t));
645 	info->i_type = DFS_OBJECT_LINK;
646 
647 	if (path == NULL) {
648 		info->i_state = DFS_VOLUME_STATE_OK;
649 		info->i_timeout = DFS_LINK_TIMEOUT;
650 		info->i_propflags = 0;
651 		uuid_generate_random(uuid);
652 		uuid_unparse(uuid, info->i_guid);
653 		return (ERROR_SUCCESS);
654 	}
655 
656 	if (infolvl == 1)
657 		return (ERROR_SUCCESS);
658 
659 	rc = smb_reparse_svcget(path, DFS_REPARSE_SVCTYPE, &link_data);
660 	if (rc != 0)
661 		return (ERROR_INTERNAL_ERROR);
662 
663 	status = dfs_link_decode(info, link_data, infolvl);
664 	free(link_data);
665 
666 	return (status);
667 }
668 
669 /*
670  * ===================
671  * Cache API (public)
672  * ===================
673  */
674 
675 /*
676  * Adds an entry with given DFS name (root sharename) and relative path
677  * to the share (relpath) and the specified entry type (i.e. root/link)
678  * to the namespace cache.
679  */
680 uint32_t
681 dfs_cache_add_byname(const char *name, const char *relpath, uint32_t type)
682 {
683 	char uncpath[DFS_PATH_MAX];
684 	char fspath[DFS_PATH_MAX];
685 	smb_share_t si;
686 
687 	if (smb_shr_get((char *)name, &si) != NERR_Success)
688 		return (ERROR_NOT_FOUND);
689 
690 	if (type == DFS_OBJECT_ROOT) {
691 		(void) snprintf(uncpath, DFS_PATH_MAX, "\\\\%s\\%s",
692 		    dfs_nbname, name);
693 		return (dfs_cache_add_byunc(uncpath, si.shr_path, type));
694 	}
695 
696 	/* add link entry */
697 	(void) snprintf(fspath, DFS_PATH_MAX, "%s/%s", si.shr_path, relpath);
698 	(void) snprintf(uncpath, DFS_PATH_MAX, "\\\\%s\\%s\\%s", dfs_nbname,
699 	    name, relpath);
700 
701 	/* relpath may contain '/' */
702 	(void) strsubst(uncpath, '/', '\\');
703 
704 	return (dfs_cache_add_byunc(uncpath, fspath, type));
705 }
706 
707 /*
708  * Removes the namespace cache entry for the given link
709  * in the namespace ('name') with 'relpath'
710  */
711 void
712 dfs_cache_remove(const char *name, const char *relpath)
713 {
714 	dfs_nscnode_t dn;
715 
716 	(void) snprintf(dn.nsc_uncpath, sizeof (dn.nsc_uncpath),
717 	    "\\\\%s\\%s\\%s", dfs_nbname, name, relpath);
718 
719 	/* relpath may contain '/' */
720 	(void) strsubst(dn.nsc_uncpath, '/', '\\');
721 
722 	smb_cache_remove(&dfs_nscache, &dn);
723 }
724 
725 /*
726  * Get the DFS data for the specified cache entry
727  */
728 uint32_t
729 dfs_cache_getinfo(dfs_nscnode_t *dn, dfs_info_t *info, uint32_t infolvl)
730 {
731 	uint32_t status;
732 
733 	if (dn->nsc_type == DFS_OBJECT_LINK)
734 		status = dfs_link_getinfo(dn->nsc_fspath, info, infolvl);
735 	else
736 		status = dfs_root_getinfo(dn->nsc_fspath, info, infolvl);
737 
738 	(void) strlcpy(info->i_uncpath, dn->nsc_uncpath,
739 	    sizeof (info->i_uncpath));
740 
741 	if (status == ERROR_SUCCESS)
742 		dfs_info_trace("dfs_cache_getinfo", info);
743 
744 	return (status);
745 }
746 
747 /*
748  * Returns the number of cache entries i.e. the number of
749  * root(s) and link(s)
750  */
751 uint32_t
752 dfs_cache_num(void)
753 {
754 	return (smb_cache_num(&dfs_nscache));
755 }
756 
757 void
758 dfs_cache_iterinit(smb_cache_cursor_t *cursor)
759 {
760 	smb_cache_iterinit(&dfs_nscache, cursor);
761 }
762 
763 boolean_t
764 dfs_cache_iterate(smb_cache_cursor_t *cursor, dfs_nscnode_t *dn)
765 {
766 	return (smb_cache_iterate(&dfs_nscache, cursor, dn));
767 }
768 
769 /*
770  * ==================
771  * Misc API (public)
772  * ==================
773  */
774 
775 /*
776  * This is the function that is called by smbd door server to
777  * fullfil a GetReferrals request from smbsrv kernel module
778  *
779  * 'reftype' specifies the requested referral type. If it is
780  * DFS_REFERRAL_ROOT then dfs_path should point to a namespace
781  * root. If it is DFS_REFERRAL_LINK then dfs_path should CONTAIN
782  * a link, in which case this function will find the link and
783  * returns its target information.
784  */
785 uint32_t
786 dfs_get_referrals(const char *dfs_path, dfs_reftype_t reftype,
787     dfs_info_t *referrals)
788 {
789 	dfs_path_t path;
790 	smb_unc_t *unc;
791 	char linkpath[DFS_PATH_MAX];
792 	uint32_t status;
793 
794 	status = dfs_path_parse(&path, dfs_path, DFS_OBJECT_ANY);
795 	if (status != ERROR_SUCCESS)
796 		return (status);
797 
798 	dfs_setpriv(PRIV_ON);
799 
800 	referrals->i_type = path.p_type;
801 
802 	switch (reftype) {
803 	case DFS_REFERRAL_ROOT:
804 		if (path.p_type != DFS_OBJECT_ROOT) {
805 			status = ERROR_INVALID_PARAMETER;
806 			break;
807 		}
808 
809 		status = dfs_root_getinfo((const char *)path.p_fspath,
810 		    referrals, DFS_INFO_ALL);
811 		(void) strlcpy(referrals->i_uncpath, dfs_path, DFS_PATH_MAX);
812 		break;
813 
814 	case DFS_REFERRAL_LINK:
815 		if (path.p_type != DFS_OBJECT_LINK) {
816 			status = ERROR_INVALID_PARAMETER;
817 			break;
818 		}
819 
820 		unc = &path.p_unc;
821 		if (!dfs_namespace_findlink(unc->unc_share, unc->unc_path,
822 		    linkpath, DFS_PATH_MAX)) {
823 			status = ERROR_NOT_FOUND;
824 			break;
825 		}
826 
827 		status = dfs_link_getinfo(linkpath, referrals, DFS_INFO_ALL);
828 		(void) snprintf(referrals->i_uncpath, DFS_PATH_MAX, "/%s/%s/%s",
829 		    unc->unc_server, unc->unc_share, unc->unc_path);
830 		break;
831 
832 	default:
833 		status = ERROR_INVALID_PARAMETER;
834 		break;
835 	}
836 
837 	dfs_setpriv(PRIV_OFF);
838 	dfs_path_free(&path);
839 	return (status);
840 }
841 
842 /*
843  * Takes a DFS path in UNC format (dfs_path) and parse it into a dfs_path_t
844  * structure.
845  *
846  * dfs_path_free() MUST be called to free the allocated memory in this
847  * function.
848  *
849  * Returns:
850  *
851  * ERROR_INVALID_PARAMETER	path is not a valid UNC or not valid for the
852  * 				specified object type
853  * ERROR_NOT_ENOUGH_MEMORY	not enough memory to peform the parse
854  * ERROR_NOT_FOUND		namespace specified does not exist
855  */
856 uint32_t
857 dfs_path_parse(dfs_path_t *path, const char *dfs_path, uint32_t path_type)
858 {
859 	char rootdir[DFS_PATH_MAX];
860 	smb_unc_t *unc;
861 	uint32_t status = ERROR_SUCCESS;
862 	int rc;
863 
864 	bzero(path, sizeof (dfs_path_t));
865 	unc = &path->p_unc;
866 
867 	rc = smb_unc_init(dfs_path, unc);
868 	switch (rc) {
869 	case EINVAL:
870 		return (ERROR_INVALID_PARAMETER);
871 	case ENOMEM:
872 		return (ERROR_NOT_ENOUGH_MEMORY);
873 	default:
874 		break;
875 	}
876 
877 	if (dfs_namespace_path(unc->unc_share, rootdir, DFS_PATH_MAX)
878 	    != ERROR_SUCCESS) {
879 		smb_unc_free(unc);
880 		return (ERROR_NOT_FOUND);
881 	}
882 
883 	if (path_type == DFS_OBJECT_ANY)
884 		path->p_type = (unc->unc_path != NULL)
885 		    ? DFS_OBJECT_LINK : DFS_OBJECT_ROOT;
886 	else
887 		path->p_type = path_type;
888 
889 	switch (path->p_type) {
890 	case DFS_OBJECT_LINK:
891 		if ((unc->unc_path == NULL) || (*unc->unc_path == '\0'))
892 			status = ERROR_NOT_FOUND;
893 		else
894 			(void) snprintf(path->p_fspath, sizeof (path->p_fspath),
895 			    "%s/%s", rootdir, unc->unc_path);
896 		break;
897 
898 	case DFS_OBJECT_ROOT:
899 		if (unc->unc_path == NULL)
900 			(void) strlcpy(path->p_fspath, rootdir,
901 			    sizeof (path->p_fspath));
902 		else
903 			status = ERROR_INVALID_PARAMETER;
904 		break;
905 
906 	default:
907 		status = ERROR_INVALID_PARAMETER;
908 	}
909 
910 	if (status != ERROR_SUCCESS)
911 		smb_unc_free(unc);
912 
913 	return (status);
914 }
915 
916 /*
917  * Frees the allocated memory for p_unc field of the passed path
918  */
919 void
920 dfs_path_free(dfs_path_t *path)
921 {
922 	if (path != NULL)
923 		smb_unc_free(&path->p_unc);
924 }
925 
926 /*
927  * Free the allocated memory for targets in the given info
928  * structure
929  */
930 void
931 dfs_info_free(dfs_info_t *info)
932 {
933 	if (info)
934 		free(info->i_targets);
935 }
936 
937 /*
938  * Trace the given DFS info structure
939  */
940 void
941 dfs_info_trace(const char *msg, dfs_info_t *info)
942 {
943 	dfs_target_t *t;
944 	int i;
945 
946 	smb_tracef("%s", msg);
947 	if (info == NULL)
948 		return;
949 
950 	smb_tracef("UNC\t%s", info->i_uncpath);
951 	smb_tracef("comment\t%s", info->i_comment);
952 	smb_tracef("GUID\t%s", info->i_guid);
953 	smb_tracef("state\t%X", info->i_state);
954 	smb_tracef("timeout\t%d", info->i_timeout);
955 	smb_tracef("props\t%X", info->i_propflags);
956 	smb_tracef("# targets\t%X", info->i_ntargets);
957 
958 	if (info->i_targets == NULL)
959 		return;
960 
961 	for (i = 0, t = info->i_targets; i < info->i_ntargets; i++, t++) {
962 		smb_tracef("[%d] \\\\%s\\%s", i, t->t_server, t->t_share);
963 		smb_tracef("[%d] state\t%X", i, t->t_state);
964 		smb_tracef("[%d] priority\t%d:%d", i, t->t_priority.p_class,
965 		    t->t_priority.p_rank);
966 	}
967 }
968 
969 /*
970  * Search the path specified by 'relpath' to see if it contains
971  * a DFS link starting from the last component. If a link is found
972  * the full path is returned in 'linkpath'
973  */
974 static boolean_t
975 dfs_namespace_findlink(const char *name, char *relpath,
976     char *linkpath, size_t bufsz)
977 {
978 	char rootdir[DFS_PATH_MAX];
979 	uint32_t stat;
980 	char *p;
981 
982 	if (dfs_namespace_path(name, rootdir, DFS_PATH_MAX) != ERROR_SUCCESS)
983 		return (B_FALSE);
984 
985 	(void) snprintf(linkpath, bufsz, "%s/%s", rootdir, relpath);
986 
987 	for (;;) {
988 		if (dfs_link_stat(linkpath, &stat) != ERROR_SUCCESS)
989 			return (B_FALSE);
990 
991 		if (stat == DFS_STAT_ISDFS)
992 			return (B_TRUE);
993 
994 		if ((p = strrchr(relpath, '/')) == NULL)
995 			return (B_FALSE);
996 		*p = '\0';
997 
998 		(void) snprintf(linkpath, bufsz, "%s/%s", rootdir, relpath);
999 	}
1000 
1001 	/*NOTREACHED*/
1002 	return (B_FALSE);
1003 }
1004 
1005 /*
1006  * Caches the specified namespace
1007  */
1008 static void *
1009 dfs_namespace_cache(void *arg)
1010 {
1011 	char *share = arg;
1012 	char uncpath[DFS_PATH_MAX];
1013 	smb_share_t si;
1014 
1015 	if (smb_shr_get(share, &si) != NERR_Success) {
1016 		free(share);
1017 		return (NULL);
1018 	}
1019 
1020 	(void) snprintf(uncpath, DFS_PATH_MAX, "\\\\%s\\%s", dfs_nbname, share);
1021 	(void) dfs_cache_add_byunc(uncpath, si.shr_path, DFS_OBJECT_ROOT);
1022 
1023 	dfs_cache_populate(uncpath, si.shr_path);
1024 
1025 	free(share);
1026 	return (NULL);
1027 }
1028 
1029 static int
1030 dfs_root_add(const char *rootdir, dfs_info_t *info)
1031 {
1032 	uint32_t status = ERROR_INTERNAL_ERROR;
1033 	int xfd;
1034 
1035 	(void) rw_wrlock(&dfs_root_rwl);
1036 	if ((xfd = dfs_root_xopen(rootdir, O_CREAT | O_TRUNC | O_RDWR)) > 0) {
1037 		status = dfs_root_xwrite(xfd, info);
1038 		dfs_root_xclose(xfd);
1039 	}
1040 	(void) rw_unlock(&dfs_root_rwl);
1041 
1042 	return (status);
1043 }
1044 
1045 /*
1046  * Deletes the specified root information
1047  */
1048 static uint32_t
1049 dfs_root_remove(const char *rootdir)
1050 {
1051 	int attrdirfd;
1052 	int err = 0;
1053 
1054 	(void) rw_wrlock(&dfs_root_rwl);
1055 
1056 	if ((attrdirfd = attropen(rootdir, ".", O_RDONLY)) > 0) {
1057 		if (unlinkat(attrdirfd, DFS_ROOT_XATTR, 0) == -1) {
1058 			if (errno != ENOENT)
1059 				err = errno;
1060 		}
1061 		(void) close(attrdirfd);
1062 	} else {
1063 		err = errno;
1064 	}
1065 
1066 	(void) rw_unlock(&dfs_root_rwl);
1067 
1068 	if (err != 0) {
1069 		syslog(LOG_DEBUG, "dfs: failed to remove root info %s (%d)",
1070 		    rootdir, err);
1071 		return (ERROR_INTERNAL_ERROR);
1072 	}
1073 
1074 	return (ERROR_SUCCESS);
1075 }
1076 
1077 /*
1078  * Opens DFS root directory's extended attribute with the given mode.
1079  */
1080 static int
1081 dfs_root_xopen(const char *rootdir, int oflag)
1082 {
1083 	int dfd;
1084 	int xfd = -1;
1085 	int err = 0;
1086 
1087 	if ((dfd = open(rootdir, O_RDONLY)) > 0) {
1088 		xfd = openat(dfd, DFS_ROOT_XATTR, oflag | O_XATTR, 0600);
1089 		if (xfd == -1)
1090 			err = errno;
1091 		(void) close(dfd);
1092 	} else {
1093 		err = errno;
1094 	}
1095 
1096 	if (err != 0) {
1097 		syslog(LOG_DEBUG, "dfs: failed to open root directory %s (%d)",
1098 		    rootdir, err);
1099 	}
1100 
1101 	return (xfd);
1102 }
1103 
1104 /*
1105  * Closes given extended attribute file descriptor
1106  */
1107 static void
1108 dfs_root_xclose(int xfd)
1109 {
1110 	(void) close(xfd);
1111 }
1112 
1113 /*
1114  * Writes the given DFS data in the DFS root directory's
1115  * extended attribute specified with xfd file descriptor.
1116  */
1117 static uint32_t
1118 dfs_root_xwrite(int xfd, dfs_info_t *info)
1119 {
1120 	size_t nbytes;
1121 	char *buf = NULL;
1122 	size_t buflen;
1123 	uint32_t status;
1124 
1125 	if ((status = dfs_root_encode(info, &buf, &buflen)) != ERROR_SUCCESS)
1126 		return (status);
1127 
1128 	(void) lseek(xfd, 0, SEEK_SET);
1129 	nbytes = write(xfd, buf, buflen);
1130 	free(buf);
1131 
1132 	return ((nbytes == buflen) ? ERROR_SUCCESS : ERROR_INTERNAL_ERROR);
1133 }
1134 
1135 /*
1136  * Reads DFS root information from its directory extended attribute
1137  * and parse it into given dfs_info_t structure
1138  */
1139 static uint32_t
1140 dfs_root_xread(int xfd, dfs_info_t *info, uint32_t infolvl)
1141 {
1142 	struct stat statbuf;
1143 	uint32_t status;
1144 	char *buf;
1145 
1146 	if (fstat(xfd, &statbuf) != 0)
1147 		return (ERROR_INTERNAL_ERROR);
1148 
1149 	if ((buf = malloc(statbuf.st_size)) == NULL)
1150 		return (ERROR_NOT_ENOUGH_MEMORY);
1151 
1152 	if (read(xfd, buf, statbuf.st_size) == statbuf.st_size)
1153 		status = dfs_root_decode(info, buf, statbuf.st_size, infolvl);
1154 	else
1155 		status = ERROR_INTERNAL_ERROR;
1156 
1157 	free(buf);
1158 	return (status);
1159 }
1160 
1161 /*
1162  * Encodes (packs) DFS information in 'info' into a flat
1163  * buffer in a name-value format. This function allocates a
1164  * buffer with appropriate size to contain all the information
1165  * so the caller MUST free the allocated memory by calling free().
1166  */
1167 static uint32_t
1168 dfs_root_encode(dfs_info_t *info, char **buf, size_t *bufsz)
1169 {
1170 	dfs_target_t *t;
1171 	nvlist_t *nvl;
1172 	int rc;
1173 
1174 	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
1175 		return (ERROR_NOT_ENOUGH_MEMORY);
1176 
1177 	rc = nvlist_add_string(nvl, "comment", info->i_comment);
1178 	rc |= nvlist_add_string(nvl, "guid", info->i_guid);
1179 	rc |= nvlist_add_uint32(nvl, "state", info->i_state);
1180 	rc |= nvlist_add_uint32(nvl, "timeout", info->i_timeout);
1181 	rc |= nvlist_add_uint32(nvl, "propflags", info->i_propflags);
1182 	t = info->i_targets;
1183 	rc |= nvlist_add_string(nvl, "t_server", t->t_server);
1184 	rc |= nvlist_add_string(nvl, "t_share", t->t_share);
1185 	rc |= nvlist_add_uint32(nvl, "t_state", t->t_state);
1186 
1187 	if (rc == 0)
1188 		rc = nvlist_pack(nvl, buf, bufsz, NV_ENCODE_NATIVE, 0);
1189 
1190 	nvlist_free(nvl);
1191 
1192 	return ((rc == 0) ? ERROR_SUCCESS : ERROR_INTERNAL_ERROR);
1193 }
1194 
1195 /*
1196  * Decodes (unpack) provided buffer which contains a list of name-value
1197  * pairs into given dfs_info_t structure
1198  */
1199 static uint32_t
1200 dfs_root_decode(dfs_info_t *info, char *buf, size_t bufsz, uint32_t infolvl)
1201 {
1202 	nvlist_t *nvl;
1203 	char *cmnt, *guid;
1204 	char *t_server, *t_share;
1205 	uint32_t t_state;
1206 	int rc;
1207 
1208 	if (nvlist_unpack(buf, bufsz, &nvl, 0) != 0)
1209 		return (ERROR_INTERNAL_ERROR);
1210 
1211 	rc = nvlist_lookup_string(nvl, "comment", &cmnt);
1212 	rc |= nvlist_lookup_string(nvl, "guid", &guid);
1213 	rc |= nvlist_lookup_uint32(nvl, "state", &info->i_state);
1214 	rc |= nvlist_lookup_uint32(nvl, "timeout", &info->i_timeout);
1215 	rc |= nvlist_lookup_uint32(nvl, "propflags", &info->i_propflags);
1216 
1217 	if (rc != 0) {
1218 		nvlist_free(nvl);
1219 		return (ERROR_INTERNAL_ERROR);
1220 	}
1221 
1222 	(void) strlcpy(info->i_comment, (cmnt) ? cmnt : "",
1223 	    sizeof (info->i_comment));
1224 	(void) strlcpy(info->i_guid, (guid) ? guid : "", sizeof (info->i_guid));
1225 
1226 	info->i_targets = NULL;
1227 	info->i_ntargets = 1;
1228 
1229 	switch (infolvl) {
1230 	case DFS_INFO_ALL:
1231 	case 3:
1232 	case 4:
1233 	case 6:
1234 	case 9:
1235 		/* need target information */
1236 		break;
1237 	default:
1238 		nvlist_free(nvl);
1239 		return (ERROR_SUCCESS);
1240 	}
1241 
1242 	info->i_targets = malloc(sizeof (dfs_target_t));
1243 	if (info->i_targets == NULL) {
1244 		nvlist_free(nvl);
1245 		return (ERROR_NOT_ENOUGH_MEMORY);
1246 	}
1247 
1248 	rc = nvlist_lookup_string(nvl, "t_server", &t_server);
1249 	rc |= nvlist_lookup_string(nvl, "t_share", &t_share);
1250 	rc |= nvlist_lookup_uint32(nvl, "t_state", &t_state);
1251 	if (rc != 0) {
1252 		nvlist_free(nvl);
1253 		free(info->i_targets);
1254 		return (ERROR_INTERNAL_ERROR);
1255 	}
1256 	dfs_target_init(info->i_targets, t_server, t_share, t_state);
1257 
1258 	nvlist_free(nvl);
1259 	return (ERROR_SUCCESS);
1260 }
1261 
1262 /*
1263  * Determines if the passed state is valid for a DFS root
1264  *
1265  * This is based on test results against Win2003 and in some cases
1266  * does not match [MS-DFSNM] spec.
1267  */
1268 static uint32_t
1269 dfs_root_isvalidstate(uint32_t state)
1270 {
1271 	switch (state) {
1272 	case DFS_VOLUME_STATE_OK:
1273 	case DFS_VOLUME_STATE_RESYNCHRONIZE:
1274 		return (ERROR_SUCCESS);
1275 
1276 	case DFS_VOLUME_STATE_INCONSISTENT:
1277 	case DFS_VOLUME_STATE_FORCE_SYNC:
1278 		return (ERROR_INVALID_PARAMETER);
1279 
1280 	case DFS_VOLUME_STATE_OFFLINE:
1281 	case DFS_VOLUME_STATE_ONLINE:
1282 	case DFS_VOLUME_STATE_STANDBY:
1283 		return (ERROR_NOT_SUPPORTED);
1284 	default:
1285 		break;
1286 	}
1287 
1288 	return (ERROR_INVALID_PARAMETER);
1289 }
1290 
1291 /*
1292  * Decodes the link info from given string buffer (buf) into
1293  * dfs_info_t structure.
1294  */
1295 static uint32_t
1296 dfs_link_decode(dfs_info_t *info, char *buf, uint32_t infolvl)
1297 {
1298 	char *lfield[DFS_LINK_HDR_NFIELDS];
1299 	dfs_target_t *t;
1300 	uint32_t linkver;
1301 	uint32_t cmntlen;
1302 	uint32_t cpylen;
1303 	int i, j;
1304 
1305 	/*
1306 	 * Header format
1307 	 * ver:state:prop:timeout:guid:ntarget:cmntlen:comment:
1308 	 */
1309 	for (i = 0; i < DFS_LINK_HDR_NFIELDS; i++) {
1310 		if ((lfield[i] = strsep((char **)&buf, ":")) == NULL)
1311 			return (ERROR_INVALID_DATA);
1312 	}
1313 
1314 	i = 0;
1315 	linkver = strtoul(lfield[i++], NULL, 10);
1316 	if (linkver != DFS_LINK_V1)
1317 		return (ERROR_INVALID_DATA);
1318 
1319 	info->i_state = strtoul(lfield[i++], NULL, 10);
1320 	info->i_propflags = strtoul(lfield[i++], NULL, 10);
1321 	info->i_timeout = strtoul(lfield[i++], NULL, 10);
1322 	(void) strlcpy(info->i_guid, lfield[i++], sizeof (info->i_guid));
1323 	info->i_ntargets = strtoul(lfield[i++], NULL, 10);
1324 	info->i_targets = NULL;
1325 
1326 	cpylen = cmntlen = strtoul(lfield[i++], NULL, 10);
1327 
1328 	if (cmntlen > sizeof (info->i_comment))
1329 		cpylen = sizeof (info->i_comment);
1330 	else if (cmntlen != 0)
1331 		cpylen = cmntlen + 1;
1332 
1333 	(void) strlcpy(info->i_comment, buf, cpylen);
1334 	buf += (cmntlen + 1);
1335 
1336 	switch (infolvl) {
1337 	case DFS_INFO_ALL:
1338 	case 3:
1339 	case 4:
1340 	case 6:
1341 	case 9:
1342 		/* need target information */
1343 		break;
1344 	default:
1345 		return (ERROR_SUCCESS);
1346 	}
1347 
1348 	info->i_targets = calloc(info->i_ntargets, sizeof (dfs_target_t));
1349 	if (info->i_targets == NULL)
1350 		return (ERROR_NOT_ENOUGH_MEMORY);
1351 
1352 	/*
1353 	 * Format for each target
1354 	 * server:share:state:class:rank
1355 	 */
1356 	for (i = 0, t = info->i_targets; i < info->i_ntargets; i++, t++) {
1357 		for (j = 0; j < DFS_LINK_TRGT_NFIELDS; j++) {
1358 			if ((lfield[j] = strsep((char **)&buf, ":")) == NULL) {
1359 				dfs_info_free(info);
1360 				return (ERROR_INVALID_DATA);
1361 			}
1362 		}
1363 
1364 		(void) strlcpy(t->t_server, lfield[0], sizeof (t->t_server));
1365 		(void) strlcpy(t->t_share, lfield[1], sizeof (t->t_share));
1366 		t->t_state = strtoul(lfield[2], NULL, 10);
1367 		t->t_priority.p_class = strtoul(lfield[3], NULL, 10);
1368 		t->t_priority.p_rank = strtoul(lfield[4], NULL, 10);
1369 	}
1370 
1371 	return (ERROR_SUCCESS);
1372 }
1373 
1374 /*
1375  * Encodes given link information (info)
1376  */
1377 static uint32_t
1378 dfs_link_encode(dfs_info_t *info, char *buf, size_t bufsz)
1379 {
1380 	char linkdata[MAXREPARSELEN];
1381 	dfs_target_t *t;
1382 	int i, sz;
1383 
1384 	/*
1385 	 * Header format
1386 	 * ver:state:prop:timeout:guid:ntarget:cmntlen:comment
1387 	 */
1388 	sz = snprintf(buf, bufsz, "%u:%u:%u:%u:%s:%u:%u:%s",
1389 	    DFS_LINK_V1, info->i_state, info->i_propflags, info->i_timeout,
1390 	    info->i_guid, info->i_ntargets,
1391 	    strlen(info->i_comment), info->i_comment);
1392 
1393 	if (sz > bufsz) {
1394 		syslog(LOG_WARNING, "dfs: link data is too large");
1395 		dfs_info_trace("DFS link encode", info);
1396 		return (ERROR_INTERNAL_ERROR);
1397 	}
1398 
1399 	/*
1400 	 * Format for each target
1401 	 * :server:share:state:class:rank
1402 	 */
1403 	bufsz -= sz;
1404 	for (i = 0, t = info->i_targets; i < info->i_ntargets; i++, t++) {
1405 		if (strchr(t->t_server, ':') || strchr(t->t_share, ':'))
1406 			return (ERROR_INVALID_NAME);
1407 
1408 		sz = snprintf(linkdata, MAXREPARSELEN, ":%s:%s:%u:%u:%u",
1409 		    t->t_server, t->t_share, t->t_state,
1410 		    t->t_priority.p_class, t->t_priority.p_rank);
1411 		if (sz > bufsz) {
1412 			syslog(LOG_WARNING, "dfs: link data is too large");
1413 			dfs_info_trace("DFS link encode", info);
1414 			return (ERROR_INTERNAL_ERROR);
1415 		}
1416 		(void) strcat(buf, linkdata);
1417 		bufsz -= sz;
1418 	}
1419 
1420 	return (ERROR_SUCCESS);
1421 }
1422 
1423 /*
1424  * Stores given information for the specified link
1425  */
1426 static uint32_t
1427 dfs_link_commit(const char *path, dfs_info_t *info)
1428 {
1429 	char linkdata[MAXREPARSELEN];
1430 	uint32_t status;
1431 	int rc;
1432 
1433 	status = dfs_link_encode(info, linkdata, MAXREPARSELEN);
1434 	if (status == ERROR_SUCCESS) {
1435 		rc = smb_reparse_svcadd(path, DFS_REPARSE_SVCTYPE, linkdata);
1436 		if (rc != 0)
1437 			status = ERROR_INTERNAL_ERROR;
1438 	}
1439 
1440 	return (status);
1441 }
1442 
1443 /*
1444  * Determines if the passed state is valid for a link
1445  */
1446 static boolean_t
1447 dfs_link_isvalidstate(uint32_t state)
1448 {
1449 	return (state == DFS_VOLUME_STATE_OK ||
1450 	    state == DFS_VOLUME_STATE_OFFLINE ||
1451 	    state == DFS_VOLUME_STATE_ONLINE);
1452 }
1453 
1454 /*
1455  * Initializes the given target structure (t) with provided information.
1456  */
1457 static void
1458 dfs_target_init(dfs_target_t *t, const char *srv, const char *share,
1459     uint32_t state)
1460 {
1461 	(void) strlcpy(t->t_server, (srv) ? srv : "", sizeof (t->t_server));
1462 	(void) strlcpy(t->t_share, (share) ? share : "", sizeof (t->t_share));
1463 	t->t_state = state;
1464 	t->t_priority.p_class = DfsSiteCostNormalPriorityClass;
1465 	t->t_priority.p_rank = 0;
1466 }
1467 
1468 /*
1469  * Lookup the specified target (server, share) in the given
1470  * target list (targets). If there is a match its index is
1471  * returned, otherwise -1 will be returned.
1472  */
1473 static int
1474 dfs_target_find(dfs_target_t *targets, uint32_t ntargets,
1475     const char *server, const char *share)
1476 {
1477 	dfs_target_t *t;
1478 	int i;
1479 
1480 	for (i = 0, t = targets; i < ntargets; i++, t++) {
1481 		if ((smb_strcasecmp(t->t_server, server, 0) == 0) &&
1482 		    (smb_strcasecmp(t->t_share, share, 0) == 0))
1483 			return (i);
1484 	}
1485 
1486 	return (-1);
1487 }
1488 
1489 /*
1490  * Determines if the passed state is valid for a link/root target
1491  */
1492 static boolean_t
1493 dfs_target_isvalidstate(uint32_t state)
1494 {
1495 	return (state == DFS_STORAGE_STATE_ONLINE ||
1496 	    state == DFS_STORAGE_STATE_OFFLINE);
1497 }
1498 
1499 /*
1500  * Cache compare function, the key is UNC path
1501  */
1502 static int
1503 dfs_cache_cmp(const void *p1, const void *p2)
1504 {
1505 	smb_cache_node_t *cn1 = (smb_cache_node_t *)p1;
1506 	smb_cache_node_t *cn2 = (smb_cache_node_t *)p2;
1507 	dfs_nscnode_t *dn1 = cn1->cn_data;
1508 	dfs_nscnode_t *dn2 = cn2->cn_data;
1509 	int rc;
1510 
1511 	rc = smb_strcasecmp(dn1->nsc_uncpath, dn2->nsc_uncpath, 0);
1512 
1513 	if (rc < 0)
1514 		return (-1);
1515 
1516 	if (rc > 0)
1517 		return (1);
1518 
1519 	return (0);
1520 }
1521 
1522 /*
1523  * Adds an entry with given UNC and filesystem path and the specified
1524  * entry type (i.e. root/link) to the namespace cache.
1525  */
1526 static uint32_t
1527 dfs_cache_add_byunc(const char *uncpath, const char *fspath, uint32_t type)
1528 {
1529 	dfs_nscnode_t *dn;
1530 	uint32_t status = ERROR_SUCCESS;
1531 
1532 	if ((dn = malloc(sizeof (dfs_nscnode_t))) == NULL)
1533 		return (ERROR_NOT_ENOUGH_MEMORY);
1534 
1535 	(void) strlcpy(dn->nsc_uncpath, uncpath, sizeof (dn->nsc_uncpath));
1536 	(void) strlcpy(dn->nsc_fspath, fspath, sizeof (dn->nsc_fspath));
1537 	dn->nsc_type = type;
1538 	if (smb_cache_add(&dfs_nscache, dn, SMB_CACHE_ADD) != 0) {
1539 		free(dn);
1540 		status = ERROR_INTERNAL_ERROR;
1541 	}
1542 
1543 	return (status);
1544 }
1545 
1546 /*
1547  * starting from DFS root directory, scans the tree for DFS links
1548  * and adds them to the cache.
1549  */
1550 static void
1551 dfs_cache_populate(const char *unc_prefix, const char *dir)
1552 {
1553 	char fspath[DFS_PATH_MAX];
1554 	char uncpath[DFS_PATH_MAX];
1555 	char *fname;
1556 	int nentries, i;
1557 	struct dirent **entry_list;
1558 	uint32_t stat;
1559 
1560 	nentries = scandir(dir, &entry_list, NULL, NULL);
1561 	if (nentries == -1)
1562 		return;
1563 
1564 	for (i = 0; i < nentries; i++) {
1565 		fname = entry_list[i]->d_name;
1566 
1567 		if (strcmp(fname, ".") == 0 ||
1568 		    strcmp(fname, "..") == 0) {
1569 			free(entry_list[i]);
1570 			continue;
1571 		}
1572 
1573 		(void) snprintf(fspath, DFS_PATH_MAX, "%s/%s", dir, fname);
1574 		(void) snprintf(uncpath, DFS_PATH_MAX, "%s\\%s", unc_prefix,
1575 		    fname);
1576 
1577 		if (dfs_path_isdir(fspath)) {
1578 			dfs_cache_populate(uncpath, fspath);
1579 		} else if (dfs_link_stat(fspath, &stat) == ERROR_SUCCESS) {
1580 			if (stat == DFS_STAT_ISDFS)
1581 				(void) dfs_cache_add_byunc(uncpath, fspath,
1582 				    DFS_OBJECT_LINK);
1583 		}
1584 
1585 		free(entry_list[i]);
1586 	}
1587 
1588 	for (; i < nentries; i++)
1589 		free(entry_list[i]);
1590 
1591 	free(entry_list);
1592 }
1593 
1594 /*
1595  * Determines whether the given path is a directory.
1596  */
1597 static boolean_t
1598 dfs_path_isdir(const char *path)
1599 {
1600 	struct stat statbuf;
1601 
1602 	if (lstat(path, &statbuf) != 0)
1603 		return (B_FALSE);
1604 
1605 	return ((statbuf.st_mode & S_IFMT) == S_IFDIR);
1606 }
1607 
1608 /*
1609  * Validates the given state based on the object type (root/link)
1610  * and whether it is the object's state or its target's state
1611  */
1612 static uint32_t
1613 dfs_isvalidstate(uint32_t state, uint32_t type, boolean_t target)
1614 {
1615 	uint32_t status = ERROR_SUCCESS;
1616 
1617 	if (type == DFS_OBJECT_ROOT) {
1618 		if (!target)
1619 			return (dfs_root_isvalidstate(state));
1620 
1621 		if (!dfs_target_isvalidstate(state))
1622 			status = ERROR_INVALID_PARAMETER;
1623 		else if (state == DFS_STORAGE_STATE_OFFLINE)
1624 			status = ERROR_NOT_SUPPORTED;
1625 	} else {
1626 		if (!target) {
1627 			if (!dfs_link_isvalidstate(state))
1628 				status = ERROR_INVALID_PARAMETER;
1629 		} else {
1630 			if (!dfs_target_isvalidstate(state))
1631 				status = ERROR_INVALID_PARAMETER;
1632 		}
1633 	}
1634 
1635 	return (status);
1636 }
1637 
1638 /*
1639  * Based on the specified information level (infolvl) copy parts of the
1640  * information provided through newinfo into the existing information
1641  * (info) for the given object.
1642  */
1643 static uint32_t
1644 dfs_modinfo(uint32_t type, dfs_info_t *info, dfs_info_t *newinfo,
1645     uint32_t infolvl)
1646 {
1647 	boolean_t target_op = B_FALSE;
1648 	uint32_t status = ERROR_SUCCESS;
1649 	uint32_t state;
1650 	int target_idx;
1651 
1652 	if (newinfo->i_targets != NULL) {
1653 		target_idx = dfs_target_find(info->i_targets, info->i_ntargets,
1654 		    newinfo->i_targets->t_server, newinfo->i_targets->t_share);
1655 		if (target_idx == -1)
1656 			return (ERROR_FILE_NOT_FOUND);
1657 		target_op = B_TRUE;
1658 	}
1659 
1660 	switch (infolvl) {
1661 	case 100:
1662 		(void) strlcpy(info->i_comment, newinfo->i_comment,
1663 		    sizeof (newinfo->i_comment));
1664 		break;
1665 
1666 	case 101:
1667 		state = (target_op)
1668 		    ? newinfo->i_targets->t_state : newinfo->i_state;
1669 		status = dfs_isvalidstate(state, type, target_op);
1670 		if (status != ERROR_SUCCESS)
1671 			return (status);
1672 
1673 		if (!target_op) {
1674 			/*
1675 			 * states specified by this mask should not be stored
1676 			 */
1677 			if (state & DFS_VOLUME_STATES_SRV_OPS)
1678 				return (ERROR_SUCCESS);
1679 
1680 			info->i_state = state;
1681 		} else {
1682 			info->i_targets[target_idx].t_state = state;
1683 		}
1684 		break;
1685 
1686 	case 102:
1687 		info->i_timeout = newinfo->i_timeout;
1688 		break;
1689 
1690 	case 103:
1691 		info->i_propflags = newinfo->i_propflags;
1692 		break;
1693 
1694 	case 104:
1695 		info->i_targets[target_idx].t_priority =
1696 		    newinfo->i_targets->t_priority;
1697 		break;
1698 
1699 	case 105:
1700 		(void) strlcpy(info->i_comment, newinfo->i_comment,
1701 		    sizeof (newinfo->i_comment));
1702 		info->i_state = newinfo->i_state;
1703 		info->i_timeout = newinfo->i_timeout;
1704 		info->i_propflags = newinfo->i_propflags;
1705 		break;
1706 
1707 	default:
1708 		status = ERROR_INVALID_LEVEL;
1709 	}
1710 
1711 	return (status);
1712 }
1713