/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Dispatch function for SMB2_CREATE * [MS-SMB2] 2.2.13 */ #include #include /* * Some flags used locally to keep track of which Create Context * names have been provided and/or requested. */ #define CCTX_EA_BUFFER 1 #define CCTX_SD_BUFFER 2 #define CCTX_DH_REQUEST 4 #define CCTX_DH_RECONNECT 8 #define CCTX_ALLOCATION_SIZE 0x10 #define CCTX_QUERY_MAX_ACCESS 0x20 #define CCTX_TIMEWARP_TOKEN 0x40 #define CCTX_QUERY_ON_DISK_ID 0x80 #define CCTX_REQUEST_LEASE 0x100 typedef struct smb2_create_ctx_elem { uint32_t cce_len; mbuf_chain_t cce_mbc; } smb2_create_ctx_elem_t; typedef struct smb2_create_ctx { uint_t cc_in_flags; /* CCTX_... */ uint_t cc_out_flags; /* CCTX_... */ /* Elements we may see in the request. */ smb2_create_ctx_elem_t cc_in_ext_attr; smb2_create_ctx_elem_t cc_in_sec_desc; smb2_create_ctx_elem_t cc_in_dh_request; smb2_create_ctx_elem_t cc_in_dh_reconnect; smb2_create_ctx_elem_t cc_in_alloc_size; smb2_create_ctx_elem_t cc_in_time_warp; smb2_create_ctx_elem_t cc_in_req_lease; /* Elements we my place in the response */ smb2_create_ctx_elem_t cc_out_max_access; smb2_create_ctx_elem_t cc_out_file_id; } smb2_create_ctx_t; static uint32_t smb2_decode_create_ctx( mbuf_chain_t *, smb2_create_ctx_t *); static uint32_t smb2_encode_create_ctx( mbuf_chain_t *, smb2_create_ctx_t *); static int smb2_encode_create_ctx_elem( mbuf_chain_t *, smb2_create_ctx_elem_t *, uint32_t); static void smb2_free_create_ctx(smb2_create_ctx_t *); smb_sdrc_t smb2_create(smb_request_t *sr) { smb_attr_t *attr; smb2_create_ctx_elem_t *cce; smb2_create_ctx_t cctx; mbuf_chain_t cc_mbc; smb_arg_open_t *op = &sr->arg.open; smb_ofile_t *of = NULL; uint16_t StructSize; uint8_t SecurityFlags; uint8_t OplockLevel; uint32_t ImpersonationLevel; uint64_t SmbCreateFlags; uint64_t Reserved4; uint16_t NameOffset; uint16_t NameLength; uint32_t CreateCtxOffset; uint32_t CreateCtxLength; smb2fid_t smb2fid; uint32_t status; int skip; int rc = 0; bzero(&cctx, sizeof (cctx)); bzero(&cc_mbc, sizeof (cc_mbc)); /* * Paranoia. This will set sr->fid_ofile, so * if we already have one, release it now. */ if (sr->fid_ofile != NULL) { smb_ofile_request_complete(sr->fid_ofile); smb_ofile_release(sr->fid_ofile); sr->fid_ofile = NULL; } /* * SMB2 Create request */ rc = smb_mbc_decodef( &sr->smb_data, "wbblqqlllllwwll", &StructSize, /* w */ &SecurityFlags, /* b */ &OplockLevel, /* b */ &ImpersonationLevel, /* l */ &SmbCreateFlags, /* q */ &Reserved4, /* q */ &op->desired_access, /* l */ &op->dattr, /* l */ &op->share_access, /* l */ &op->create_disposition, /* l */ &op->create_options, /* l */ &NameOffset, /* w */ &NameLength, /* w */ &CreateCtxOffset, /* l */ &CreateCtxLength); /* l */ if (rc != 0 || StructSize != 57) return (SDRC_ERROR); /* * We're normally positioned at the path name now, * but there could be some padding before it. */ skip = (NameOffset + sr->smb2_cmd_hdr) - sr->smb_data.chain_offset; if (skip < 0) { status = NT_STATUS_OBJECT_PATH_INVALID; goto errout; } if (skip > 0) (void) smb_mbc_decodef(&sr->smb_data, "#.", skip); /* * Get the path name */ if (NameLength >= SMB_MAXPATHLEN) { status = NT_STATUS_OBJECT_PATH_INVALID; goto errout; } if (NameLength == 0) { op->fqi.fq_path.pn_path = "\\"; } else { rc = smb_mbc_decodef(&sr->smb_data, "%#U", sr, NameLength, &op->fqi.fq_path.pn_path); if (rc) { status = NT_STATUS_OBJECT_PATH_INVALID; goto errout; } } op->fqi.fq_dnode = sr->tid_tree->t_snode; switch (OplockLevel) { case SMB2_OPLOCK_LEVEL_NONE: op->op_oplock_level = SMB_OPLOCK_NONE; break; case SMB2_OPLOCK_LEVEL_II: op->op_oplock_level = SMB_OPLOCK_LEVEL_II; break; case SMB2_OPLOCK_LEVEL_EXCLUSIVE: op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE; break; case SMB2_OPLOCK_LEVEL_BATCH: op->op_oplock_level = SMB_OPLOCK_BATCH; break; case SMB2_OPLOCK_LEVEL_LEASE: status = NT_STATUS_INVALID_PARAMETER; goto errout; } op->op_oplock_levelII = B_TRUE; /* * ImpersonationLevel (spec. says ignore) * SmbCreateFlags (spec. says ignore) */ if ((op->create_options & FILE_DELETE_ON_CLOSE) && !(op->desired_access & DELETE)) { status = NT_STATUS_INVALID_PARAMETER; goto errout; } if (op->create_disposition > FILE_MAXIMUM_DISPOSITION) { status = NT_STATUS_INVALID_PARAMETER; goto errout; } if (op->dattr & FILE_FLAG_WRITE_THROUGH) op->create_options |= FILE_WRITE_THROUGH; if (op->dattr & FILE_FLAG_DELETE_ON_CLOSE) op->create_options |= FILE_DELETE_ON_CLOSE; if (op->dattr & FILE_FLAG_BACKUP_SEMANTICS) op->create_options |= FILE_OPEN_FOR_BACKUP_INTENT; if (op->create_options & FILE_OPEN_FOR_BACKUP_INTENT) sr->user_cr = smb_user_getprivcred(sr->uid_user); /* * If there is a "Create Context" payload, decode it. * This may carry things like a security descriptor, * extended attributes, etc. to be used in create. * * The create ctx buffer must start after the headers * and file name, and must be 8-byte aligned. */ if (CreateCtxLength != 0) { if ((CreateCtxOffset & 7) != 0 || (CreateCtxOffset + sr->smb2_cmd_hdr) < sr->smb_data.chain_offset) { status = NT_STATUS_INVALID_PARAMETER; goto errout; } rc = MBC_SHADOW_CHAIN(&cc_mbc, &sr->smb_data, sr->smb2_cmd_hdr + CreateCtxOffset, CreateCtxLength); if (rc) { status = NT_STATUS_INVALID_PARAMETER; goto errout; } status = smb2_decode_create_ctx(&cc_mbc, &cctx); if (status) goto errout; if (cctx.cc_in_flags & CCTX_EA_BUFFER) { status = NT_STATUS_EAS_NOT_SUPPORTED; goto errout; } if (cctx.cc_in_flags & CCTX_SD_BUFFER) { smb_sd_t sd; cce = &cctx.cc_in_sec_desc; status = smb_decode_sd( &cce->cce_mbc, &sd); if (status) goto errout; op->sd = kmem_alloc(sizeof (sd), KM_SLEEP); *op->sd = sd; } if (cctx.cc_in_flags & CCTX_ALLOCATION_SIZE) { cce = &cctx.cc_in_alloc_size; rc = smb_mbc_decodef(&cce->cce_mbc, "q", &op->dsize); if (rc) { status = NT_STATUS_INVALID_PARAMETER; goto errout; } } /* * Support for opening "Previous Versions". * [MS-SMB2] 2.2.13.2.7 Data is an NT time. */ if (cctx.cc_in_flags & CCTX_TIMEWARP_TOKEN) { uint64_t timewarp; cce = &cctx.cc_in_time_warp; status = smb_mbc_decodef(&cce->cce_mbc, "q", &timewarp); if (status) goto errout; smb_time_nt_to_unix(timewarp, &op->timewarp); op->create_timewarp = B_TRUE; } } /* * The real open call. Note: this gets attributes into * op->fqi.fq_fattr (SMB_AT_ALL). We need those below. */ status = smb_common_open(sr); if (status != NT_STATUS_SUCCESS) goto errout; attr = &op->fqi.fq_fattr; /* * Convert the negotiate Oplock level back into * SMB2 encoding form. */ switch (op->op_oplock_level) { default: case SMB_OPLOCK_NONE: OplockLevel = SMB2_OPLOCK_LEVEL_NONE; break; case SMB_OPLOCK_LEVEL_II: OplockLevel = SMB2_OPLOCK_LEVEL_II; break; case SMB_OPLOCK_EXCLUSIVE: OplockLevel = SMB2_OPLOCK_LEVEL_EXCLUSIVE; break; case SMB_OPLOCK_BATCH: OplockLevel = SMB2_OPLOCK_LEVEL_BATCH; break; } /* * NB: after the above smb_common_open() success, * we have a handle allocated (sr->fid_ofile). * If we don't return success, we must close it. * * Using sr->smb_fid as the file handle for now, * though it could later be something larger, * (16 bytes) similar to an NFSv4 open handle. */ of = sr->fid_ofile; smb2fid.persistent = 0; smb2fid.temporal = sr->smb_fid; switch (sr->tid_tree->t_res_type & STYPE_MASK) { case STYPE_DISKTREE: case STYPE_PRINTQ: if (op->create_options & FILE_DELETE_ON_CLOSE) smb_ofile_set_delete_on_close(of); break; } /* * Build the Create Context to return; first the * per-element parts, then the aggregated buffer. * * No response for these: * CCTX_EA_BUFFER * CCTX_SD_BUFFER * CCTX_ALLOCATION_SIZE * CCTX_TIMEWARP_TOKEN * * We don't handle these yet. * CCTX_DH_REQUEST * CCTX_DH_RECONNECT * CCTX_REQUEST_LEASE */ if (cctx.cc_in_flags & CCTX_QUERY_MAX_ACCESS) { cce = &cctx.cc_out_max_access; uint32_t MaxAccess = 0; if (of->f_node != NULL) { smb_fsop_eaccess(sr, of->f_cr, of->f_node, &MaxAccess); } MaxAccess |= of->f_granted_access; cce->cce_len = 8; cce->cce_mbc.max_bytes = 8; (void) smb_mbc_encodef(&cce->cce_mbc, "ll", 0, MaxAccess); cctx.cc_out_flags |= CCTX_QUERY_MAX_ACCESS; } if ((cctx.cc_in_flags & CCTX_QUERY_ON_DISK_ID) != 0 && of->f_node != NULL) { cce = &cctx.cc_out_file_id; fsid_t fsid; fsid = SMB_NODE_FSID(of->f_node); cce->cce_len = 32; cce->cce_mbc.max_bytes = 32; (void) smb_mbc_encodef( &cce->cce_mbc, "qll.15.", op->fileid, /* q */ fsid.val[0], /* l */ fsid.val[1]); /* l */ /* reserved (16 bytes) .15. */ cctx.cc_out_flags |= CCTX_QUERY_ON_DISK_ID; } if (cctx.cc_out_flags) { sr->raw_data.max_bytes = smb2_max_trans; status = smb2_encode_create_ctx(&sr->raw_data, &cctx); if (status) goto errout; } /* * SMB2 Create reply */ rc = smb_mbc_encodef( &sr->reply, "wb.lTTTTqqllqqll", 89, /* StructSize */ /* w */ OplockLevel, /* b */ op->action_taken, /* l */ &attr->sa_crtime, /* T */ &attr->sa_vattr.va_atime, /* T */ &attr->sa_vattr.va_mtime, /* T */ &attr->sa_vattr.va_ctime, /* T */ attr->sa_allocsz, /* q */ attr->sa_vattr.va_size, /* q */ attr->sa_dosattr, /* l */ 0, /* reserved2 */ /* l */ smb2fid.persistent, /* q */ smb2fid.temporal, /* q */ 0, /* CreateCtxOffset l */ 0); /* CreateCtxLength l */ if (rc != 0) { status = NT_STATUS_UNSUCCESSFUL; goto errout; } CreateCtxOffset = sr->reply.chain_offset - sr->smb2_reply_hdr; CreateCtxLength = MBC_LENGTH(&sr->raw_data); if (CreateCtxLength != 0) { /* * Overwrite CreateCtxOffset, CreateCtxLength, pad */ sr->reply.chain_offset -= 8; rc = smb_mbc_encodef( &sr->reply, "ll#C", CreateCtxOffset, /* l */ CreateCtxLength, /* l */ CreateCtxLength, /* # */ &sr->raw_data); /* C */ if (rc != 0) { status = NT_STATUS_UNSUCCESSFUL; goto errout; } } else { (void) smb_mbc_encodef(&sr->reply, "."); } return (SDRC_SUCCESS); errout: if (of != NULL) smb_ofile_close(of, 0); if (cctx.cc_out_flags) smb2_free_create_ctx(&cctx); smb2sr_put_error(sr, status); return (SDRC_SUCCESS); } /* * Decode an SMB2 Create Context buffer into our internal form. * No policy decisions about what's supported here, just decode. */ static uint32_t smb2_decode_create_ctx(mbuf_chain_t *in_mbc, smb2_create_ctx_t *cc) { smb2_create_ctx_elem_t *cce; mbuf_chain_t name_mbc; union { uint32_t i; char ch[4]; } cc_name; uint32_t status; int32_t next_off; uint32_t data_len; uint16_t data_off; uint16_t name_off; uint16_t name_len; int top_offset; int rc; status = NT_STATUS_INVALID_PARAMETER; for (;;) { cce = NULL; top_offset = in_mbc->chain_offset; rc = smb_mbc_decodef( in_mbc, "lww..wl", &next_off, /* l */ &name_off, /* w */ &name_len, /* w */ /* reserved .. */ &data_off, /* w */ &data_len); /* l */ if (rc) break; /* * The Create Context "name", per [MS-SMB] 2.2.13.2 * They're defined as network-order integers for our * switch below. We don't have routines to decode * native order, so read as char[4] then ntohl. * NB: in SMB3, some of these are 8 bytes. */ if ((top_offset + name_off) < in_mbc->chain_offset) break; rc = MBC_SHADOW_CHAIN(&name_mbc, in_mbc, top_offset + name_off, name_len); if (rc) break; rc = smb_mbc_decodef(&name_mbc, "4c", &cc_name); if (rc) break; cc_name.i = ntohl(cc_name.i); switch (cc_name.i) { case SMB2_CREATE_EA_BUFFER: /* ("ExtA") */ cc->cc_in_flags |= CCTX_EA_BUFFER; cce = &cc->cc_in_ext_attr; break; case SMB2_CREATE_SD_BUFFER: /* ("SecD") */ cc->cc_in_flags |= CCTX_SD_BUFFER; cce = &cc->cc_in_sec_desc; break; case SMB2_CREATE_DURABLE_HANDLE_REQUEST: /* ("DHnQ") */ cc->cc_in_flags |= CCTX_DH_REQUEST; cce = &cc->cc_in_dh_request; break; case SMB2_CREATE_DURABLE_HANDLE_RECONNECT: /* ("DHnC") */ cc->cc_in_flags |= CCTX_DH_RECONNECT; cce = &cc->cc_in_dh_reconnect; break; case SMB2_CREATE_ALLOCATION_SIZE: /* ("AISi") */ cc->cc_in_flags |= CCTX_ALLOCATION_SIZE; cce = &cc->cc_in_alloc_size; break; case SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQ: /* ("MxAc") */ cc->cc_in_flags |= CCTX_QUERY_MAX_ACCESS; /* no input data for this */ break; case SMB2_CREATE_TIMEWARP_TOKEN: /* ("TWrp") */ cc->cc_in_flags |= CCTX_TIMEWARP_TOKEN; cce = &cc->cc_in_time_warp; break; case SMB2_CREATE_QUERY_ON_DISK_ID: /* ("QFid") */ cc->cc_in_flags |= CCTX_QUERY_ON_DISK_ID; /* no input data for this */ break; case SMB2_CREATE_REQUEST_LEASE: /* ("RqLs") */ cc->cc_in_flags |= CCTX_REQUEST_LEASE; cce = &cc->cc_in_req_lease; break; default: /* * Unknown create context values are normal, and * should be ignored. However, in debug mode, * let's log them so we know which ones we're * not handling (and may want to add). */ #ifdef DEBUG cmn_err(CE_NOTE, "unknown create context ID 0x%x", cc_name.i); #endif cce = NULL; break; } if (cce != NULL && data_len != 0) { if ((data_off & 7) != 0) break; if ((top_offset + data_off) < in_mbc->chain_offset) break; rc = MBC_SHADOW_CHAIN(&cce->cce_mbc, in_mbc, top_offset + data_off, data_len); if (rc) break; cce->cce_len = data_len; } if (next_off == 0) { /* Normal loop termination */ status = 0; break; } if ((next_off & 7) != 0) break; if ((top_offset + next_off) < in_mbc->chain_offset) break; if ((top_offset + next_off) > in_mbc->max_bytes) break; in_mbc->chain_offset = top_offset + next_off; } return (status); } /* * Encode an SMB2 Create Context buffer from our internal form. */ /* ARGSUSED */ static uint32_t smb2_encode_create_ctx(mbuf_chain_t *mbc, smb2_create_ctx_t *cc) { smb2_create_ctx_elem_t *cce; int last_top = -1; int rc; if (cc->cc_out_flags & CCTX_QUERY_MAX_ACCESS) { cce = &cc->cc_out_max_access; last_top = mbc->chain_offset; rc = smb2_encode_create_ctx_elem(mbc, cce, SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQ); if (rc) return (NT_STATUS_INTERNAL_ERROR); (void) smb_mbc_poke(mbc, last_top, "l", mbc->chain_offset - last_top); } if (cc->cc_out_flags & CCTX_QUERY_ON_DISK_ID) { cce = &cc->cc_out_file_id; last_top = mbc->chain_offset; rc = smb2_encode_create_ctx_elem(mbc, cce, SMB2_CREATE_QUERY_ON_DISK_ID); if (rc) return (NT_STATUS_INTERNAL_ERROR); (void) smb_mbc_poke(mbc, last_top, "l", mbc->chain_offset - last_top); } if (last_top >= 0) (void) smb_mbc_poke(mbc, last_top, "l", 0); return (0); } static int smb2_encode_create_ctx_elem(mbuf_chain_t *out_mbc, smb2_create_ctx_elem_t *cce, uint32_t id) { union { uint32_t i; char ch[4]; } cc_name; int rc; /* as above */ cc_name.i = htonl(id); /* * This is the header, per [MS-SMB2] 2.2.13.2 * Sorry about the fixed offsets. We know we'll * layout the data part as [name, payload] and * name is a fixed length, so this easy. * The final layout looks like this: * a: this header (16 bytes) * b: the name (4 bytes, 4 pad) * c: the payload (variable) * * Note that "Next elem." is filled in later. */ rc = smb_mbc_encodef( out_mbc, "lwwwwl", 0, /* Next offset l */ 16, /* NameOffset w */ 4, /* NameLength w */ 0, /* Reserved w */ 24, /* DataOffset w */ cce->cce_len); /* l */ if (rc) return (rc); /* * Now the "name" and payload. */ rc = smb_mbc_encodef( out_mbc, "4c4.#C", cc_name.ch, /* 4c4. */ cce->cce_len, /* # */ &cce->cce_mbc); /* C */ return (rc); } static void smb2_free_create_ctx(smb2_create_ctx_t *cc) { smb2_create_ctx_elem_t *cce; if (cc->cc_out_flags & CCTX_QUERY_MAX_ACCESS) { cce = &cc->cc_out_max_access; MBC_FLUSH(&cce->cce_mbc); } if (cc->cc_out_flags & CCTX_QUERY_ON_DISK_ID) { cce = &cc->cc_out_file_id; MBC_FLUSH(&cce->cce_mbc); } }