/* * This is the SMB2 handler for new smb requests, called from * smb_session_reader after SMB negotiate is done. For most SMB2 * requests, we just enqueue them for the smb_session_worker to * execute via the task queue, so they can block for resources * without stopping the reader thread. A few protocol messages * are special cases and are handled directly here in the reader * thread so they don't wait for taskq scheduling. * * This function must either enqueue the new request for * execution via the task queue, or execute it directly * and then free it. If this returns non-zero, the caller * will drop the session. */ int smb2sr_newrq(smb_request_t *sr) { uint32_t magic; uint16_t command; int rc; magic = LE_IN32(sr->sr_request_buf); if (magic != SMB2_PROTOCOL_MAGIC) { smb_request_free(sr); /* will drop the connection */ return (EPROTO); } /* * Execute Cancel requests immediately, (here in the * reader thread) so they won't wait for any other * commands we might already have in the task queue. * Cancel also skips signature verification and * does not consume a sequence number. * [MS-SMB2] 3.2.4.24 Cancellation... */ command = LE_IN16((uint8_t *)sr->sr_request_buf + 12); if (command == SMB2_CANCEL) { rc = smb2sr_newrq_cancel(sr); smb_request_free(sr); return (rc); } /* * XXX With SMB3 this is supposed to increment based on * the number of credits consumed by a request. Todo */ if (sr->session->signing.flags & SMB_SIGNING_ENABLED) { /* XXX MS-SMB2 is unclear on this. todo */ sr->session->signing.seqnum++; sr->sr_seqnum = sr->session->signing.seqnum; sr->reply_seqnum = sr->sr_seqnum; } /* * Submit the request to the task queue, which calls * smb2_dispatch_request when the workload permits. */ sr->sr_time_submitted = gethrtime(); sr->sr_state = SMB_REQ_STATE_SUBMITTED; sr->work_func = smb2sr_work; smb_srqueue_waitq_enter(sr->session->s_srqueue); (void) taskq_dispatch(sr->session->s_server->sv_worker_pool, smb_session_worker, sr, TQ_SLEEP); return (0); }
void smb_reply_notify_change_request(smb_request_t *sr) { smb_node_t *node; smb_srqueue_t *srq; int total_bytes, n_setup, n_param, n_data; int param_off, param_pad, data_off, data_pad; struct smb_xa *xa; smb_error_t err; SMB_REQ_VALID(sr); srq = sr->session->s_srqueue; smb_srqueue_waitq_to_runq(srq); xa = sr->r_xa; node = sr->sr_ncr.nc_node; if (--node->waiting_event == 0) { node->flags &= ~(NODE_FLAGS_NOTIFY_CHANGE | NODE_FLAGS_CHANGED); smb_fem_fcn_uninstall(node); } mutex_enter(&sr->sr_mutex); switch (sr->sr_state) { case SMB_REQ_STATE_EVENT_OCCURRED: sr->sr_state = SMB_REQ_STATE_ACTIVE; /* many things changed */ (void) smb_mbc_encodef(&xa->rep_data_mb, "l", 0L); /* setup the NT transact reply */ n_setup = MBC_LENGTH(&xa->rep_setup_mb); n_param = MBC_LENGTH(&xa->rep_param_mb); n_data = MBC_LENGTH(&xa->rep_data_mb); n_setup = (n_setup + 1) / 2; /* Convert to setup words */ param_pad = 1; /* must be one */ param_off = param_pad + 32 + 37 + (n_setup << 1) + 2; /* Pad to 4 bytes */ data_pad = (4 - ((param_off + n_param) & 3)) % 4; /* Param off from hdr */ data_off = param_off + n_param + data_pad; total_bytes = param_pad + n_param + data_pad + n_data; (void) smbsr_encode_result(sr, 18+n_setup, total_bytes, "b3.llllllllbCw#.C#.C", 18 + n_setup, /* wct */ n_param, /* Total Parameter Bytes */ n_data, /* Total Data Bytes */ n_param, /* Total Parameter Bytes this buffer */ param_off, /* Param offset from header start */ 0, /* Param displacement */ n_data, /* Total Data Bytes this buffer */ data_off, /* Data offset from header start */ 0, /* Data displacement */ n_setup, /* suwcnt */ &xa->rep_setup_mb, /* setup[] */ total_bytes, /* Total data bytes */ param_pad, &xa->rep_param_mb, data_pad, &xa->rep_data_mb); break; case SMB_REQ_STATE_CANCELED: err.status = NT_STATUS_CANCELLED; err.errcls = ERRDOS; err.errcode = ERROR_OPERATION_ABORTED; smbsr_set_error(sr, &err); (void) smb_mbc_encodef(&sr->reply, "bwbw", (short)0, 0L, (short)0, 0L); sr->smb_wct = 0; sr->smb_bcc = 0; break; default: ASSERT(0); } mutex_exit(&sr->sr_mutex); /* Setup the header */ (void) smb_mbc_poke(&sr->reply, 0, SMB_HEADER_ED_FMT, sr->first_smb_com, sr->smb_rcls, sr->smb_reh, sr->smb_err, sr->smb_flg | SMB_FLAGS_REPLY, sr->smb_flg2, sr->smb_pid_high, sr->smb_sig, sr->smb_tid, sr->smb_pid, sr->smb_uid, sr->smb_mid); if (sr->session->signing.flags & SMB_SIGNING_ENABLED) smb_sign_reply(sr, NULL); /* send the reply */ DTRACE_PROBE1(ncr__reply, struct smb_request *, sr) (void) smb_session_send(sr->session, 0, &sr->reply); smbsr_cleanup(sr); mutex_enter(&sr->sr_mutex); sr->sr_state = SMB_REQ_STATE_COMPLETED; mutex_exit(&sr->sr_mutex); smb_srqueue_runq_exit(srq); smb_request_free(sr); }
/* * smb2sr_work * * This function processes each SMB command in the current request * (which may be a compound request) building a reply containing * SMB reply messages, one-to-one with the SMB commands. Some SMB * commands (change notify, blocking pipe read) may require both an * "interim response" and a later "async response" at completion. * In such cases, we'll encode the interim response in the reply * compound we're building, and put the (now async) command on a * list of commands that need further processing. After we've * finished processing the commands in this compound and building * the compound reply, we'll send the compound reply, and finally * process the list of async commands. * * As we work our way through the compound request and reply, * we need to keep track of the bounds of the current request * and reply. For the request, this uses an MBC_SHADOW_CHAIN * that begins at smb2_cmd_hdr. The reply is appended to the * sr->reply chain starting at smb2_reply_hdr. * * This function must always free the smb request. */ void smb2sr_work(struct smb_request *sr) { smb_session_t *session; uint32_t msg_len; int rc; boolean_t disconnect = B_FALSE; session = sr->session; ASSERT(sr->tid_tree == 0); ASSERT(sr->uid_user == 0); ASSERT(sr->fid_ofile == 0); sr->smb_fid = (uint16_t)-1; /* temporary until we identify a user */ sr->user_cr = zone_kcred(); mutex_enter(&sr->sr_mutex); switch (sr->sr_state) { case SMB_REQ_STATE_SUBMITTED: case SMB_REQ_STATE_CLEANED_UP: sr->sr_state = SMB_REQ_STATE_ACTIVE; break; default: ASSERT(0); /* FALLTHROUGH */ case SMB_REQ_STATE_CANCELED: goto complete_unlock_free; } mutex_exit(&sr->sr_mutex); cmd_start: /* * Reserve space for the reply header, and save the offset. * The reply header will be overwritten later. */ sr->smb2_reply_hdr = sr->reply.chain_offset; (void) smb_mbc_encodef(&sr->reply, "#.", SMB2_HDR_SIZE); /* * Decode the request header * * Most problems with decoding will result in the error * STATUS_INVALID_PARAMETER. If the decoding problem * prevents continuing, we'll close the connection. * [MS-SMB2] 3.3.5.2.6 Handling Incorrectly Formatted... */ sr->smb2_status = 0; sr->smb2_cmd_hdr = sr->command.chain_offset; if ((rc = smb2_decode_header(sr)) != 0) { cmn_err(CE_WARN, "clnt %s bad SMB2 header", session->ip_addr_str); disconnect = B_TRUE; goto cleanup; } /* * Figure out the length of data following the SMB2 header. * It ends at either the next SMB2 header if there is one * (smb2_next_command != 0) or at the end of the message. */ if (sr->smb2_next_command != 0) { /* [MS-SMB2] says this is 8-byte aligned */ msg_len = sr->smb2_next_command; if ((msg_len & 7) != 0 || (msg_len < SMB2_HDR_SIZE) || ((sr->smb2_cmd_hdr + msg_len) > sr->command.max_bytes)) { cmn_err(CE_WARN, "clnt %s bad SMB2 next cmd", session->ip_addr_str); disconnect = B_TRUE; goto cleanup; } } else { msg_len = sr->command.max_bytes - sr->smb2_cmd_hdr; } /* * Setup a shadow chain for this SMB2 command, starting * with the header and ending at either the next command * or the end of the message. Note that we've already * decoded the header, so chain_offset is now positioned * at the end of the header. The signing check needs the * entire SMB2 command, so we'll shadow starting at the * smb2_cmd_hdr offset. After the signing check, we'll * move chain_offset up to the end of the header. */ (void) MBC_SHADOW_CHAIN(&sr->smb_data, &sr->command, sr->smb2_cmd_hdr, msg_len); /* * Verify SMB signature if signing is enabled and active now. * [MS-SMB2] 3.3.5.2.4 Verifying the Signature */ if ((sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) != 0) { rc = smb2_sign_check_request(sr); if (rc != 0) { DTRACE_PROBE1(smb2__sign__check, smb_request_t, sr); if (session->signing.flags & SMB_SIGNING_CHECK) { smb2sr_put_error(sr, NT_STATUS_ACCESS_DENIED); goto cmd_finish; } } } /* * Now that the signing check is done with smb_data, * advance past the SMB2 header we decoded above. * This leaves sr->smb_data correctly positioned * for command-specific decoding in the dispatch * function called next. */ sr->smb_data.chain_offset = sr->smb2_cmd_hdr + SMB2_HDR_SIZE; /* * Default credit response. Command handler may modify. */ sr->smb2_credit_response = sr->smb2_credit_request; /* * Common dispatch (for sync & async) */ rc = smb2sr_dispatch(sr, NULL); switch (rc) { case SDRC_SUCCESS: break; default: /* * SMB2 does not use the other dispatch return codes. * If we see something else, log an event so we'll * know something is returning bogus status codes. * If you see these in the log, use dtrace to find * the code returning something else. */ #ifdef DEBUG cmn_err(CE_NOTE, "smb2sr_dispatch -> 0x%x", rc); #endif /* FALLTHROUGH */ case SDRC_ERROR: if (sr->smb2_status == 0) sr->smb2_status = NT_STATUS_INTERNAL_ERROR; break; case SDRC_DROP_VC: disconnect = B_TRUE; goto cleanup; } /* * If there's a next command, figure out where it starts, * and fill in the next command offset for the reply. * Note: We sanity checked smb2_next_command above * (the offset to the next command). Similarly set * smb2_next_reply as the offset to the next reply. */ cmd_finish: if (sr->smb2_next_command != 0) { sr->command.chain_offset = sr->smb2_cmd_hdr + sr->smb2_next_command; sr->smb2_next_reply = sr->reply.chain_offset - sr->smb2_reply_hdr; } else { sr->smb2_next_reply = 0; } /* * Overwrite the SMB2 header for the response of * this command (possibly part of a compound). */ sr->smb2_hdr_flags |= SMB2_FLAGS_SERVER_TO_REDIR; (void) smb2_encode_header(sr, B_TRUE); if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) smb2_sign_reply(sr); if (sr->smb2_next_command != 0) goto cmd_start; /* * We've done all the commands in this compound. * Send it out. */ smb2_send_reply(sr); /* * If any of the requests "went async", process those now. */ if (sr->sr_async_req != NULL) { smb2sr_do_async(sr); } cleanup: if (disconnect) { smb_rwx_rwenter(&session->s_lock, RW_WRITER); switch (session->s_state) { case SMB_SESSION_STATE_DISCONNECTED: case SMB_SESSION_STATE_TERMINATED: break; default: smb_soshutdown(session->sock); session->s_state = SMB_SESSION_STATE_DISCONNECTED; break; } smb_rwx_rwexit(&session->s_lock); } mutex_enter(&sr->sr_mutex); complete_unlock_free: sr->sr_state = SMB_REQ_STATE_COMPLETED; mutex_exit(&sr->sr_mutex); smb_request_free(sr); }