2 * Unix SMB/CIFS implementation.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "libcli/smb/reparse.h"
20 #include "lib/util/iov_buf.h"
21 #include "libcli/smb/smb_constants.h"
22 #include "libcli/util/error.h"
23 #include "lib/util/debug.h"
24 #include "lib/util/bytearray.h"
25 #include "lib/util/talloc_stack.h"
26 #include "lib/util/charset/charset.h"
29 NTSTATUS
reparse_buffer_check(const uint8_t *in_data
,
31 uint32_t *reparse_tag
,
32 const uint8_t **_reparse_data
,
33 size_t *_reparse_data_length
)
35 uint16_t reparse_data_length
;
38 DBG_DEBUG("in_len=0\n");
39 return NT_STATUS_INVALID_BUFFER_SIZE
;
42 DBG_DEBUG("in_len=%zu\n", in_len
);
43 return NT_STATUS_IO_REPARSE_DATA_INVALID
;
46 reparse_data_length
= PULL_LE_U16(in_data
, 4);
48 if (reparse_data_length
!= (in_len
- 8)) {
49 DBG_DEBUG("in_len=%zu, reparse_data_length=%" PRIu16
"\n",
52 return NT_STATUS_IO_REPARSE_DATA_INVALID
;
55 *reparse_tag
= PULL_LE_U32(in_data
, 0);
56 *_reparse_data
= in_data
+ 8;
57 *_reparse_data_length
= reparse_data_length
;
62 static int nfs_reparse_buffer_parse(TALLOC_CTX
*mem_ctx
,
63 struct nfs_reparse_data_buffer
*dst
,
70 DBG_DEBUG("srclen=%zu too short\n", srclen
);
74 type
= PULL_LE_U64(src
, 0);
77 case NFS_SPECFILE_CHR
:
79 case NFS_SPECFILE_BLK
:
81 DBG_DEBUG("srclen %zu too short for type %" PRIx64
"\n",
86 dst
->data
.dev
.major
= PULL_LE_U32(src
, 8);
87 dst
->data
.dev
.minor
= PULL_LE_U32(src
, 12);
89 case NFS_SPECFILE_LNK
: {
92 ok
= convert_string_talloc(mem_ctx
,
97 &dst
->data
.lnk_target
,
104 case NFS_SPECFILE_FIFO
:
105 break; /* empty, no data */
106 case NFS_SPECFILE_SOCK
:
107 break; /* empty, no data */
109 DBG_DEBUG("Unknown NFS reparse type %" PRIx64
"\n", type
);
118 static int symlink_reparse_buffer_parse(TALLOC_CTX
*mem_ctx
,
119 struct symlink_reparse_struct
*dst
,
123 uint16_t reparse_data_length
;
124 uint16_t substitute_name_offset
, substitute_name_length
;
125 uint16_t print_name_offset
, print_name_length
;
129 DBG_DEBUG("srclen = %zu, expected >= 20\n", srclen
);
132 if (PULL_LE_U32(src
, 0) != IO_REPARSE_TAG_SYMLINK
) {
133 DBG_DEBUG("Got ReparseTag %8.8x, expected %8.8x\n",
135 IO_REPARSE_TAG_SYMLINK
);
139 reparse_data_length
= PULL_LE_U16(src
, 4);
140 substitute_name_offset
= PULL_LE_U16(src
, 8);
141 substitute_name_length
= PULL_LE_U16(src
, 10);
142 print_name_offset
= PULL_LE_U16(src
, 12);
143 print_name_length
= PULL_LE_U16(src
, 14);
145 if (reparse_data_length
< 12) {
146 DBG_DEBUG("reparse_data_length = %"PRIu16
", expected >= 12\n",
147 reparse_data_length
);
150 if (smb_buffer_oob(srclen
- 8, reparse_data_length
, 0)) {
151 DBG_DEBUG("reparse_data_length (%"PRIu16
") too large for "
157 if (smb_buffer_oob(reparse_data_length
- 12, substitute_name_offset
,
158 substitute_name_length
)) {
159 DBG_DEBUG("substitute_name (%"PRIu16
"/%"PRIu16
") does not fit "
160 "in reparse_data_length (%"PRIu16
")\n",
161 substitute_name_offset
,
162 substitute_name_length
,
163 reparse_data_length
- 12);
166 if (smb_buffer_oob(reparse_data_length
- 12, print_name_offset
,
167 print_name_length
)) {
168 DBG_DEBUG("print_name (%"PRIu16
"/%"PRIu16
") does not fit in "
169 "reparse_data_length (%"PRIu16
")\n",
172 reparse_data_length
- 12);
176 *dst
= (struct symlink_reparse_struct
) {
177 .unparsed_path_length
= PULL_LE_U16(src
, 6),
178 .flags
= PULL_LE_U32(src
, 16),
181 ok
= convert_string_talloc(mem_ctx
,
184 src
+ 20 + substitute_name_offset
,
185 substitute_name_length
,
186 &dst
->substitute_name
,
190 DBG_DEBUG("convert_string_talloc for substitute_name "
195 ok
= convert_string_talloc(mem_ctx
,
198 src
+ 20 + print_name_offset
,
204 DBG_DEBUG("convert_string_talloc for print_name failed\n");
205 TALLOC_FREE(dst
->substitute_name
);
212 NTSTATUS
reparse_data_buffer_parse(TALLOC_CTX
*mem_ctx
,
213 struct reparse_data_buffer
*dst
,
217 const uint8_t *reparse_data
;
218 size_t reparse_data_length
;
222 status
= reparse_buffer_check(buf
,
226 &reparse_data_length
);
227 if (!NT_STATUS_IS_OK(status
)) {
232 case IO_REPARSE_TAG_SYMLINK
:
233 ret
= symlink_reparse_buffer_parse(mem_ctx
,
238 return map_nt_error_from_unix_common(ret
);
241 case IO_REPARSE_TAG_NFS
:
242 ret
= nfs_reparse_buffer_parse(mem_ctx
,
245 reparse_data_length
);
247 return map_nt_error_from_unix_common(ret
);
251 dst
->parsed
.raw
.data
= talloc_memdup(mem_ctx
,
253 reparse_data_length
);
254 if (dst
->parsed
.raw
.data
== NULL
) {
255 return NT_STATUS_NO_MEMORY
;
257 dst
->parsed
.raw
.length
= reparse_data_length
;
258 dst
->parsed
.raw
.reserved
= PULL_LE_U16(buf
, 6);
265 char *reparse_data_buffer_str(TALLOC_CTX
*mem_ctx
,
266 const struct reparse_data_buffer
*dst
)
268 char *s
= talloc_strdup(mem_ctx
, "");
271 case IO_REPARSE_TAG_SYMLINK
: {
272 const struct symlink_reparse_struct
*lnk
= &dst
->parsed
.lnk
;
273 talloc_asprintf_addbuf(&s
,
275 " (IO_REPARSE_TAG_SYMLINK)\n",
277 talloc_asprintf_addbuf(&s
,
278 "unparsed=%" PRIu16
"\n",
279 lnk
->unparsed_path_length
);
280 talloc_asprintf_addbuf(&s
,
281 "substitute_name=%s\n",
282 lnk
->substitute_name
);
283 talloc_asprintf_addbuf(&s
, "print_name=%s\n", lnk
->print_name
);
284 talloc_asprintf_addbuf(&s
, "flags=%" PRIu32
"\n", lnk
->flags
);
287 case IO_REPARSE_TAG_NFS
: {
288 const struct nfs_reparse_data_buffer
*nfs
= &dst
->parsed
.nfs
;
290 talloc_asprintf_addbuf(&s
,
291 "0x%" PRIx32
" (IO_REPARSE_TAG_NFS)\n",
295 case NFS_SPECFILE_FIFO
:
296 talloc_asprintf_addbuf(&s
,
298 " (NFS_SPECFILE_FIFO)\n",
301 case NFS_SPECFILE_SOCK
:
302 talloc_asprintf_addbuf(&s
,
304 " (NFS_SPECFILE_SOCK)\n",
307 case NFS_SPECFILE_LNK
:
308 talloc_asprintf_addbuf(&s
,
310 " (NFS_SPECFILE_LNK)\n",
312 talloc_asprintf_addbuf(&s
,
314 nfs
->data
.lnk_target
);
316 case NFS_SPECFILE_BLK
:
317 talloc_asprintf_addbuf(&s
,
319 " (NFS_SPECFILE_BLK)\n",
321 talloc_asprintf_addbuf(&s
,
322 " %" PRIu32
"/%" PRIu32
"\n",
324 nfs
->data
.dev
.minor
);
326 case NFS_SPECFILE_CHR
:
327 talloc_asprintf_addbuf(&s
,
329 " (NFS_SPECFILE_CHR)\n",
331 talloc_asprintf_addbuf(&s
,
332 " %" PRIu32
"/%" PRIu32
"\n",
334 nfs
->data
.dev
.minor
);
337 talloc_asprintf_addbuf(&s
,
346 talloc_asprintf_addbuf(&s
, "%" PRIu32
"\n", dst
->tag
);
352 static ssize_t
reparse_buffer_marshall(uint32_t reparse_tag
,
354 const struct iovec
*iov
,
359 ssize_t reparse_data_length
= iov_buflen(iov
, iovlen
);
362 if (reparse_data_length
== -1) {
365 if (reparse_data_length
> UINT16_MAX
) {
369 needed
= reparse_data_length
+ 8;
370 if (needed
< reparse_data_length
) {
374 if (buflen
>= needed
) {
375 PUSH_LE_U32(buf
, 0, reparse_tag
);
376 PUSH_LE_U16(buf
, 4, reparse_data_length
);
377 PUSH_LE_U16(buf
, 6, reserved
);
378 iov_buf(iov
, iovlen
, buf
+ 8, buflen
- 8);
385 reparse_data_buffer_marshall_syml(const struct symlink_reparse_struct
*src
,
391 const char *print_name
= src
->print_name
;
392 uint8_t *subst_utf16
= NULL
;
393 uint8_t *print_utf16
= NULL
;
394 size_t subst_len
= 0;
395 size_t print_len
= 0;
399 if (src
->substitute_name
== NULL
) {
402 if (src
->print_name
== NULL
) {
403 print_name
= src
->substitute_name
;
406 iov
[0] = (struct iovec
){
408 .iov_len
= sizeof(sbuf
),
411 ok
= convert_string_talloc(talloc_tos(),
414 src
->substitute_name
,
415 strlen(src
->substitute_name
),
421 if (subst_len
> UINT16_MAX
) {
424 iov
[1] = (struct iovec
){
425 .iov_base
= subst_utf16
,
426 .iov_len
= subst_len
,
429 ok
= convert_string_talloc(talloc_tos(),
439 if (print_len
> UINT16_MAX
) {
442 iov
[2] = (struct iovec
){
443 .iov_base
= print_utf16
,
444 .iov_len
= print_len
,
447 PUSH_LE_U16(sbuf
, 0, 0); /* SubstituteNameOffset */
448 PUSH_LE_U16(sbuf
, 2, subst_len
); /* SubstituteNameLength */
449 PUSH_LE_U16(sbuf
, 4, subst_len
); /* PrintNameOffset */
450 PUSH_LE_U16(sbuf
, 6, print_len
); /* PrintNameLength */
451 PUSH_LE_U32(sbuf
, 8, src
->flags
); /* Flags */
453 ret
= reparse_buffer_marshall(IO_REPARSE_TAG_SYMLINK
,
454 src
->unparsed_path_length
,
461 TALLOC_FREE(subst_utf16
);
462 TALLOC_FREE(print_utf16
);
467 reparse_data_buffer_marshall_nfs(const struct nfs_reparse_data_buffer
*src
,
473 struct iovec iov
[2] = {};
475 uint8_t *lnk_utf16
= NULL
;
479 PUSH_LE_U64(typebuf
, 0, src
->type
);
480 iov
[0] = (struct iovec
){
482 .iov_len
= sizeof(typebuf
),
487 case NFS_SPECFILE_LNK
: {
488 bool ok
= convert_string_talloc(talloc_tos(),
491 src
->data
.lnk_target
,
492 strlen(src
->data
.lnk_target
),
498 iov
[1] = (struct iovec
){
499 .iov_base
= lnk_utf16
,
505 case NFS_SPECFILE_CHR
:
507 case NFS_SPECFILE_BLK
:
508 PUSH_LE_U32(devbuf
, 0, src
->data
.dev
.major
);
509 PUSH_LE_U32(devbuf
, 4, src
->data
.dev
.minor
);
510 iov
[1] = (struct iovec
){
512 .iov_len
= sizeof(devbuf
),
518 /* Nothing to do for NFS_SPECFILE_FIFO and _SOCK */
521 ret
= reparse_buffer_marshall(IO_REPARSE_TAG_NFS
,
527 TALLOC_FREE(lnk_utf16
);
531 ssize_t
reparse_data_buffer_marshall(const struct reparse_data_buffer
*src
,
538 case IO_REPARSE_TAG_SYMLINK
:
540 ret
= reparse_data_buffer_marshall_syml(&src
->parsed
.lnk
,
545 case IO_REPARSE_TAG_NFS
:
547 ret
= reparse_data_buffer_marshall_nfs(&src
->parsed
.nfs
,
554 .iov_base
= src
->parsed
.raw
.data
,
555 .iov_len
= src
->parsed
.raw
.length
,
557 ret
= reparse_buffer_marshall(src
->tag
,
558 src
->parsed
.raw
.reserved
,
570 * Implement [MS-SMB2] 2.2.2.2.1.1 Handling the Symbolic Link Error Response
573 int symlink_target_path(TALLOC_CTX
*ctx
,
574 const char *_name_in
,
576 const char *substitute
,
581 size_t name_in_len
= strlen(_name_in
);
583 char name_in
[name_in_len
+ 1];
584 char *unparsed
= NULL
;
588 if (num_unparsed
> name_in_len
) {
591 num_parsed
= name_in_len
- num_unparsed
;
594 * We need to NULL out separators in name_in. Make a copy of
595 * _name_in, which is a const char *.
597 memcpy(name_in
, _name_in
, sizeof(name_in
));
599 unparsed
= name_in
+ num_parsed
;
601 if ((num_unparsed
!= 0) && (unparsed
[0] != separator
)) {
603 * Symlinks in the middle of name_in must end in a separator
610 * From [MS-SMB2] 2.2.2.2.1.1:
612 * If the SYMLINK_FLAG_RELATIVE flag is not set in the Flags
613 * field of the symbolic link error response, the unparsed
614 * portion of the file name MUST be appended to the substitute
615 * name to create the new target path name.
617 target
= talloc_asprintf(ctx
, "%s%s", substitute
, unparsed
);
622 * From [MS-SMB2] 2.2.2.2.1.1:
624 * If the SYMLINK_FLAG_RELATIVE flag is set in the Flags field
625 * of the symbolic link error response, the symbolic link name
626 * MUST be identified by backing up one path name element from
627 * the unparsed portion of the path name. The symbolic link
628 * MUST be replaced with the substitute name to create the new
633 char symlink_end_char
= unparsed
[0]; /* '\0' or a separator */
636 syml
= strrchr_m(name_in
, separator
);
637 unparsed
[0] = symlink_end_char
;
642 * Nothing to back up to, the symlink was the first
648 * Make "name_in" up to the symlink usable for asprintf
653 target
= talloc_asprintf(ctx
, "%s%s%s", name_in
, substitute
, unparsed
);
656 if (target
== NULL
) {