xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_directory.c (revision ef2504f26d1ea5859db9838255bb63f488f1b050)
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 <smbsrv/nterror.h>
27 #include <smbsrv/ntstatus.h>
28 #include <smbsrv/smbinfo.h>
29 #include <smbsrv/smb_incl.h>
30 #include <smbsrv/smb_fsops.h>
31 
32 typedef struct smb_dirpath {
33 	char	*sp_path;	/* Original path */
34 	char	*sp_curp;	/* Current pointer into the original path */
35 	smb_request_t *sp_sr;	/* Current request pointer */
36 } smb_dirpath_t;
37 
38 static smb_dirpath_t *smb_dirpath_new(smb_request_t *);
39 static int smb_dirpath_next(smb_dirpath_t *);
40 static boolean_t smb_dirpath_isvalid(const char *);
41 
42 /*
43  * The create directory message is sent to create a new directory.  The
44  * appropriate Tid and additional pathname are passed.  The directory must
45  * not exist for it to be created.
46  *
47  * Client Request                     Description
48  * ================================== =================================
49  * UCHAR WordCount;                   Count of parameter words = 0
50  * USHORT ByteCount;                  Count of data bytes; min = 2
51  * UCHAR BufferFormat;                0x04
52  * STRING DirectoryName[];            Directory name
53  *
54  * Servers require clients to have at least create permission for the
55  * subtree containing the directory in order to create a new directory.
56  * The creator's access rights to the new directory are be determined by
57  * local policy on the server.
58  *
59  * Server Response                    Description
60  * ================================== =================================
61  * UCHAR WordCount;                   Count of parameter words = 0
62  * USHORT ByteCount;                  Count of data bytes = 0
63  */
64 smb_sdrc_t
65 smb_pre_create_directory(smb_request_t *sr)
66 {
67 	int rc;
68 
69 	rc = smbsr_decode_data(sr, "%S", sr,
70 	    &sr->arg.dirop.fqi.fq_path.pn_path);
71 
72 	DTRACE_SMB_2(op__CreateDirectory__start, smb_request_t *, sr,
73 	    struct dirop *, &sr->arg.dirop);
74 
75 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
76 }
77 
78 void
79 smb_post_create_directory(smb_request_t *sr)
80 {
81 	DTRACE_SMB_1(op__CreateDirectory__done, smb_request_t *, sr);
82 }
83 
84 /*
85  * smb_com_create_directory
86  *
87  * It is possible to get a full pathname here and the client expects any
88  * or all of the components to be created if they don't already exist.
89  */
90 smb_sdrc_t
91 smb_com_create_directory(smb_request_t *sr)
92 {
93 	smb_dirpath_t *spp;
94 	smb_attr_t *attr;
95 	DWORD status;
96 	int rc = 0;
97 
98 	if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) {
99 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
100 		    ERRDOS, ERROR_ACCESS_DENIED);
101 		return (SDRC_ERROR);
102 	}
103 
104 	if (!smb_dirpath_isvalid(sr->arg.dirop.fqi.fq_path.pn_path)) {
105 		smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD,
106 		    ERRDOS, ERROR_BAD_PATHNAME);
107 		return (SDRC_ERROR);
108 	}
109 
110 	status = smb_validate_dirname(sr->arg.dirop.fqi.fq_path.pn_path);
111 	if (status != 0) {
112 		smbsr_error(sr, status, ERRDOS, ERROR_INVALID_NAME);
113 		return (SDRC_ERROR);
114 	}
115 
116 	/*
117 	 * Try each component of the path.  EEXIST on path
118 	 * components is okay except on the last one.
119 	 */
120 	spp = smb_dirpath_new(sr);
121 
122 	while (smb_dirpath_next(spp)) {
123 		rc = smb_common_create_directory(sr);
124 
125 		switch (rc) {
126 		case 0:
127 			break;
128 		case EEXIST:
129 			attr = &sr->arg.dirop.fqi.fq_fattr;
130 
131 			if (attr->sa_vattr.va_type != VDIR) {
132 				smbsr_error(sr, NT_STATUS_OBJECT_NAME_COLLISION,
133 				    ERRDOS, ERROR_PATH_NOT_FOUND);
134 				return (SDRC_ERROR);
135 			}
136 			break;
137 		case ENOENT:
138 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
139 			    ERRDOS, ERROR_FILE_NOT_FOUND);
140 			return (SDRC_ERROR);
141 		case ENOTDIR:
142 			smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY,
143 			    ERRDOS, ERROR_PATH_NOT_FOUND);
144 			return (SDRC_ERROR);
145 		default:
146 			smbsr_errno(sr, rc);
147 			return (SDRC_ERROR);
148 		}
149 	}
150 
151 	if (rc != 0) {
152 		smbsr_errno(sr, rc);
153 		return (SDRC_ERROR);
154 	}
155 
156 	rc = smbsr_encode_empty_result(sr);
157 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
158 }
159 
160 /*
161  * smb_validate_dirname
162  *
163  * Very basic directory name validation: checks for colons in a path.
164  * Need to skip the drive prefix since it contains a colon.
165  *
166  * Returns NT_STATUS_SUCCESS if the name is valid,
167  *         otherwise NT_STATUS_NOT_A_DIRECTORY.
168  */
169 uint32_t
170 smb_validate_dirname(char *path)
171 {
172 	char *name;
173 
174 	if ((name = path) != 0) {
175 		name += strspn(name, "\\");
176 
177 		if (strchr(name, ':') != 0)
178 			return (NT_STATUS_NOT_A_DIRECTORY);
179 	}
180 
181 	return (NT_STATUS_SUCCESS);
182 }
183 
184 /*
185  * smb_common_create_directory
186  *
187  * Currently called from:
188  *		smb_com_create_directory
189  *		smb_com_trans2_create_directory
190  *
191  * Returns errno values.
192  */
193 int
194 smb_common_create_directory(smb_request_t *sr)
195 {
196 	int rc;
197 	smb_attr_t new_attr;
198 	smb_node_t *dnode;
199 	smb_node_t *node;
200 
201 	sr->arg.dirop.fqi.fq_sattr = 0;
202 
203 	rc = smbd_fs_query(sr, &sr->arg.dirop.fqi, FQM_PATH_MUST_NOT_EXIST);
204 	if (rc)
205 		return (rc);
206 
207 	/*
208 	 * Because of FQM_PATH_MUST_NOT_EXIST and the successful return
209 	 * value, only fqi.fq_dnode has a valid parameter (fqi.fq_fnode
210 	 * is NULL).
211 	 */
212 	dnode = sr->arg.dirop.fqi.fq_dnode;
213 
214 	rc = smb_fsop_access(sr, sr->user_cr, dnode, FILE_ADD_SUBDIRECTORY);
215 	if (rc != NT_STATUS_SUCCESS)
216 		return (EACCES);
217 
218 	/*
219 	 * Explicitly set sa_dosattr, otherwise the file system may
220 	 * automatically apply FILE_ATTRIBUTE_ARCHIVE which, for
221 	 * compatibility with windows servers, should not be set.
222 	 */
223 	bzero(&new_attr, sizeof (new_attr));
224 	new_attr.sa_dosattr = FILE_ATTRIBUTE_DIRECTORY;
225 	new_attr.sa_vattr.va_type = VDIR;
226 	new_attr.sa_vattr.va_mode = 0777;
227 	new_attr.sa_mask = SMB_AT_TYPE | SMB_AT_MODE | SMB_AT_DOSATTR;
228 
229 	if ((rc = smb_fsop_mkdir(sr, sr->user_cr, dnode,
230 	    sr->arg.dirop.fqi.fq_last_comp, &new_attr,
231 	    &sr->arg.dirop.fqi.fq_fnode,
232 	    &sr->arg.dirop.fqi.fq_fattr)) != 0) {
233 		smb_node_release(dnode);
234 		SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
235 		return (rc);
236 	}
237 
238 	node = sr->arg.dirop.fqi.fq_fnode;
239 	node->flags |= NODE_FLAGS_CREATED;
240 
241 	sr->arg.open.create_options = FILE_DIRECTORY_FILE;
242 
243 	smb_node_release(node);
244 	smb_node_release(dnode);
245 	SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
246 	return (0);
247 }
248 
249 static smb_dirpath_t *
250 smb_dirpath_new(smb_request_t *sr)
251 {
252 	int pathLen;
253 	char *xpath;
254 	smb_dirpath_t *spp;
255 
256 	/* Malloc from the request storage area. This is freed automatically */
257 	/* so we don't need to worry about freeing it later */
258 	spp = smbsr_malloc(&sr->request_storage, sizeof (smb_dirpath_t));
259 	spp->sp_path = sr->arg.dirop.fqi.fq_path.pn_path;
260 	pathLen = strlen(spp->sp_path);
261 	spp->sp_curp = spp->sp_path;
262 	xpath = smbsr_malloc(&sr->request_storage, pathLen + 1);
263 	sr->arg.dirop.fqi.fq_path.pn_path = xpath;
264 	spp->sp_sr = sr;
265 
266 	return (spp);
267 }
268 
269 /*
270  * Perhaps somewhat dangerous since everything happens as a side effect. The
271  * returns 1 if there is a valid component updated to the fqi, 0 otherwise.
272  */
273 static int
274 smb_dirpath_next(smb_dirpath_t *spp)
275 {
276 	char *xp;
277 	int xlen;
278 
279 	if (spp == 0)
280 		return (0);
281 
282 	/* Move the index to the "next" "\" and copy the path to the fqi */
283 	/* path for the next component. */
284 
285 	/* First look for the next component */
286 	while (*spp->sp_curp == '\\')
287 		spp->sp_curp++;
288 
289 	/* Now get to the end of the component */
290 	xp = spp->sp_curp; /* Remember from where we started */
291 	while (*spp->sp_curp != '\0' && *spp->sp_curp != '\\') {
292 		spp->sp_curp++;
293 	}
294 
295 	/* If we made no progress, we are done */
296 	if (xp == spp->sp_curp)
297 		return (0);
298 
299 	/*
300 	 * Now copy the original path up to but not including our current
301 	 * pointer
302 	 */
303 
304 	/*LINTED E_PTRDIFF_OVERFLOW*/
305 	xlen = spp->sp_curp - spp->sp_path;
306 	(void) strncpy(spp->sp_sr->arg.dirop.fqi.fq_path.pn_path,
307 	    spp->sp_path, xlen);
308 
309 	/* Now NULL terminate it */
310 	spp->sp_sr->arg.dirop.fqi.fq_path.pn_path[xlen] = '\0';
311 	return (1);
312 }
313 
314 /*
315  * The delete directory message is sent to delete an empty directory. The
316  * appropriate Tid and additional pathname are passed. The directory must
317  * be empty for it to be deleted.
318  *
319  * NT supports a hidden permission known as File Delete Child (FDC). If
320  * the user has FullControl access to a directory, the user is permitted
321  * to delete any object in the directory regardless of the permissions
322  * on the object.
323  *
324  * Client Request                     Description
325  * ================================== =================================
326  * UCHAR WordCount;                   Count of parameter words = 0
327  * USHORT ByteCount;                  Count of data bytes; min = 2
328  * UCHAR BufferFormat;                0x04
329  * STRING DirectoryName[];            Directory name
330  *
331  * The directory to be deleted cannot be the root of the share specified
332  * by Tid.
333  *
334  * Server Response                    Description
335  * ================================== =================================
336  * UCHAR WordCount;                   Count of parameter words = 0
337  * USHORT ByteCount;                  Count of data bytes = 0
338  */
339 smb_sdrc_t
340 smb_pre_delete_directory(smb_request_t *sr)
341 {
342 	int rc;
343 
344 	rc = smbsr_decode_data(sr, "%S", sr,
345 	    &sr->arg.dirop.fqi.fq_path.pn_path);
346 
347 	DTRACE_SMB_2(op__DeleteDirectory__start, smb_request_t *, sr,
348 	    struct dirop *, &sr->arg.dirop);
349 
350 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
351 }
352 
353 void
354 smb_post_delete_directory(smb_request_t *sr)
355 {
356 	DTRACE_SMB_1(op__DeleteDirectory__done, smb_request_t *, sr);
357 }
358 
359 smb_sdrc_t
360 smb_com_delete_directory(smb_request_t *sr)
361 {
362 	smb_node_t *dnode;
363 	smb_attr_t *attr;
364 	int rc;
365 	uint32_t flags = 0;
366 
367 	if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) {
368 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
369 		    ERRDOS, ERROR_ACCESS_DENIED);
370 		return (SDRC_ERROR);
371 	}
372 
373 	sr->arg.dirop.fqi.fq_sattr = 0;
374 
375 	rc = smbd_fs_query(sr, &sr->arg.dirop.fqi, FQM_PATH_MUST_EXIST);
376 	if (rc) {
377 		if (rc == ENOENT)
378 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
379 			    ERRDOS, ERROR_FILE_NOT_FOUND);
380 		else
381 			smbsr_errno(sr, rc);
382 		return (SDRC_ERROR);
383 	}
384 
385 	attr = &sr->arg.dirop.fqi.fq_fattr;
386 	if (attr->sa_vattr.va_type != VDIR) {
387 		smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY,
388 		    ERRDOS, ERROR_PATH_NOT_FOUND);
389 		return (SDRC_ERROR);
390 	}
391 
392 	dnode = sr->arg.dirop.fqi.fq_fnode;
393 	rc = smb_fsop_access(sr, sr->user_cr, dnode, DELETE);
394 
395 	if ((rc != NT_STATUS_SUCCESS) ||
396 	    (dnode->attr.sa_dosattr & FILE_ATTRIBUTE_READONLY)) {
397 		smb_node_release(dnode);
398 		smb_node_release(sr->arg.dirop.fqi.fq_dnode);
399 		SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
400 		smbsr_error(sr, NT_STATUS_CANNOT_DELETE,
401 		    ERRDOS, ERROR_ACCESS_DENIED);
402 		return (SDRC_ERROR);
403 	}
404 
405 	smb_node_release(dnode);
406 
407 	dnode = sr->arg.dirop.fqi.fq_dnode;
408 
409 	if (SMB_TREE_SUPPORTS_CATIA(sr))
410 		flags |= SMB_CATIA;
411 
412 	rc = smb_fsop_rmdir(sr, sr->user_cr, dnode,
413 	    sr->arg.dirop.fqi.fq_od_name, flags);
414 	if (rc != 0) {
415 		smb_node_release(dnode);
416 		SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
417 		if (rc == EEXIST)
418 			smbsr_error(sr, NT_STATUS_DIRECTORY_NOT_EMPTY,
419 			    ERRDOS, ERROR_DIR_NOT_EMPTY);
420 		else
421 			smbsr_errno(sr, rc);
422 		return (SDRC_ERROR);
423 	}
424 
425 	smb_node_release(dnode);
426 	SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
427 
428 	rc = smbsr_encode_empty_result(sr);
429 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
430 }
431 
432 /*
433  * This SMB is used to verify that a path exists and is a directory.  No
434  * error is returned if the given path exists and the client has read
435  * access to it.  Client machines which maintain a concept of a "working
436  * directory" will find this useful to verify the validity of a "change
437  * working directory" command.  Note that the servers do NOT have a concept
438  * of working directory for a particular client.  The client must always
439  * supply full pathnames relative to the Tid in the SMB header.
440  *
441  * Client Request                     Description
442  * ================================== =================================
443  *
444  * UCHAR WordCount;                   Count of parameter words = 0
445  * USHORT ByteCount;                  Count of data bytes;    min = 2
446  * UCHAR BufferFormat;                0x04
447  * STRING DirectoryPath[];            Directory path
448  *
449  * Server Response                    Description
450  * ================================== =================================
451  *
452  * UCHAR WordCount;                   Count of parameter words = 0
453  * USHORT ByteCount;                  Count of data bytes = 0
454  *
455  * DOS clients, in particular, depend on ERRbadpath if the directory is
456  * not found.
457  */
458 smb_sdrc_t
459 smb_pre_check_directory(smb_request_t *sr)
460 {
461 	int rc;
462 
463 	rc = smbsr_decode_data(sr, "%S", sr,
464 	    &sr->arg.dirop.fqi.fq_path.pn_path);
465 
466 	DTRACE_SMB_2(op__CheckDirectory__start, smb_request_t *, sr,
467 	    struct dirop *, &sr->arg.dirop);
468 
469 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
470 }
471 
472 void
473 smb_post_check_directory(smb_request_t *sr)
474 {
475 	DTRACE_SMB_1(op__CheckDirectory__done, smb_request_t *, sr);
476 }
477 
478 smb_sdrc_t
479 smb_com_check_directory(smb_request_t *sr)
480 {
481 	smb_node_t *dnode;
482 	int rc;
483 
484 	if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) {
485 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS,
486 		    ERROR_ACCESS_DENIED);
487 		return (SDRC_ERROR);
488 	}
489 
490 	if (sr->arg.dirop.fqi.fq_path.pn_path[0] == '\0') {
491 		rc = smbsr_encode_empty_result(sr);
492 		return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
493 	}
494 
495 	if (!smb_dirpath_isvalid(sr->arg.dirop.fqi.fq_path.pn_path)) {
496 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
497 		    ERRDOS, ERROR_PATH_NOT_FOUND);
498 		return (SDRC_ERROR);
499 	}
500 
501 	sr->arg.dirop.fqi.fq_sattr = 0;
502 
503 	rc = smbd_fs_query(sr, &sr->arg.dirop.fqi, FQM_PATH_MUST_EXIST);
504 	if (rc) {
505 		if (rc == ENOENT)
506 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
507 			    ERRDOS, ERROR_PATH_NOT_FOUND);
508 		else
509 			smbsr_errno(sr, rc);
510 		return (SDRC_ERROR);
511 	}
512 
513 	smb_node_release(sr->arg.dirop.fqi.fq_dnode);
514 
515 	dnode = sr->arg.dirop.fqi.fq_fnode;
516 
517 	if (sr->arg.dirop.fqi.fq_fattr.sa_vattr.va_type != VDIR) {
518 		smb_node_release(dnode);
519 		SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
520 		smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY,
521 		    ERRDOS, ERROR_PATH_NOT_FOUND);
522 		return (SDRC_ERROR);
523 	}
524 
525 	rc = smb_fsop_access(sr, sr->user_cr, dnode, FILE_TRAVERSE);
526 
527 	smb_node_release(dnode);
528 	SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
529 
530 	if (rc != 0) {
531 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
532 		    ERRDOS, ERROR_ACCESS_DENIED);
533 		return (SDRC_ERROR);
534 	}
535 
536 	rc = smbsr_encode_empty_result(sr);
537 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
538 }
539 
540 static boolean_t
541 smb_dirpath_isvalid(const char *path)
542 {
543 	struct {
544 		char *name;
545 		int len;
546 	} *bad, bad_paths[] = {
547 		{ ".\0",   2 },
548 		{ ".\\\0", 3 },
549 		{ "..\0",  3 },
550 		{ "..\\",  3 }
551 	};
552 
553 	char *cp;
554 	char *p;
555 	int i;
556 
557 	if (*path == '\0')
558 		return (B_TRUE);
559 
560 	cp = smb_kstrdup(path, MAXPATHLEN);
561 	p = strcanon(cp, "\\");
562 	p += strspn(p, "\\");
563 
564 	for (i = 0; i < sizeof (bad_paths) / sizeof (bad_paths[0]); ++i) {
565 		bad = &bad_paths[i];
566 
567 		if (strncmp(p, bad->name, bad->len) == 0) {
568 			kmem_free(cp, MAXPATHLEN);
569 			return (B_FALSE);
570 		}
571 	}
572 
573 	kmem_free(cp, MAXPATHLEN);
574 	return (B_TRUE);
575 }
576