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 /*
23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2020 Tintri by DDN, Inc. All rights reserved.
25 * Copyright 2022 RackTop Systems, Inc.
26 */
27
28 #include <sys/sdt.h>
29 #include <smbsrv/smb_kproto.h>
30 #include <smbsrv/smb_fsops.h>
31 #include <smbsrv/netbios.h>
32
33
34 static int smb_write_truncate(smb_request_t *, smb_rw_param_t *);
35
36
37 /*
38 * Write count bytes at the specified offset in a file. The offset is
39 * limited to 32-bits. If the count is zero, the file is truncated to
40 * the length specified by the offset.
41 *
42 * The response count indicates the actual number of bytes written, which
43 * will equal the requested count on success. If request and response
44 * counts differ but there is no error, the client will assume that the
45 * server encountered a resource issue.
46 */
47 smb_sdrc_t
smb_pre_write(smb_request_t * sr)48 smb_pre_write(smb_request_t *sr)
49 {
50 smb_rw_param_t *param;
51 uint32_t off;
52 uint16_t count;
53 int rc;
54
55 param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
56 sr->arg.rw = param;
57 param->rw_magic = SMB_RW_MAGIC;
58
59 rc = smbsr_decode_vwv(sr, "wwl", &sr->smb_fid, &count, &off);
60
61 param->rw_count = (uint32_t)count;
62 param->rw_offset = (uint64_t)off;
63 param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
64
65 DTRACE_SMB_START(op__Write, smb_request_t *, sr); /* arg.rw */
66
67 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
68 }
69
70 void
smb_post_write(smb_request_t * sr)71 smb_post_write(smb_request_t *sr)
72 {
73 DTRACE_SMB_DONE(op__Write, smb_request_t *, sr); /* arg.rw */
74
75 kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
76 }
77
78 smb_sdrc_t
smb_com_write(smb_request_t * sr)79 smb_com_write(smb_request_t *sr)
80 {
81 smb_rw_param_t *param = sr->arg.rw;
82 int rc;
83
84 smbsr_lookup_file(sr);
85 if (sr->fid_ofile == NULL) {
86 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
87 return (SDRC_ERROR);
88 }
89
90 sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
91
92 if (param->rw_count == 0) {
93 rc = smb_write_truncate(sr, param);
94 } else {
95 rc = smbsr_decode_data(sr, "D", ¶m->rw_vdb);
96
97 if ((rc != 0) || (param->rw_vdb.vdb_len != param->rw_count)) {
98 smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
99 ERRDOS, ERROR_INVALID_PARAMETER);
100 return (SDRC_ERROR);
101 }
102
103 param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
104
105 rc = smb_common_write(sr, param);
106 }
107
108 if (rc != 0) {
109 if (sr->smb_error.status != NT_STATUS_FILE_LOCK_CONFLICT)
110 smbsr_errno(sr, rc);
111 return (SDRC_ERROR);
112 }
113
114 rc = smbsr_encode_result(sr, 1, 0, "bww", 1,
115 (uint16_t)param->rw_count, 0);
116 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
117 }
118
119 /*
120 * Write count bytes to a file and then close the file. This function
121 * can only be used to write to 32-bit offsets and the client must set
122 * WordCount (6 or 12) correctly in order to locate the data to be
123 * written. If an error occurs on the write, the file should still be
124 * closed. If Count is 0, the file is truncated (or extended) to offset.
125 *
126 * If the last_write time is non-zero, last_write should be used to set
127 * the mtime. Otherwise the file system stamps the mtime. Failure to
128 * set mtime should not result in an error response.
129 */
130 smb_sdrc_t
smb_pre_write_and_close(smb_request_t * sr)131 smb_pre_write_and_close(smb_request_t *sr)
132 {
133 smb_rw_param_t *param;
134 uint32_t off;
135 uint16_t count;
136 int rc;
137
138 param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
139 sr->arg.rw = param;
140 param->rw_magic = SMB_RW_MAGIC;
141
142 if (sr->smb_wct == 12) {
143 rc = smbsr_decode_vwv(sr, "wwll12.", &sr->smb_fid,
144 &count, &off, ¶m->rw_last_write);
145 } else {
146 rc = smbsr_decode_vwv(sr, "wwll", &sr->smb_fid,
147 &count, &off, ¶m->rw_last_write);
148 }
149
150 param->rw_count = (uint32_t)count;
151 param->rw_offset = (uint64_t)off;
152
153 DTRACE_SMB_START(op__WriteAndClose, smb_request_t *, sr); /* arg.rw */
154
155 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
156 }
157
158 void
smb_post_write_and_close(smb_request_t * sr)159 smb_post_write_and_close(smb_request_t *sr)
160 {
161 DTRACE_SMB_DONE(op__WriteAndClose, smb_request_t *, sr); /* arg.rw */
162
163 kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
164 }
165
166 smb_sdrc_t
smb_com_write_and_close(smb_request_t * sr)167 smb_com_write_and_close(smb_request_t *sr)
168 {
169 smb_rw_param_t *param = sr->arg.rw;
170 uint16_t count;
171 int rc = 0;
172
173 smbsr_lookup_file(sr);
174 if (sr->fid_ofile == NULL) {
175 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
176 return (SDRC_ERROR);
177 }
178
179 sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
180
181 if (param->rw_count == 0) {
182 rc = smb_write_truncate(sr, param);
183 } else {
184 /*
185 * There may be a bug here: should this be "3.#B"?
186 */
187 rc = smbsr_decode_data(sr, ".#B", param->rw_count,
188 ¶m->rw_vdb);
189
190 if ((rc != 0) || (param->rw_vdb.vdb_len != param->rw_count)) {
191 smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
192 ERRDOS, ERROR_INVALID_PARAMETER);
193 return (SDRC_ERROR);
194 }
195
196 param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
197
198 rc = smb_common_write(sr, param);
199 }
200
201 if (rc != 0) {
202 if (sr->smb_error.status != NT_STATUS_FILE_LOCK_CONFLICT)
203 smbsr_errno(sr, rc);
204 return (SDRC_ERROR);
205 }
206
207 smb_ofile_close(sr->fid_ofile, param->rw_last_write);
208
209 count = (uint16_t)param->rw_count;
210 rc = smbsr_encode_result(sr, 1, 0, "bww", 1, count, 0);
211 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
212 }
213
214 /*
215 * Write count bytes to a file at the specified offset and then unlock
216 * them. Write behind is safe because the client should have the range
217 * locked and this request is allowed to extend the file - note that
218 * offset is limited to 32-bits.
219 *
220 * Spec advice: it is an error for count to be zero. For compatibility,
221 * we take no action and return success.
222 *
223 * The SmbLockAndRead/SmbWriteAndUnlock sub-dialect is only valid on disk
224 * files. Reject any attempt to use it on other shares.
225 *
226 * The response count indicates the actual number of bytes written, which
227 * will equal the requested count on success. If request and response
228 * counts differ but there is no error, the client will assume that the
229 * server encountered a resource issue.
230 */
231 smb_sdrc_t
smb_pre_write_and_unlock(smb_request_t * sr)232 smb_pre_write_and_unlock(smb_request_t *sr)
233 {
234 smb_rw_param_t *param;
235 uint32_t off;
236 uint16_t count;
237 uint16_t remcnt;
238 int rc;
239
240 param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
241 sr->arg.rw = param;
242 param->rw_magic = SMB_RW_MAGIC;
243
244 rc = smbsr_decode_vwv(sr, "wwlw", &sr->smb_fid, &count, &off, &remcnt);
245
246 param->rw_count = (uint32_t)count;
247 param->rw_offset = (uint64_t)off;
248
249 DTRACE_SMB_START(op__WriteAndUnlock, smb_request_t *, sr); /* arg.rw */
250
251 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
252 }
253
254 void
smb_post_write_and_unlock(smb_request_t * sr)255 smb_post_write_and_unlock(smb_request_t *sr)
256 {
257 DTRACE_SMB_DONE(op__WriteAndUnlock, smb_request_t *, sr); /* arg.rw */
258
259 kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
260 }
261
262 smb_sdrc_t
smb_com_write_and_unlock(smb_request_t * sr)263 smb_com_write_and_unlock(smb_request_t *sr)
264 {
265 smb_rw_param_t *param = sr->arg.rw;
266 uint32_t lk_pid;
267 uint32_t status;
268 int rc = 0;
269
270 if (STYPE_ISDSK(sr->tid_tree->t_res_type) == 0) {
271 smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERRnoaccess);
272 return (SDRC_ERROR);
273 }
274
275 smbsr_lookup_file(sr);
276 if (sr->fid_ofile == NULL) {
277 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
278 return (SDRC_ERROR);
279 }
280
281 sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
282
283 if (param->rw_count == 0) {
284 rc = smbsr_encode_result(sr, 1, 0, "bww", 1, 0, 0);
285 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
286 }
287
288
289 rc = smbsr_decode_data(sr, "D", ¶m->rw_vdb);
290
291 if ((rc != 0) || (param->rw_count != param->rw_vdb.vdb_len)) {
292 smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
293 ERRDOS, ERROR_INVALID_PARAMETER);
294 return (SDRC_ERROR);
295 }
296
297 param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
298
299 if ((rc = smb_common_write(sr, param)) != 0) {
300 if (sr->smb_error.status != NT_STATUS_FILE_LOCK_CONFLICT)
301 smbsr_errno(sr, rc);
302 return (SDRC_ERROR);
303 }
304
305
306 /* Note: SMB1 locking uses 16-bit PIDs. */
307 lk_pid = sr->smb_pid & 0xFFFF;
308
309 status = smb_unlock_range(sr, param->rw_offset,
310 (uint64_t)param->rw_count, lk_pid);
311 if (status != NT_STATUS_SUCCESS) {
312 smbsr_error(sr, NT_STATUS_RANGE_NOT_LOCKED,
313 ERRDOS, ERROR_NOT_LOCKED);
314 return (SDRC_ERROR);
315 }
316
317 rc = smbsr_encode_result(sr, 1, 0, "bww", 1,
318 (uint16_t)param->rw_count, 0);
319 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
320 }
321
322 /*
323 * The SMB_COM_WRITE_RAW protocol was a negotiated option introduced in
324 * SMB Core Plus to maximize performance when writing a large block
325 * of data to a server. It's obsolete and no longer supported.
326 *
327 * We keep a handler for it so the dtrace provider can see if
328 * the client tried to use this command.
329 */
330 smb_sdrc_t
smb_pre_write_raw(smb_request_t * sr)331 smb_pre_write_raw(smb_request_t *sr)
332 {
333 DTRACE_SMB_START(op__WriteRaw, smb_request_t *, sr);
334 return (SDRC_SUCCESS);
335 }
336
337 void
smb_post_write_raw(smb_request_t * sr)338 smb_post_write_raw(smb_request_t *sr)
339 {
340 DTRACE_SMB_DONE(op__WriteRaw, smb_request_t *, sr);
341 }
342
343 smb_sdrc_t
smb_com_write_raw(struct smb_request * sr)344 smb_com_write_raw(struct smb_request *sr)
345 {
346 smbsr_error(sr, NT_STATUS_NOT_SUPPORTED, ERRDOS,
347 ERROR_NOT_SUPPORTED);
348 return (SDRC_ERROR);
349 }
350
351 /*
352 * Write bytes to a file (SMB Core). This request was extended in
353 * LM 0.12 to support 64-bit offsets, indicated by sending a wct of
354 * 14, instead of 12, and including additional offset information.
355 *
356 * A ByteCount of 0 does not truncate the file - use SMB_COM_WRITE
357 * to truncate a file. A zero length merely transfers zero bytes.
358 *
359 * If bit 0 of WriteMode is set, Fid must refer to a disk file and
360 * the data must be on stable storage before responding.
361 *
362 * MS-SMB 3.3.5.8 update to LM 0.12 4.2.5:
363 * If CAP_LARGE_WRITEX is set, the byte count may be larger than the
364 * negotiated buffer size and the server is expected to write the
365 * number of bytes specified.
366 */
367 smb_sdrc_t
smb_pre_write_andx(smb_request_t * sr)368 smb_pre_write_andx(smb_request_t *sr)
369 {
370 smb_rw_param_t *param;
371 uint32_t off_low;
372 uint32_t off_high;
373 uint16_t datalen_low;
374 uint16_t datalen_high;
375 uint16_t remcnt;
376 int rc;
377
378 param = kmem_zalloc(sizeof (smb_rw_param_t), KM_SLEEP);
379 sr->arg.rw = param;
380 param->rw_magic = SMB_RW_MAGIC;
381
382 if (sr->smb_wct == 14) {
383 rc = smbsr_decode_vwv(sr, "4.wl4.wwwwwl", &sr->smb_fid,
384 &off_low, ¶m->rw_mode, &remcnt, &datalen_high,
385 &datalen_low, ¶m->rw_dsoff, &off_high);
386
387 if (param->rw_dsoff >= 63)
388 param->rw_dsoff -= 63;
389 param->rw_offset = ((uint64_t)off_high << 32) | off_low;
390 } else if (sr->smb_wct == 12) {
391 rc = smbsr_decode_vwv(sr, "4.wl4.wwwww", &sr->smb_fid,
392 &off_low, ¶m->rw_mode, &remcnt, &datalen_high,
393 &datalen_low, ¶m->rw_dsoff);
394
395 if (param->rw_dsoff >= 59)
396 param->rw_dsoff -= 59;
397 param->rw_offset = (uint64_t)off_low;
398 /* off_high not present */
399 } else {
400 rc = -1;
401 }
402
403 param->rw_count = (uint32_t)datalen_low;
404
405 /*
406 * Work-around a Win7 bug, where it fails to set the
407 * CAP_LARGE_WRITEX flag during session setup. Assume
408 * a large write if the data remaining is >= 64k.
409 */
410 if ((sr->session->capabilities & CAP_LARGE_WRITEX) != 0 ||
411 (sr->smb_data.max_bytes > (sr->smb_data.chain_offset + 0xFFFF)))
412 param->rw_count |= ((uint32_t)datalen_high << 16);
413
414 DTRACE_SMB_START(op__WriteX, smb_request_t *, sr); /* arg.rw */
415
416 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
417 }
418
419 void
smb_post_write_andx(smb_request_t * sr)420 smb_post_write_andx(smb_request_t *sr)
421 {
422 DTRACE_SMB_DONE(op__WriteX, smb_request_t *, sr); /* arg.rw */
423
424 kmem_free(sr->arg.rw, sizeof (smb_rw_param_t));
425 }
426
427 smb_sdrc_t
smb_com_write_andx(smb_request_t * sr)428 smb_com_write_andx(smb_request_t *sr)
429 {
430 smb_rw_param_t *param = sr->arg.rw;
431 uint16_t count_high;
432 uint16_t count_low;
433 int rc;
434
435 ASSERT(param);
436 ASSERT(param->rw_magic == SMB_RW_MAGIC);
437
438 smbsr_lookup_file(sr);
439 if (sr->fid_ofile == NULL) {
440 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
441 return (SDRC_ERROR);
442 }
443
444 sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
445
446 if (SMB_WRMODE_IS_STABLE(param->rw_mode) &&
447 STYPE_ISIPC(sr->tid_tree->t_res_type)) {
448 smbsr_error(sr, 0, ERRSRV, ERRaccess);
449 return (SDRC_ERROR);
450 }
451
452 rc = smbsr_decode_data(sr, "#.#B", param->rw_dsoff, param->rw_count,
453 ¶m->rw_vdb);
454
455 if ((rc != 0) || (param->rw_vdb.vdb_len != param->rw_count)) {
456 smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
457 ERRDOS, ERROR_INVALID_PARAMETER);
458 return (SDRC_ERROR);
459 }
460
461 param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset;
462
463 if (param->rw_count != 0) {
464 if ((rc = smb_common_write(sr, param)) != 0) {
465 if (sr->smb_error.status !=
466 NT_STATUS_FILE_LOCK_CONFLICT)
467 smbsr_errno(sr, rc);
468 return (SDRC_ERROR);
469 }
470 }
471
472 count_low = param->rw_count & 0xFFFF;
473 count_high = (param->rw_count >> 16) & 0xFF;
474
475 rc = smbsr_encode_result(sr, 6, 0, "bb1.wwwwww",
476 6, sr->andx_com, 15, count_low, 0, count_high, 0, 0);
477
478 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
479 }
480
481 /*
482 * Common function for writing files or IPC/MSRPC named pipes.
483 *
484 * Returns errno values.
485 */
486 int
smb_common_write(smb_request_t * sr,smb_rw_param_t * param)487 smb_common_write(smb_request_t *sr, smb_rw_param_t *param)
488 {
489 smb_ofile_t *ofile = sr->fid_ofile;
490 smb_node_t *node;
491 int stability = 0;
492 uint32_t lcount;
493 int rc = 0;
494
495 switch (sr->tid_tree->t_res_type & STYPE_MASK) {
496 case STYPE_DISKTREE:
497 case STYPE_PRINTQ:
498 node = ofile->f_node;
499
500 if (!smb_node_is_dir(node)) {
501 rc = smb_lock_range_access(sr, node, param->rw_offset,
502 param->rw_count, B_TRUE);
503 if (rc != NT_STATUS_SUCCESS) {
504 smbsr_error(sr, NT_STATUS_FILE_LOCK_CONFLICT,
505 ERRDOS, ERROR_LOCK_VIOLATION);
506 return (EACCES);
507 }
508 }
509
510 if (SMB_WRMODE_IS_STABLE(param->rw_mode) ||
511 (node->flags & NODE_FLAGS_WRITE_THROUGH)) {
512 stability = FSYNC;
513 }
514
515 rc = smb_fsop_write(sr, sr->user_cr, node, ofile,
516 ¶m->rw_vdb.vdb_uio, &lcount, stability);
517 if (rc)
518 return (rc);
519 param->rw_count = lcount;
520 /* This revokes read cache delegations. */
521 (void) smb_oplock_break_WRITE(node, ofile);
522 /*
523 * Don't want the performance cost of generating
524 * change notify events on every write. Instead:
525 * Keep track of the fact that we have written
526 * data via this handle, and do change notify
527 * work on the first write, and during close.
528 */
529 if (ofile->f_written == B_FALSE) {
530 ofile->f_written = B_TRUE;
531 smb_node_notify_modified(node);
532 }
533 break;
534
535 case STYPE_IPC:
536 param->rw_count = param->rw_vdb.vdb_uio.uio_resid;
537
538 if ((rc = smb_opipe_write(sr, ¶m->rw_vdb.vdb_uio)) != 0)
539 param->rw_count = 0;
540 break;
541
542 default:
543 rc = EACCES;
544 break;
545 }
546
547 if (rc != 0)
548 return (rc);
549
550 mutex_enter(&ofile->f_mutex);
551 ofile->f_seek_pos = param->rw_offset + param->rw_count;
552 mutex_exit(&ofile->f_mutex);
553 return (rc);
554 }
555
556 /*
557 * Truncate a disk file to the specified offset.
558 * Typically, w_count will be zero here.
559 *
560 * Note that smb_write_andx cannot be used to reduce the file size so,
561 * if this is required, smb_write is called with a count of zero and
562 * the appropriate file length in offset. The file should be resized
563 * to the length specified by the offset.
564 *
565 * Returns errno values.
566 */
567 static int
smb_write_truncate(smb_request_t * sr,smb_rw_param_t * param)568 smb_write_truncate(smb_request_t *sr, smb_rw_param_t *param)
569 {
570 smb_ofile_t *ofile = sr->fid_ofile;
571 smb_node_t *node = ofile->f_node;
572 smb_attr_t attr;
573 uint32_t status;
574 int rc;
575
576 if (STYPE_ISIPC(sr->tid_tree->t_res_type))
577 return (0);
578
579 mutex_enter(&node->n_mutex);
580 if (!smb_node_is_dir(node)) {
581 status = smb_lock_range_access(sr, node, param->rw_offset,
582 param->rw_count, B_TRUE);
583 if (status != NT_STATUS_SUCCESS) {
584 mutex_exit(&node->n_mutex);
585 smbsr_error(sr, NT_STATUS_FILE_LOCK_CONFLICT,
586 ERRDOS, ERROR_LOCK_VIOLATION);
587 return (EACCES);
588 }
589 }
590 mutex_exit(&node->n_mutex);
591
592 bzero(&attr, sizeof (smb_attr_t));
593 attr.sa_mask = SMB_AT_SIZE;
594 attr.sa_vattr.va_size = param->rw_offset;
595 rc = smb_node_setattr(sr, node, sr->user_cr, ofile, &attr);
596 if (rc != 0)
597 return (rc);
598
599 mutex_enter(&ofile->f_mutex);
600 ofile->f_seek_pos = param->rw_offset + param->rw_count;
601 mutex_exit(&ofile->f_mutex);
602 return (0);
603 }
604