xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_directory.c (revision 4eaa471005973e11a6110b69fe990530b3b95a38)
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)) != 0) {
232 		smb_node_release(dnode);
233 		SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
234 		return (rc);
235 	}
236 
237 	node = sr->arg.dirop.fqi.fq_fnode;
238 	rc = smb_node_getattr(sr, node, &sr->arg.dirop.fqi.fq_fattr);
239 	if (rc != 0) {
240 		smb_node_release(dnode);
241 		SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
242 		return (rc);
243 	}
244 	node->flags |= NODE_FLAGS_CREATED;
245 
246 	sr->arg.open.create_options = FILE_DIRECTORY_FILE;
247 
248 	smb_node_release(node);
249 	smb_node_release(dnode);
250 	SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
251 	return (0);
252 }
253 
254 static smb_dirpath_t *
255 smb_dirpath_new(smb_request_t *sr)
256 {
257 	int pathLen;
258 	char *xpath;
259 	smb_dirpath_t *spp;
260 
261 	/* Malloc from the request storage area. This is freed automatically */
262 	/* so we don't need to worry about freeing it later */
263 	spp = smbsr_malloc(&sr->request_storage, sizeof (smb_dirpath_t));
264 	spp->sp_path = sr->arg.dirop.fqi.fq_path.pn_path;
265 	pathLen = strlen(spp->sp_path);
266 	spp->sp_curp = spp->sp_path;
267 	xpath = smbsr_malloc(&sr->request_storage, pathLen + 1);
268 	sr->arg.dirop.fqi.fq_path.pn_path = xpath;
269 	spp->sp_sr = sr;
270 
271 	return (spp);
272 }
273 
274 /*
275  * Perhaps somewhat dangerous since everything happens as a side effect. The
276  * returns 1 if there is a valid component updated to the fqi, 0 otherwise.
277  */
278 static int
279 smb_dirpath_next(smb_dirpath_t *spp)
280 {
281 	char *xp;
282 	int xlen;
283 
284 	if (spp == 0)
285 		return (0);
286 
287 	/* Move the index to the "next" "\" and copy the path to the fqi */
288 	/* path for the next component. */
289 
290 	/* First look for the next component */
291 	while (*spp->sp_curp == '\\')
292 		spp->sp_curp++;
293 
294 	/* Now get to the end of the component */
295 	xp = spp->sp_curp; /* Remember from where we started */
296 	while (*spp->sp_curp != '\0' && *spp->sp_curp != '\\') {
297 		spp->sp_curp++;
298 	}
299 
300 	/* If we made no progress, we are done */
301 	if (xp == spp->sp_curp)
302 		return (0);
303 
304 	/*
305 	 * Now copy the original path up to but not including our current
306 	 * pointer
307 	 */
308 
309 	/*LINTED E_PTRDIFF_OVERFLOW*/
310 	xlen = spp->sp_curp - spp->sp_path;
311 	(void) strncpy(spp->sp_sr->arg.dirop.fqi.fq_path.pn_path,
312 	    spp->sp_path, xlen);
313 
314 	/* Now NULL terminate it */
315 	spp->sp_sr->arg.dirop.fqi.fq_path.pn_path[xlen] = '\0';
316 	return (1);
317 }
318 
319 /*
320  * The delete directory message is sent to delete an empty directory. The
321  * appropriate Tid and additional pathname are passed. The directory must
322  * be empty for it to be deleted.
323  *
324  * NT supports a hidden permission known as File Delete Child (FDC). If
325  * the user has FullControl access to a directory, the user is permitted
326  * to delete any object in the directory regardless of the permissions
327  * on the object.
328  *
329  * Client Request                     Description
330  * ================================== =================================
331  * UCHAR WordCount;                   Count of parameter words = 0
332  * USHORT ByteCount;                  Count of data bytes; min = 2
333  * UCHAR BufferFormat;                0x04
334  * STRING DirectoryName[];            Directory name
335  *
336  * The directory to be deleted cannot be the root of the share specified
337  * by Tid.
338  *
339  * Server Response                    Description
340  * ================================== =================================
341  * UCHAR WordCount;                   Count of parameter words = 0
342  * USHORT ByteCount;                  Count of data bytes = 0
343  */
344 smb_sdrc_t
345 smb_pre_delete_directory(smb_request_t *sr)
346 {
347 	int rc;
348 
349 	rc = smbsr_decode_data(sr, "%S", sr,
350 	    &sr->arg.dirop.fqi.fq_path.pn_path);
351 
352 	DTRACE_SMB_2(op__DeleteDirectory__start, smb_request_t *, sr,
353 	    struct dirop *, &sr->arg.dirop);
354 
355 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
356 }
357 
358 void
359 smb_post_delete_directory(smb_request_t *sr)
360 {
361 	DTRACE_SMB_1(op__DeleteDirectory__done, smb_request_t *, sr);
362 }
363 
364 smb_sdrc_t
365 smb_com_delete_directory(smb_request_t *sr)
366 {
367 	smb_node_t *dnode;
368 	smb_attr_t *attr;
369 	int rc;
370 	uint32_t flags = 0;
371 
372 	if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) {
373 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
374 		    ERRDOS, ERROR_ACCESS_DENIED);
375 		return (SDRC_ERROR);
376 	}
377 
378 	sr->arg.dirop.fqi.fq_sattr = 0;
379 
380 	rc = smbd_fs_query(sr, &sr->arg.dirop.fqi, FQM_PATH_MUST_EXIST);
381 	if (rc) {
382 		if (rc == ENOENT)
383 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
384 			    ERRDOS, ERROR_FILE_NOT_FOUND);
385 		else
386 			smbsr_errno(sr, rc);
387 		return (SDRC_ERROR);
388 	}
389 
390 	attr = &sr->arg.dirop.fqi.fq_fattr;
391 	if (attr->sa_vattr.va_type != VDIR) {
392 		smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY,
393 		    ERRDOS, ERROR_PATH_NOT_FOUND);
394 		return (SDRC_ERROR);
395 	}
396 
397 	dnode = sr->arg.dirop.fqi.fq_fnode;
398 	rc = smb_fsop_access(sr, sr->user_cr, dnode, DELETE);
399 
400 	if ((rc != NT_STATUS_SUCCESS) ||
401 	    attr->sa_dosattr & FILE_ATTRIBUTE_READONLY) {
402 		smb_node_release(dnode);
403 		smb_node_release(sr->arg.dirop.fqi.fq_dnode);
404 		SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
405 		smbsr_error(sr, NT_STATUS_CANNOT_DELETE,
406 		    ERRDOS, ERROR_ACCESS_DENIED);
407 		return (SDRC_ERROR);
408 	}
409 
410 	smb_node_release(dnode);
411 
412 	dnode = sr->arg.dirop.fqi.fq_dnode;
413 
414 	if (SMB_TREE_SUPPORTS_CATIA(sr))
415 		flags |= SMB_CATIA;
416 
417 	rc = smb_fsop_rmdir(sr, sr->user_cr, dnode,
418 	    sr->arg.dirop.fqi.fq_od_name, flags);
419 	if (rc != 0) {
420 		smb_node_release(dnode);
421 		SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
422 		if (rc == EEXIST)
423 			smbsr_error(sr, NT_STATUS_DIRECTORY_NOT_EMPTY,
424 			    ERRDOS, ERROR_DIR_NOT_EMPTY);
425 		else
426 			smbsr_errno(sr, rc);
427 		return (SDRC_ERROR);
428 	}
429 
430 	smb_node_release(dnode);
431 	SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
432 
433 	rc = smbsr_encode_empty_result(sr);
434 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
435 }
436 
437 /*
438  * This SMB is used to verify that a path exists and is a directory.  No
439  * error is returned if the given path exists and the client has read
440  * access to it.  Client machines which maintain a concept of a "working
441  * directory" will find this useful to verify the validity of a "change
442  * working directory" command.  Note that the servers do NOT have a concept
443  * of working directory for a particular client.  The client must always
444  * supply full pathnames relative to the Tid in the SMB header.
445  *
446  * Client Request                     Description
447  * ================================== =================================
448  *
449  * UCHAR WordCount;                   Count of parameter words = 0
450  * USHORT ByteCount;                  Count of data bytes;    min = 2
451  * UCHAR BufferFormat;                0x04
452  * STRING DirectoryPath[];            Directory path
453  *
454  * Server Response                    Description
455  * ================================== =================================
456  *
457  * UCHAR WordCount;                   Count of parameter words = 0
458  * USHORT ByteCount;                  Count of data bytes = 0
459  *
460  * DOS clients, in particular, depend on ERRbadpath if the directory is
461  * not found.
462  */
463 smb_sdrc_t
464 smb_pre_check_directory(smb_request_t *sr)
465 {
466 	int rc;
467 
468 	rc = smbsr_decode_data(sr, "%S", sr,
469 	    &sr->arg.dirop.fqi.fq_path.pn_path);
470 
471 	DTRACE_SMB_2(op__CheckDirectory__start, smb_request_t *, sr,
472 	    struct dirop *, &sr->arg.dirop);
473 
474 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
475 }
476 
477 void
478 smb_post_check_directory(smb_request_t *sr)
479 {
480 	DTRACE_SMB_1(op__CheckDirectory__done, smb_request_t *, sr);
481 }
482 
483 smb_sdrc_t
484 smb_com_check_directory(smb_request_t *sr)
485 {
486 	smb_node_t *dnode;
487 	int rc;
488 
489 	if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) {
490 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS,
491 		    ERROR_ACCESS_DENIED);
492 		return (SDRC_ERROR);
493 	}
494 
495 	if (sr->arg.dirop.fqi.fq_path.pn_path[0] == '\0') {
496 		rc = smbsr_encode_empty_result(sr);
497 		return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
498 	}
499 
500 	if (!smb_dirpath_isvalid(sr->arg.dirop.fqi.fq_path.pn_path)) {
501 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
502 		    ERRDOS, ERROR_PATH_NOT_FOUND);
503 		return (SDRC_ERROR);
504 	}
505 
506 	sr->arg.dirop.fqi.fq_sattr = 0;
507 
508 	rc = smbd_fs_query(sr, &sr->arg.dirop.fqi, FQM_PATH_MUST_EXIST);
509 	if (rc) {
510 		if (rc == ENOENT)
511 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
512 			    ERRDOS, ERROR_PATH_NOT_FOUND);
513 		else
514 			smbsr_errno(sr, rc);
515 		return (SDRC_ERROR);
516 	}
517 
518 	smb_node_release(sr->arg.dirop.fqi.fq_dnode);
519 
520 	dnode = sr->arg.dirop.fqi.fq_fnode;
521 
522 	if (sr->arg.dirop.fqi.fq_fattr.sa_vattr.va_type != VDIR) {
523 		smb_node_release(dnode);
524 		SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
525 		smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY,
526 		    ERRDOS, ERROR_PATH_NOT_FOUND);
527 		return (SDRC_ERROR);
528 	}
529 
530 	rc = smb_fsop_access(sr, sr->user_cr, dnode, FILE_TRAVERSE);
531 
532 	smb_node_release(dnode);
533 	SMB_NULL_FQI_NODES(sr->arg.dirop.fqi);
534 
535 	if (rc != 0) {
536 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
537 		    ERRDOS, ERROR_ACCESS_DENIED);
538 		return (SDRC_ERROR);
539 	}
540 
541 	rc = smbsr_encode_empty_result(sr);
542 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
543 }
544 
545 static boolean_t
546 smb_dirpath_isvalid(const char *path)
547 {
548 	struct {
549 		char *name;
550 		int len;
551 	} *bad, bad_paths[] = {
552 		{ ".\0",   2 },
553 		{ ".\\\0", 3 },
554 		{ "..\0",  3 },
555 		{ "..\\",  3 }
556 	};
557 
558 	char *cp;
559 	char *p;
560 	int i;
561 
562 	if (*path == '\0')
563 		return (B_TRUE);
564 
565 	cp = smb_kstrdup(path, MAXPATHLEN);
566 	p = strcanon(cp, "\\");
567 	p += strspn(p, "\\");
568 
569 	for (i = 0; i < sizeof (bad_paths) / sizeof (bad_paths[0]); ++i) {
570 		bad = &bad_paths[i];
571 
572 		if (strncmp(p, bad->name, bad->len) == 0) {
573 			kmem_free(cp, MAXPATHLEN);
574 			return (B_FALSE);
575 		}
576 	}
577 
578 	kmem_free(cp, MAXPATHLEN);
579 	return (B_TRUE);
580 }
581