diff options
Diffstat (limited to 'usr/src/uts/common/fs')
-rw-r--r-- | usr/src/uts/common/fs/smbsrv/smb2_qinfo_file.c | 99 | ||||
-rw-r--r-- | usr/src/uts/common/fs/smbsrv/smb_pathname.c | 95 | ||||
-rw-r--r-- | usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c | 207 |
3 files changed, 260 insertions, 141 deletions
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_qinfo_file.c b/usr/src/uts/common/fs/smbsrv/smb2_qinfo_file.c index 00198aa31f..dcfd771baa 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_qinfo_file.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_qinfo_file.c @@ -32,6 +32,7 @@ static uint32_t smb2_qif_internal(smb_request_t *, smb_queryinfo_t *); static uint32_t smb2_qif_ea_size(smb_request_t *, smb_queryinfo_t *); static uint32_t smb2_qif_access(smb_request_t *, smb_queryinfo_t *); static uint32_t smb2_qif_name(smb_request_t *, smb_queryinfo_t *); +static uint32_t smb2_qif_normalized_name(smb_request_t *, smb_queryinfo_t *); static uint32_t smb2_qif_position(smb_request_t *, smb_queryinfo_t *); static uint32_t smb2_qif_full_ea(smb_request_t *, smb_queryinfo_t *); static uint32_t smb2_qif_mode(smb_request_t *, smb_queryinfo_t *); @@ -45,6 +46,7 @@ static uint32_t smb2_qif_pipe_rem(smb_request_t *, smb_queryinfo_t *); static uint32_t smb2_qif_compr(smb_request_t *, smb_queryinfo_t *); static uint32_t smb2_qif_opens(smb_request_t *, smb_queryinfo_t *); static uint32_t smb2_qif_tags(smb_request_t *, smb_queryinfo_t *); +static uint32_t smb2_qif_id_info(smb_request_t *, smb_queryinfo_t *); uint32_t @@ -80,6 +82,7 @@ smb2_qinfo_file(smb_request_t *sr, smb_queryinfo_t *qi) break; case FileNameInformation: + case FileNormalizedNameInformation: getname = B_TRUE; break; @@ -99,6 +102,11 @@ smb2_qinfo_file(smb_request_t *sr, smb_queryinfo_t *qi) case FileNetworkOpenInformation: mask = SMB_AT_BASIC | SMB_AT_STANDARD; + break; + + case FileIdInformation: + mask = SMB_AT_NODEID; + break; default: break; @@ -141,6 +149,9 @@ smb2_qinfo_file(smb_request_t *sr, smb_queryinfo_t *qi) case FileNameInformation: status = smb2_qif_name(sr, qi); break; + case FileNormalizedNameInformation: + status = smb2_qif_normalized_name(sr, qi); + break; case FilePositionInformation: status = smb2_qif_position(sr, qi); break; @@ -180,6 +191,9 @@ smb2_qinfo_file(smb_request_t *sr, smb_queryinfo_t *qi) case FileAttributeTagInformation: status = smb2_qif_tags(sr, qi); break; + case FileIdInformation: + status = smb2_qif_id_info(sr, qi); + break; default: status = NT_STATUS_INVALID_INFO_CLASS; break; @@ -392,15 +406,51 @@ smb2_qif_access(smb_request_t *sr, smb_queryinfo_t *qi) static uint32_t smb2_qif_name(smb_request_t *sr, smb_queryinfo_t *qi) { + char *name; + uint32_t nlen; int rc; - ASSERT(qi->qi_namelen > 0); + /* SMB2 leaves off the leading / */ + nlen = qi->qi_namelen; + name = qi->qi_name; + if (qi->qi_name[0] == '\\') { + name++; + nlen -= 2; + } rc = smb_mbc_encodef( &sr->raw_data, "llU", 0, /* FileIndex (l) */ - qi->qi_namelen, /* l */ - qi->qi_name); /* U */ + nlen, /* l */ + name); /* U */ + if (rc != 0) + return (NT_STATUS_BUFFER_OVERFLOW); + + return (0); +} + +/* + * FileNormalizedNameInformation + */ +static uint32_t +smb2_qif_normalized_name(smb_request_t *sr, smb_queryinfo_t *qi) +{ + char *name; + uint32_t nlen; + int rc; + + /* SMB2 leaves off the leading / */ + nlen = qi->qi_namelen; + name = qi->qi_name; + if (qi->qi_name[0] == '\\') { + name++; + nlen -= 2; + } + + rc = smb_mbc_encodef( + &sr->raw_data, "lU", + nlen, /* l */ + name); /* U */ if (rc != 0) return (NT_STATUS_BUFFER_OVERFLOW); @@ -645,3 +695,46 @@ smb2_qif_tags(smb_request_t *sr, smb_queryinfo_t *qi) return (0); } + +/* + * FileIdInformation + * + * Returns a A FILE_ID_INFORMATION + * VolumeSerialNumber (8 bytes) + * FileId (16 bytes) + * + * Take the volume serial from the share root, + * and compose the FileId from the nodeid and fsid + * of the file (in case we crossed mounts) + */ +static uint32_t +smb2_qif_id_info(smb_request_t *sr, smb_queryinfo_t *qi) +{ + smb_attr_t *sa = &qi->qi_attr; + smb_ofile_t *of = sr->fid_ofile; + smb_tree_t *tree = sr->tid_tree; + vfs_t *f_vfs; // file + vfs_t *s_vfs; // share + uint64_t nodeid; + int rc; + + ASSERT((sa->sa_mask & SMB_AT_NODEID) != 0); + if (of->f_ftype != SMB_FTYPE_DISK) + return (NT_STATUS_INVALID_INFO_CLASS); + + s_vfs = SMB_NODE_VFS(tree->t_snode); + f_vfs = SMB_NODE_VFS(of->f_node); + nodeid = (uint64_t)sa->sa_vattr.va_nodeid; + + rc = smb_mbc_encodef( + &sr->raw_data, "llqll", + s_vfs->vfs_fsid.val[0], /* l */ + s_vfs->vfs_fsid.val[1], /* l */ + nodeid, /* q */ + f_vfs->vfs_fsid.val[0], /* l */ + f_vfs->vfs_fsid.val[1]); /* l */ + if (rc != 0) + return (NT_STATUS_INFO_LENGTH_MISMATCH); + + return (0); +} diff --git a/usr/src/uts/common/fs/smbsrv/smb_pathname.c b/usr/src/uts/common/fs/smbsrv/smb_pathname.c index 3dd99c9a61..5edbecb733 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_pathname.c +++ b/usr/src/uts/common/fs/smbsrv/smb_pathname.c @@ -21,7 +21,7 @@ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Nexenta by DDN, Inc. All rights reserved. - * Copyright 2021 RackTop Systems, Inc. + * Copyright 2022 RackTop Systems, Inc. */ #include <smbsrv/smb_kproto.h> @@ -804,47 +804,99 @@ smb_pathname_init(smb_request_t *sr, smb_pathname_t *pn, char *path) /* parse pn->pn_path into its constituent parts */ pname = pn->pn_path; - fname = strrchr(pn->pn_path, '\\'); - if (fname) { + /* + * Split the string between the directory and filename. + * Either part may be empty. + * + * Fill in pn->pn_pname (the path name) + */ + fname = strrchr(pname, '\\'); + if (fname != NULL) { if (fname == pname) { + /* + * Last '/' is at start of string. + * No directory part (dir is root) + */ pn->pn_pname = NULL; } else { + /* + * Directory part ends at the last '/' + * Temporarily truncate and copy + */ *fname = '\0'; pn->pn_pname = smb_pathname_strdup(sr, pname); *fname = '\\'; } ++fname; + /* fname is just after the '/' */ } else { + /* + * No '/' at all in the string. + * It's all filename + */ fname = pname; pn->pn_pname = NULL; } - if (fname[0] == '\0') { - pn->pn_fname = NULL; + /* + * Find end of the filename part of the string, + * which may be the null terminator, or may be + * the start of the optional :sname suffix. + */ + sname = strchr(fname, ':'); + if (sname == NULL) { + /* + * No :sname suffix. We're done. + */ + pn->pn_fname = smb_pathname_strdup(sr, fname); return; } - if (!smb_is_stream_name(fname)) { + /* + * We have a stream name, and maybe a stream type. + * Can't use smb_is_stream_name(fname) here because + * we need to allow sname="::$DATA" + */ + if (sname == fname) { + /* + * The ":sname" part is at the start of + * the file name, which means that the + * file name is "" and this pathname + * refers to a stream on the directory. + */ + pn->pn_fname = NULL; + } else { + /* + * The filename part ends at the ':' + * Temporarily truncate and copy + */ + *sname = '\0'; pn->pn_fname = smb_pathname_strdup(sr, fname); - return; + *sname = ':'; } /* - * find sname and stype in fname. - * sname can't be NULL smb_is_stream_name checks this + * Special case "::$DATA" which "points to" + * the "unnamed" stream (the file itself). + * Basically ignore the "::$DATA" */ - sname = strchr(fname, ':'); - if (sname == fname) - fname = NULL; - else { + if (strcasecmp(sname, "::$DATA") == 0) { + ASSERT(sname >= pname && + sname < (pname + strlen(pname))); *sname = '\0'; - pn->pn_fname = - smb_pathname_strdup(sr, fname); - *sname = ':'; + return; } + /* + * sname points to ":sname:stype" in pn_path + * If ":stype" is missing, add it, then set + * pn_stype to point after the 2nd ':' + * + * Caller knows pn_stype is NOT allocated. + * Allocations here are free'd via smb_srm_fini + */ pn->pn_sname = smb_pathname_strdup(sr, sname); pn->pn_stype = strchr(pn->pn_sname + 1, ':'); if (pn->pn_stype) { @@ -1065,6 +1117,9 @@ smb_validate_dirname(smb_request_t *sr, smb_pathname_t *pn) } } + if (pn->pn_sname) + return (smb_validate_stream_name(sr, pn)); + return (B_TRUE); } @@ -1230,14 +1285,6 @@ smb_validate_stream_name(smb_request_t *sr, smb_pathname_t *pn) ASSERT(pn); ASSERT(pn->pn_sname); - if ((!(pn->pn_sname)) || - ((pn->pn_pname) && !(pn->pn_fname))) { - smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID, - ERRDOS, ERROR_INVALID_NAME); - return (B_FALSE); - } - - if (pn->pn_stype != NULL) { for (i = 0; i < sizeof (strmtype) / sizeof (strmtype[0]); ++i) { if (strcasecmp(pn->pn_stype, strmtype[i]) == 0) diff --git a/usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c b/usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c index 6baef805aa..6f903cb2e0 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c +++ b/usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c @@ -22,6 +22,7 @@ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2022 Tintri by DDN, Inc. All rights reserved. + * Copyright 2022 RackTop Systems, Inc. */ #include <smbsrv/smb_kproto.h> @@ -88,8 +89,6 @@ static boolean_t smb_query_pipe_valid_infolev(smb_request_t *, uint16_t); static int smb_query_encode_response(smb_request_t *, smb_xa_t *, uint16_t, smb_queryinfo_t *); -static boolean_t smb_stream_fits(smb_request_t *, mbuf_chain_t *, - char *, uint32_t); static int smb_query_pathname(smb_request_t *, smb_node_t *, boolean_t, smb_queryinfo_t *); @@ -599,10 +598,9 @@ smb_query_encode_response(smb_request_t *sr, smb_xa_t *xa, * those streams but there should not be an entry for the unnamed * stream. * - * Note that the stream name lengths exclude the null terminator but - * the field lengths (i.e. next offset calculations) need to include - * the null terminator and be padded to a multiple of 8 bytes. The - * last entry does not seem to need any padding. + * Note that the stream names are NOT null terminated, and the lengths + * reflect that. Entries are aligned on 8-byte boundaries with padding + * and the "next offset" tells where the next entry begins. * * If an error is encountered when trying to read the stream entries * (smb_odir_read_streaminfo) it is treated as if there are no [more] @@ -619,19 +617,14 @@ smb_query_stream_info(smb_request_t *sr, mbuf_chain_t *mbc, smb_queryinfo_t *qinfo) { char *stream_name; - uint32_t next_offset; uint32_t stream_nlen; - uint32_t pad; - u_offset_t datasz, allocsz; - smb_streaminfo_t *sinfo, *sinfo_next; + smb_streaminfo_t *sinfo; int rc = 0; - boolean_t done = B_FALSE; - boolean_t eos = B_FALSE; + int prev_ent_off; + int cur_ent_off; smb_odir_t *od = NULL; uint32_t status = 0; - smb_node_t *fnode = qinfo->qi_node; - smb_attr_t *attr = &qinfo->qi_attr; ASSERT(fnode); if (SMB_IS_STREAM(fnode)) { @@ -641,10 +634,50 @@ smb_query_stream_info(smb_request_t *sr, mbuf_chain_t *mbc, ASSERT(fnode->n_magic == SMB_NODE_MAGIC); ASSERT(fnode->n_state != SMB_NODE_STATE_DESTROYING); - sinfo = kmem_alloc(sizeof (smb_streaminfo_t), KM_SLEEP); - sinfo_next = kmem_alloc(sizeof (smb_streaminfo_t), KM_SLEEP); - datasz = attr->sa_vattr.va_size; - allocsz = attr->sa_allocsz; + sinfo = smb_srm_alloc(sr, sizeof (smb_streaminfo_t)); + + /* + * Keep track of where the last entry starts so we can + * come back and poke the NextEntryOffset field. Also, + * after enumeration finishes, the caller uses this to + * poke the last entry again with zero to mark it as + * the end of the enumeration. + */ + ASSERT(mbc->chain_offset == 0); + cur_ent_off = prev_ent_off = 0; + + /* + * If the unnamed stream is a file, encode an entry for + * the unnamed stream. Note we can't generally get the + * size or allocsize from qi_attr because those may be + * from one of the named streams. Get the sizes. + */ + if (smb_node_is_file(fnode)) { + smb_attr_t attr; + uint64_t datasz, allocsz; + + bzero(&attr, sizeof (attr)); + attr.sa_mask = SMB_AT_SIZE | SMB_AT_ALLOCSZ; + rc = smb_node_getattr(sr, fnode, sr->user_cr, NULL, &attr); + if (rc != 0) { + status = smb_errno2status(rc); + goto out; + } + + stream_name = "::$DATA"; + stream_nlen = smb_ascii_or_unicode_strlen(sr, stream_name); + datasz = attr.sa_vattr.va_size; + allocsz = attr.sa_allocsz; + /* Leave NextEntryOffset=0, set later. */ + rc = smb_mbc_encodef(mbc, "%llqq#u", sr, + 0, stream_nlen, datasz, allocsz, + stream_nlen, stream_name); + if (rc != 0) { + /* Ran out of room. */ + status = NT_STATUS_BUFFER_OVERFLOW; + goto out; + } + } status = smb_odir_openat(sr, fnode, &od, B_TRUE); switch (status) { @@ -655,88 +688,61 @@ smb_query_stream_info(smb_request_t *sr, mbuf_chain_t *mbc, case NT_STATUS_NOT_SUPPORTED: /* No streams. */ status = 0; - done = B_TRUE; - break; + goto out; default: - return (status); + goto out; } - if (!done) { + for (;;) { + boolean_t eos = B_FALSE; rc = smb_odir_read_streaminfo(sr, od, sinfo, &eos); - if ((rc != 0) || (eos)) - done = B_TRUE; - } - - /* If not a directory, encode an entry for the unnamed stream. */ - if (qinfo->qi_isdir == 0) { - stream_name = "::$DATA"; - stream_nlen = smb_ascii_or_unicode_strlen(sr, stream_name); - next_offset = SMB_STREAM_ENCODE_FIXED_SZ + stream_nlen + - smb_ascii_or_unicode_null_len(sr); - - /* Can unnamed stream fit in response buffer? */ - if (MBC_ROOM_FOR(mbc, next_offset) == 0) { - done = B_TRUE; - status = NT_STATUS_BUFFER_OVERFLOW; - } else { - /* Can first named stream fit in rsp buffer? */ - if (!done && !smb_stream_fits(sr, mbc, sinfo->si_name, - next_offset)) { - done = B_TRUE; - status = NT_STATUS_BUFFER_OVERFLOW; - } - - if (done) - next_offset = 0; - - (void) smb_mbc_encodef(mbc, "%llqqu", sr, - next_offset, stream_nlen, datasz, allocsz, - stream_name); - } - } - - /* - * If there is no next entry, or there is not enough space in - * the response buffer for the next entry, the next_offset and - * padding are 0. - */ - while (!done) { - stream_nlen = smb_ascii_or_unicode_strlen(sr, sinfo->si_name); - sinfo_next->si_name[0] = 0; - - rc = smb_odir_read_streaminfo(sr, od, sinfo_next, &eos); if ((rc != 0) || (eos)) { - done = B_TRUE; - } else { - next_offset = SMB_STREAM_ENCODE_FIXED_SZ + - stream_nlen + - smb_ascii_or_unicode_null_len(sr); - pad = smb_pad_align(next_offset, 8); - next_offset += pad; - - /* Can next named stream fit in response buffer? */ - if (!smb_stream_fits(sr, mbc, sinfo_next->si_name, - next_offset)) { - done = B_TRUE; - status = NT_STATUS_BUFFER_OVERFLOW; - } + status = 0; + break; // normal termination } - if (done) { - next_offset = 0; - pad = 0; + /* + * We have a directory entry to process. + * Align before encoding. + */ + rc = smb_mbc_put_align(mbc, 8); + if (rc != 0) { + status = NT_STATUS_BUFFER_OVERFLOW; + break; } + cur_ent_off = mbc->chain_offset; - (void) smb_mbc_encodef(mbc, "%llqqu#.", - sr, next_offset, stream_nlen, + /* + * Encode it. + */ + stream_name = sinfo->si_name; + stream_nlen = smb_ascii_or_unicode_strlen(sr, stream_name); + /* Leave NextEntryOffset=0, set later. */ + rc = smb_mbc_encodef(mbc, "%llqq#u", sr, + 0, stream_nlen, sinfo->si_size, sinfo->si_alloc_size, - sinfo->si_name, pad); + stream_nlen, stream_name); + if (rc != 0) { + status = NT_STATUS_BUFFER_OVERFLOW; + break; + } - (void) memcpy(sinfo, sinfo_next, sizeof (smb_streaminfo_t)); + /* + * We succeeded encoding the current entry, so + * fill in NextEntryOffset in the previous entry. + * When listing streams on a file, we're always at + * the 2nd or later entry due to "::$DATA" above. + * However, when listing streams on a directory, + * there might not be previous entry. + */ + if (cur_ent_off > 0) { + (void) smb_mbc_poke(mbc, prev_ent_off, "l", + cur_ent_off - prev_ent_off); + } + prev_ent_off = cur_ent_off; } - kmem_free(sinfo, sizeof (smb_streaminfo_t)); - kmem_free(sinfo_next, sizeof (smb_streaminfo_t)); +out: if (od) { smb_odir_close(od); smb_odir_release(od); @@ -746,33 +752,6 @@ smb_query_stream_info(smb_request_t *sr, mbuf_chain_t *mbc, } /* - * smb_stream_fits - * - * Check if the named stream entry can fit in the response buffer. - * - * Required space = - * offset (size of current entry) - * + SMB_STREAM_ENCODE_FIXED_SIZE - * + length of encoded stream name - * + length of null terminator - * + alignment padding - */ -static boolean_t -smb_stream_fits(smb_request_t *sr, mbuf_chain_t *mbc, - char *name, uint32_t offset) -{ - uint32_t len, pad; - - len = SMB_STREAM_ENCODE_FIXED_SZ + - smb_ascii_or_unicode_strlen(sr, name) + - smb_ascii_or_unicode_null_len(sr); - pad = smb_pad_align(len, 8); - len += pad; - - return (MBC_ROOM_FOR(mbc, offset + len) != 0); -} - -/* * smb_query_fileinfo * * Populate smb_queryinfo_t structure for SMB_FTYPE_DISK |