760 lines · 17.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *   Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org> *   Copyright (C) 2018 Samsung Electronics Co., Ltd. */ #include "glob.h"#include "oplock.h"#include "misc.h"#include <linux/sched/signal.h>#include <linux/workqueue.h>#include <linux/sysfs.h>#include <linux/module.h>#include <linux/moduleparam.h> #include "server.h"#include "smb_common.h"#include "smb2pdu.h"#include "../common/smb2status.h"#include "connection.h"#include "transport_ipc.h"#include "mgmt/user_session.h"#include "crypto_ctx.h"#include "auth.h"#include "stats.h"#include "compress.h"#include "mgmt/share_config.h"#include "mgmt/tree_connect.h" int ksmbd_debug_types; struct ksmbd_server_config server_conf; enum SERVER_CTRL_TYPE {	SERVER_CTRL_TYPE_INIT,	SERVER_CTRL_TYPE_RESET,}; struct server_ctrl_struct {	int			type;	struct work_struct	ctrl_work;}; static DEFINE_MUTEX(ctrl_lock); static int ___server_conf_set(int idx, char *val){	if (idx >= ARRAY_SIZE(server_conf.conf))		return -EINVAL; 	if (!val || val[0] == 0x00)		return -EINVAL; 	kfree(server_conf.conf[idx]);	server_conf.conf[idx] = kstrdup(val, KSMBD_DEFAULT_GFP);	if (!server_conf.conf[idx])		return -ENOMEM;	return 0;} int ksmbd_set_netbios_name(char *v){	return ___server_conf_set(SERVER_CONF_NETBIOS_NAME, v);} int ksmbd_set_server_string(char *v){	return ___server_conf_set(SERVER_CONF_SERVER_STRING, v);} int ksmbd_set_work_group(char *v){	return ___server_conf_set(SERVER_CONF_WORK_GROUP, v);} char *ksmbd_netbios_name(void){	return server_conf.conf[SERVER_CONF_NETBIOS_NAME];} char *ksmbd_server_string(void){	return server_conf.conf[SERVER_CONF_SERVER_STRING];} char *ksmbd_work_group(void){	return server_conf.conf[SERVER_CONF_WORK_GROUP];} /** * check_conn_state() - check state of server thread connection * @work:     smb work containing server thread information * * Return:	0 on valid connection, otherwise 1 to reconnect */static inline int check_conn_state(struct ksmbd_work *work){	struct smb_hdr *rsp_hdr; 	if (ksmbd_conn_exiting(work->conn) ||	    ksmbd_conn_need_reconnect(work->conn)) {		rsp_hdr = smb_get_msg(work->response_buf);		rsp_hdr->Status.CifsError = STATUS_CONNECTION_DISCONNECTED;		return 1;	}	return 0;} #define SERVER_HANDLER_CONTINUE		0#define SERVER_HANDLER_ABORT		1 static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn,			     u16 *cmd){	struct smb_version_cmds *cmds;	u16 command;	bool signed_req;	int ret; 	if (check_conn_state(work))		return SERVER_HANDLER_CONTINUE; 	if (ksmbd_verify_smb_message(work)) {		conn->ops->set_rsp_status(work, STATUS_INVALID_PARAMETER);		return SERVER_HANDLER_ABORT;	} 	command = conn->ops->get_cmd_val(work);	*cmd = command; andx_again:	if (command >= conn->max_cmds) {		conn->ops->set_rsp_status(work, STATUS_INVALID_PARAMETER);		return SERVER_HANDLER_ABORT;	} 	cmds = &conn->cmds[command];	if (!cmds->proc) {		ksmbd_debug(SMB, "*** not implemented yet cmd = %x\n", command);		conn->ops->set_rsp_status(work, STATUS_NOT_IMPLEMENTED);		return SERVER_HANDLER_ABORT;	} 	signed_req = conn->ops->is_sign_req && conn->ops->is_sign_req(work, command);	if (work->sess && work->sess->sign && !work->encrypted &&	    !signed_req) {		conn->ops->set_rsp_status(work, STATUS_ACCESS_DENIED);		return SERVER_HANDLER_ABORT;	} 	if (work->sess && signed_req) {		ret = conn->ops->check_sign_req(work);		if (!ret) {			conn->ops->set_rsp_status(work, STATUS_ACCESS_DENIED);			return SERVER_HANDLER_ABORT;		}	} 	ret = cmds->proc(work);	if (conn->ops->inc_reqs) {		struct smb2_hdr *rsp = ksmbd_resp_buf_curr(work); 		conn->ops->inc_reqs(command, rsp->Status);	} 	if (ret < 0)		ksmbd_debug(CONN, "Failed to process %u [%d]\n", command, ret);	/* AndX commands - chained request can return positive values */	else if (ret > 0) {		command = ret;		*cmd = command;		goto andx_again;	} 	if (work->send_no_response)		return SERVER_HANDLER_ABORT;	return SERVER_HANDLER_CONTINUE;} static void __handle_ksmbd_work(struct ksmbd_work *work,				struct ksmbd_conn *conn){	u16 command = 0;	int rc;	bool is_chained = false; 	if (conn->ops->is_transform_hdr &&	    conn->ops->is_transform_hdr(work->request_buf)) {		rc = conn->ops->decrypt_req(work);		if (rc < 0) {			ksmbd_conn_abort(conn);			return;		}		work->encrypted = true; 		/*		 * SMB3 applies compression before encryption.  The receive loop		 * handles a plain compression transform before allocating work, but		 * an encrypted request exposes that transform only after decryption.		 */		if (((struct smb2_hdr *)smb_get_msg(work->request_buf))->ProtocolId ==		    SMB2_COMPRESSION_TRANSFORM_ID) {			rc = ksmbd_decompress_work_request(work);			if (rc < 0) {				ksmbd_conn_abort(conn);				return;			}		} 		/* The decrypted payload must now be a complete SMB2 request. */		if (((struct smb2_hdr *)smb_get_msg(work->request_buf))->ProtocolId !=			SMB2_PROTO_NUMBER ||		    get_rfc1002_len(work->request_buf) < sizeof(struct smb2_pdu)) {			ksmbd_conn_abort(conn);			return;		}	} 	if (conn->ops->allocate_rsp_buf(work))		return; 	rc = conn->ops->init_rsp_hdr(work);	if (rc) {		/* either uid or tid is not correct */		conn->ops->set_rsp_status(work, STATUS_INVALID_HANDLE);		goto send;	} 	do {		if (conn->ops->check_user_session) {			rc = conn->ops->check_user_session(work);			if (rc < 0) {				if (rc == -EINVAL)					conn->ops->set_rsp_status(work,						STATUS_INVALID_PARAMETER);				else if (rc == -EKEYEXPIRED)					conn->ops->set_rsp_status(work,						STATUS_NETWORK_SESSION_EXPIRED);				else					conn->ops->set_rsp_status(work,						STATUS_USER_SESSION_DELETED);				if (conn->ops->is_sign_req(work, conn->ops->get_cmd_val(work))) {					struct smb2_hdr *rsp_hdr; 					rsp_hdr = ksmbd_resp_buf_curr(work);					if (rc == -EKEYEXPIRED && work->sess &&					    conn->ops->set_sign_rsp)						conn->ops->set_sign_rsp(work);					else						rsp_hdr->Flags |= SMB2_FLAGS_SIGNED;				}				goto send;			} else if (rc > 0) {				rc = conn->ops->get_ksmbd_tcon(work);				if (rc < 0) {					if (rc == -EINVAL)						conn->ops->set_rsp_status(work,							STATUS_INVALID_PARAMETER);					else						conn->ops->set_rsp_status(work,							STATUS_NETWORK_NAME_DELETED);					goto send;				} 				if (work->tcon &&				    test_share_config_flag(work->tcon->share_conf,							   KSMBD_SHARE_FLAG_ENCRYPT_DATA) &&				    !work->encrypted) {					conn->ops->set_rsp_status(work,								  STATUS_ACCESS_DENIED);					goto send;				}			}		} 		rc = __process_request(work, conn, &command);		if (rc == SERVER_HANDLER_ABORT) {			smb2_complete_request_open(work);			break;		} 		/*		 * Call smb2_set_rsp_credits() function to set number of credits		 * granted in hdr of smb2 response.		 */		if (conn->ops->set_rsp_credits) {			spin_lock(&conn->credits_lock);			rc = conn->ops->set_rsp_credits(work);			spin_unlock(&conn->credits_lock);			if (rc < 0) {				conn->ops->set_rsp_status(work,					STATUS_INVALID_PARAMETER);				smb2_complete_request_open(work);				goto send;			}		} 		smb2_complete_request_open(work); 		is_chained = is_chained_smb2_message(work); 		if (work->sess &&		    (work->sess->sign || smb3_11_final_sess_setup_resp(work) ||		     conn->ops->is_sign_req(work, command))) {			if (command == SMB2_SESSION_SETUP_HE &&			    work->sess->dialect >= SMB30_PROT_ID &&			    conn->dialect < SMB30_PROT_ID)				smb3_set_sign_rsp(work);			else				conn->ops->set_sign_rsp(work);		}	} while (is_chained == true); send:	smb2_complete_request_open(work);	/*	 * Release any credit charge still outstanding for this request.  On	 * the normal path smb2_set_rsp_credits() already returned it, but the	 * abort, error and send-no-response paths skip that call, so the	 * charge would otherwise leak and eventually exhaust the connection's	 * outstanding credit window.	 */	if (work->credit_charge) {		spin_lock(&conn->credits_lock);		conn->outstanding_credits -= work->credit_charge;		work->credit_charge = 0;		spin_unlock(&conn->credits_lock);	} 	if (work->tcon)		ksmbd_tree_connect_put(work->tcon);	smb3_preauth_hash_rsp(work);	/*	 * Preauthentication hashes cover the original SMB2 response. Apply the	 * transport compression wrapper only after updating the hash.	 */	if (work->compress_response) {		rc = ksmbd_compress_response(work);		if (rc < 0)			ksmbd_debug(CONN, "Failed to compress response: %d\n", rc);	}	if (work->sess && work->sess->enc && work->encrypted &&	    conn->ops->encrypt_resp) {		rc = conn->ops->encrypt_resp(work);		if (rc < 0)			conn->ops->set_rsp_status(work, STATUS_DATA_ERROR);	}	if (work->sess)		ksmbd_user_session_put(work->sess); 	ksmbd_conn_write(work);} /** * handle_ksmbd_work() - process pending smb work requests * @wk:	smb work containing request command buffer * * called by kworker threads to processing remaining smb work requests */static void handle_ksmbd_work(struct work_struct *wk){	struct ksmbd_work *work = container_of(wk, struct ksmbd_work, work);	struct ksmbd_conn *conn = work->conn; 	atomic64_inc(&conn->stats.request_served); 	__handle_ksmbd_work(work, conn); 	ksmbd_conn_try_dequeue_request(work);	ksmbd_free_work_struct(work);	ksmbd_conn_r_count_dec(conn);} /** * queue_ksmbd_work() - queue a smb request to worker thread queue *		for processing smb command and sending response * @conn:	connection instance * * read remaining data from socket create and submit work. */static int queue_ksmbd_work(struct ksmbd_conn *conn){	struct ksmbd_work *work;	int err; 	err = ksmbd_init_smb_server(conn);	if (err)		return 0; 	work = ksmbd_alloc_work_struct();	if (!work) {		pr_err("allocation for work failed\n");		return -ENOMEM;	} 	work->conn = conn;	work->request_buf = conn->request_buf;	conn->request_buf = NULL; 	ksmbd_conn_enqueue_request(work);	ksmbd_conn_r_count_inc(conn);	/* update activity on connection */	conn->last_active = jiffies;	INIT_WORK(&work->work, handle_ksmbd_work);	ksmbd_queue_work(work);	return 0;} static int ksmbd_server_process_request(struct ksmbd_conn *conn){	return queue_ksmbd_work(conn);} static int ksmbd_server_terminate_conn(struct ksmbd_conn *conn){	ksmbd_sessions_deregister(conn);	destroy_lease_table(conn);	return 0;} static void ksmbd_server_tcp_callbacks_init(void){	struct ksmbd_conn_ops ops; 	ops.process_fn = ksmbd_server_process_request;	ops.terminate_fn = ksmbd_server_terminate_conn; 	ksmbd_conn_init_server_callbacks(&ops);} static void server_conf_free(void){	int i; 	for (i = 0; i < ARRAY_SIZE(server_conf.conf); i++) {		kfree(server_conf.conf[i]);		server_conf.conf[i] = NULL;	}} static int server_conf_init(void){	WRITE_ONCE(server_conf.state, SERVER_STATE_STARTING_UP);	server_conf.enforced_signing = 0;	server_conf.min_protocol = ksmbd_min_protocol();	server_conf.max_protocol = ksmbd_max_protocol();	server_conf.auth_mechs = KSMBD_AUTH_NTLMSSP;#ifdef CONFIG_SMB_SERVER_KERBEROS5	server_conf.auth_mechs |= KSMBD_AUTH_KRB5 |				KSMBD_AUTH_MSKRB5;#endif	server_conf.max_inflight_req = SMB2_MAX_CREDITS;	return 0;} static void server_ctrl_handle_init(struct server_ctrl_struct *ctrl){	int ret; 	ksmbd_proc_reset();	ret = ksmbd_conn_transport_init();	if (ret) {		server_queue_ctrl_reset_work();		return;	} 	pr_info("running\n");	WRITE_ONCE(server_conf.state, SERVER_STATE_RUNNING);} static void server_ctrl_handle_reset(struct server_ctrl_struct *ctrl){	ksmbd_ipc_soft_reset();	ksmbd_conn_transport_destroy();	ksmbd_stop_durable_scavenger();	server_conf_free();	server_conf_init();	WRITE_ONCE(server_conf.state, SERVER_STATE_STARTING_UP);} static void server_ctrl_handle_work(struct work_struct *work){	struct server_ctrl_struct *ctrl; 	ctrl = container_of(work, struct server_ctrl_struct, ctrl_work); 	mutex_lock(&ctrl_lock);	switch (ctrl->type) {	case SERVER_CTRL_TYPE_INIT:		server_ctrl_handle_init(ctrl);		break;	case SERVER_CTRL_TYPE_RESET:		server_ctrl_handle_reset(ctrl);		break;	default:		pr_err("Unknown server work type: %d\n", ctrl->type);	}	mutex_unlock(&ctrl_lock);	kfree(ctrl);

Lines 1–500 of 760.