summaryrefslogtreecommitdiffstats
path: root/source3/smbd/uid.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/smbd/uid.c')
-rw-r--r--source3/smbd/uid.c752
1 files changed, 752 insertions, 0 deletions
diff --git a/source3/smbd/uid.c b/source3/smbd/uid.c
new file mode 100644
index 0000000..52918c4
--- /dev/null
+++ b/source3/smbd/uid.c
@@ -0,0 +1,752 @@
+/*
+ Unix SMB/CIFS implementation.
+ uid/user handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "libcli/security/security.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "../auth/auth_util.h"
+#include "source3/lib/substitute.h"
+
+/* what user is current? */
+extern struct current_user current_user;
+
+/****************************************************************************
+ Become the guest user without changing the security context stack.
+****************************************************************************/
+
+bool change_to_guest(void)
+{
+ struct passwd *pass;
+
+ pass = Get_Pwnam_alloc(talloc_tos(), lp_guest_account());
+ if (!pass) {
+ return false;
+ }
+
+#ifdef AIX
+ /* MWW: From AIX FAQ patch to WU-ftpd: call initgroups before
+ setting IDs */
+ initgroups(pass->pw_name, pass->pw_gid);
+#endif
+
+ set_sec_ctx(pass->pw_uid, pass->pw_gid, 0, NULL, NULL);
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+
+ TALLOC_FREE(pass);
+
+ return true;
+}
+
+/****************************************************************************
+ talloc free the conn->session_info if not used in the vuid cache.
+****************************************************************************/
+
+static void free_conn_session_info_if_unused(connection_struct *conn)
+{
+ unsigned int i;
+
+ for (i = 0; i < VUID_CACHE_SIZE; i++) {
+ struct vuid_cache_entry *ent;
+ ent = &conn->vuid_cache->array[i];
+ if (ent->vuid != UID_FIELD_INVALID &&
+ conn->session_info == ent->session_info) {
+ return;
+ }
+ }
+ /* Not used, safe to free. */
+ TALLOC_FREE(conn->session_info);
+}
+
+/****************************************************************************
+ Setup the share access mask for a connection.
+****************************************************************************/
+
+static uint32_t create_share_access_mask(int snum,
+ bool readonly_share,
+ const struct security_token *token)
+{
+ uint32_t share_access = 0;
+
+ share_access_check(token,
+ lp_const_servicename(snum),
+ MAXIMUM_ALLOWED_ACCESS,
+ &share_access);
+
+ if (readonly_share) {
+ share_access &=
+ ~(SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA |
+ SEC_FILE_WRITE_EA | SEC_FILE_WRITE_ATTRIBUTE |
+ SEC_DIR_DELETE_CHILD );
+ }
+
+ if (security_token_has_privilege(token, SEC_PRIV_SECURITY)) {
+ share_access |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+ if (security_token_has_privilege(token, SEC_PRIV_RESTORE)) {
+ share_access |= SEC_RIGHTS_PRIV_RESTORE;
+ }
+ if (security_token_has_privilege(token, SEC_PRIV_BACKUP)) {
+ share_access |= SEC_RIGHTS_PRIV_BACKUP;
+ }
+ if (security_token_has_privilege(token, SEC_PRIV_TAKE_OWNERSHIP)) {
+ share_access |= SEC_STD_WRITE_OWNER;
+ }
+
+ return share_access;
+}
+
+/*******************************************************************
+ Calculate access mask and if this user can access this share.
+********************************************************************/
+
+NTSTATUS check_user_share_access(connection_struct *conn,
+ const struct auth_session_info *session_info,
+ uint32_t *p_share_access,
+ bool *p_readonly_share)
+{
+ int snum = SNUM(conn);
+ uint32_t share_access = 0;
+ bool readonly_share = false;
+
+ if (!user_ok_token(session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token, snum)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ readonly_share = is_share_read_only_for_token(
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token,
+ conn);
+
+ share_access = create_share_access_mask(snum,
+ readonly_share,
+ session_info->security_token);
+
+ if ((share_access & (FILE_READ_DATA|FILE_WRITE_DATA)) == 0) {
+ /* No access, read or write. */
+ DBG_NOTICE("user %s connection to %s denied due to share "
+ "security descriptor.\n",
+ session_info->unix_info->unix_name,
+ lp_const_servicename(snum));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!readonly_share &&
+ !(share_access & FILE_WRITE_DATA)) {
+ /* smb.conf allows r/w, but the security descriptor denies
+ * write. Fall back to looking at readonly. */
+ readonly_share = true;
+ DBG_INFO("falling back to read-only access-evaluation due to "
+ "security descriptor\n");
+ }
+
+ *p_share_access = share_access;
+ *p_readonly_share = readonly_share;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Check if a username is OK.
+
+ This sets up conn->session_info with a copy related to this vuser that
+ later code can then mess with.
+********************************************************************/
+
+static bool check_user_ok(connection_struct *conn,
+ uint64_t vuid,
+ const struct auth_session_info *session_info,
+ int snum)
+{
+ unsigned int i;
+ bool readonly_share = false;
+ bool admin_user = false;
+ struct vuid_cache_entry *ent = NULL;
+ uint32_t share_access = 0;
+ NTSTATUS status;
+
+ for (i=0; i<VUID_CACHE_SIZE; i++) {
+ ent = &conn->vuid_cache->array[i];
+ if (ent->vuid == vuid) {
+ if (vuid == UID_FIELD_INVALID) {
+ /*
+ * Slow path, we don't care
+ * about the array traversal.
+ */
+ continue;
+ }
+ free_conn_session_info_if_unused(conn);
+ conn->session_info = ent->session_info;
+ conn->read_only = ent->read_only;
+ conn->share_access = ent->share_access;
+ conn->vuid = ent->vuid;
+ return(True);
+ }
+ }
+
+ status = check_user_share_access(conn,
+ session_info,
+ &share_access,
+ &readonly_share);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ admin_user = token_contains_name_in_list(
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ NULL, session_info->security_token, lp_admin_users(snum));
+
+ ent = &conn->vuid_cache->array[conn->vuid_cache->next_entry];
+
+ conn->vuid_cache->next_entry =
+ (conn->vuid_cache->next_entry + 1) % VUID_CACHE_SIZE;
+
+ TALLOC_FREE(ent->session_info);
+
+ /*
+ * If force_user was set, all session_info's are based on the same
+ * username-based faked one.
+ */
+
+ ent->session_info = copy_session_info(
+ conn, conn->force_user ? conn->session_info : session_info);
+
+ if (ent->session_info == NULL) {
+ ent->vuid = UID_FIELD_INVALID;
+ return false;
+ }
+
+ if (admin_user) {
+ DEBUG(2,("check_user_ok: user %s is an admin user. "
+ "Setting uid as %d\n",
+ ent->session_info->unix_info->unix_name,
+ sec_initial_uid() ));
+ ent->session_info->unix_token->uid = sec_initial_uid();
+ }
+
+ /*
+ * It's actually OK to call check_user_ok() with
+ * vuid == UID_FIELD_INVALID as called from become_user_by_session().
+ * All this will do is throw away one entry in the cache.
+ */
+
+ ent->vuid = vuid;
+ ent->read_only = readonly_share;
+ ent->share_access = share_access;
+ free_conn_session_info_if_unused(conn);
+ conn->session_info = ent->session_info;
+ conn->vuid = ent->vuid;
+ if (vuid == UID_FIELD_INVALID) {
+ /*
+ * Not strictly needed, just make it really
+ * clear this entry is actually an unused one.
+ */
+ ent->read_only = false;
+ ent->share_access = 0;
+ ent->session_info = NULL;
+ }
+
+ conn->read_only = readonly_share;
+ conn->share_access = share_access;
+
+ return(True);
+}
+
+static void print_impersonation_info(connection_struct *conn)
+{
+ struct smb_filename *cwdfname = NULL;
+
+ if (!CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ return;
+ }
+
+ cwdfname = vfs_GetWd(talloc_tos(), conn);
+ if (cwdfname == NULL) {
+ return;
+ }
+
+ DBG_INFO("Impersonated user: uid=(%d,%d), gid=(%d,%d), cwd=[%s]\n",
+ (int)getuid(),
+ (int)geteuid(),
+ (int)getgid(),
+ (int)getegid(),
+ cwdfname->base_name);
+ TALLOC_FREE(cwdfname);
+}
+
+/****************************************************************************
+ Become the user of a connection number without changing the security context
+ stack, but modify the current_user entries.
+****************************************************************************/
+
+static bool change_to_user_impersonate(connection_struct *conn,
+ const struct auth_session_info *session_info,
+ uint64_t vuid)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ gid_t gid;
+ uid_t uid;
+ const char *force_group_name;
+ char group_c;
+ int num_groups = 0;
+ gid_t *group_list = NULL;
+ bool ok;
+
+ if ((current_user.conn == conn) &&
+ (current_user.vuid == vuid) &&
+ (current_user.ut.uid == session_info->unix_token->uid))
+ {
+ DBG_INFO("Skipping user change - already user\n");
+ return true;
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ snum = SNUM(conn);
+
+ ok = check_user_ok(conn, vuid, session_info, snum);
+ if (!ok) {
+ DBG_WARNING("SMB user %s (unix user %s) "
+ "not permitted access to share %s.\n",
+ session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ lp_const_servicename(snum));
+ return false;
+ }
+
+ uid = conn->session_info->unix_token->uid;
+ gid = conn->session_info->unix_token->gid;
+ num_groups = conn->session_info->unix_token->ngroups;
+ group_list = conn->session_info->unix_token->groups;
+
+ /*
+ * See if we should force group for this service. If so this overrides
+ * any group set in the force user code.
+ */
+ force_group_name = lp_force_group(talloc_tos(), lp_sub, snum);
+ group_c = *force_group_name;
+
+ if ((group_c != '\0') && (conn->force_group_gid == (gid_t)-1)) {
+ /*
+ * This can happen if "force group" is added to a
+ * share definition whilst an existing connection
+ * to that share exists. In that case, don't change
+ * the existing credentials for force group, only
+ * do so for new connections.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13690
+ */
+ DBG_INFO("Not forcing group %s on existing connection to "
+ "share %s for SMB user %s (unix user %s)\n",
+ force_group_name,
+ lp_const_servicename(snum),
+ session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name);
+ }
+
+ if((group_c != '\0') && (conn->force_group_gid != (gid_t)-1)) {
+ /*
+ * Only force group for connections where
+ * conn->force_group_gid has already been set
+ * to the correct value (i.e. the connection
+ * happened after the 'force group' definition
+ * was added to the share definition. Connections
+ * that were made before force group was added
+ * should stay with their existing credentials.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13690
+ */
+
+ if (group_c == '+') {
+ int i;
+
+ /*
+ * Only force group if the user is a member of the
+ * service group. Check the group memberships for this
+ * user (we already have this) to see if we should force
+ * the group.
+ */
+ for (i = 0; i < num_groups; i++) {
+ if (group_list[i] == conn->force_group_gid) {
+ conn->session_info->unix_token->gid =
+ conn->force_group_gid;
+ gid = conn->force_group_gid;
+ gid_to_sid(&conn->session_info->security_token
+ ->sids[1], gid);
+ break;
+ }
+ }
+ } else {
+ conn->session_info->unix_token->gid = conn->force_group_gid;
+ gid = conn->force_group_gid;
+ gid_to_sid(&conn->session_info->security_token->sids[1],
+ gid);
+ }
+ }
+
+ set_sec_ctx(uid,
+ gid,
+ num_groups,
+ group_list,
+ conn->session_info->security_token);
+
+ current_user.conn = conn;
+ current_user.vuid = vuid;
+ return true;
+}
+
+/**
+ * Impersonate user and change directory to service
+ *
+ * change_to_user_and_service() is used to impersonate the user associated with
+ * the given vuid and to change the working directory of the process to the
+ * service base directory.
+ **/
+bool change_to_user_and_service(connection_struct *conn, uint64_t vuid)
+{
+ int snum = SNUM(conn);
+ struct auth_session_info *si = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ if (conn == NULL) {
+ DBG_WARNING("Connection not open\n");
+ return false;
+ }
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &si);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Invalid vuid %llu used on share %s.\n",
+ (unsigned long long)vuid,
+ lp_const_servicename(snum));
+ return false;
+ }
+
+ ok = change_to_user_impersonate(conn, si, vuid);
+ if (!ok) {
+ return false;
+ }
+
+ if (conn->tcon_done) {
+ ok = chdir_current_service(conn);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ print_impersonation_info(conn);
+ return true;
+}
+
+/**
+ * Impersonate user and change directory to service
+ *
+ * change_to_user_and_service_by_fsp() is used to impersonate the user
+ * associated with the given vuid and to change the working directory of the
+ * process to the service base directory.
+ **/
+bool change_to_user_and_service_by_fsp(struct files_struct *fsp)
+{
+ return change_to_user_and_service(fsp->conn, fsp->vuid);
+}
+
+/****************************************************************************
+ Go back to being root without changing the security context stack,
+ but modify the current_user entries.
+****************************************************************************/
+
+bool smbd_change_to_root_user(void)
+{
+ set_root_sec_ctx();
+
+ DEBUG(5,("change_to_root_user: now uid=(%d,%d) gid=(%d,%d)\n",
+ (int)getuid(),(int)geteuid(),(int)getgid(),(int)getegid()));
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+
+ return(True);
+}
+
+/****************************************************************************
+ Become the user of an authenticated connected named pipe.
+ When this is called we are currently running as the connection
+ user. Doesn't modify current_user.
+****************************************************************************/
+
+bool smbd_become_authenticated_pipe_user(struct auth_session_info *session_info)
+{
+ if (!push_sec_ctx())
+ return False;
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ set_sec_ctx(session_info->unix_token->uid, session_info->unix_token->gid,
+ session_info->unix_token->ngroups, session_info->unix_token->groups,
+ session_info->security_token);
+
+ DEBUG(5, ("Impersonated user: uid=(%d,%d), gid=(%d,%d)\n",
+ (int)getuid(),
+ (int)geteuid(),
+ (int)getgid(),
+ (int)getegid()));
+
+ return True;
+}
+
+/****************************************************************************
+ Unbecome the user of an authenticated connected named pipe.
+ When this is called we are running as the authenticated pipe
+ user and need to go back to being the connection user. Doesn't modify
+ current_user.
+****************************************************************************/
+
+bool smbd_unbecome_authenticated_pipe_user(void)
+{
+ return pop_sec_ctx();
+}
+
+/****************************************************************************
+ Utility functions used by become_xxx/unbecome_xxx.
+****************************************************************************/
+
+static void push_conn_ctx(void)
+{
+ struct conn_ctx *ctx_p;
+ extern userdom_struct current_user_info;
+
+ /* Check we don't overflow our stack */
+
+ if (conn_ctx_stack_ndx == MAX_SEC_CTX_DEPTH) {
+ DEBUG(0, ("Connection context stack overflow!\n"));
+ smb_panic("Connection context stack overflow!\n");
+ }
+
+ /* Store previous user context */
+ ctx_p = &conn_ctx_stack[conn_ctx_stack_ndx];
+
+ ctx_p->conn = current_user.conn;
+ ctx_p->vuid = current_user.vuid;
+ ctx_p->user_info = current_user_info;
+
+ DEBUG(4, ("push_conn_ctx(%llu) : conn_ctx_stack_ndx = %d\n",
+ (unsigned long long)ctx_p->vuid, conn_ctx_stack_ndx));
+
+ conn_ctx_stack_ndx++;
+}
+
+static void pop_conn_ctx(void)
+{
+ struct conn_ctx *ctx_p;
+
+ /* Check for stack underflow. */
+
+ if (conn_ctx_stack_ndx == 0) {
+ DEBUG(0, ("Connection context stack underflow!\n"));
+ smb_panic("Connection context stack underflow!\n");
+ }
+
+ conn_ctx_stack_ndx--;
+ ctx_p = &conn_ctx_stack[conn_ctx_stack_ndx];
+
+ set_current_user_info(ctx_p->user_info.smb_name,
+ ctx_p->user_info.unix_name,
+ ctx_p->user_info.domain);
+
+ current_user.conn = ctx_p->conn;
+ current_user.vuid = ctx_p->vuid;
+
+ *ctx_p = (struct conn_ctx) {
+ .vuid = UID_FIELD_INVALID,
+ };
+}
+
+/****************************************************************************
+ Temporarily become a root user. Must match with unbecome_root(). Saves and
+ restores the connection context.
+****************************************************************************/
+
+void smbd_become_root(void)
+{
+ /*
+ * no good way to handle push_sec_ctx() failing without changing
+ * the prototype of become_root()
+ */
+ if (!push_sec_ctx()) {
+ smb_panic("become_root: push_sec_ctx failed");
+ }
+ push_conn_ctx();
+ set_root_sec_ctx();
+}
+
+/* Unbecome the root user */
+
+void smbd_unbecome_root(void)
+{
+ pop_sec_ctx();
+ pop_conn_ctx();
+}
+
+/****************************************************************************
+ Push the current security context then force a change via change_to_user().
+ Saves and restores the connection context.
+****************************************************************************/
+
+bool become_user_without_service(connection_struct *conn, uint64_t vuid)
+{
+ struct auth_session_info *session_info = NULL;
+ int snum = SNUM(conn);
+ NTSTATUS status;
+ bool ok;
+
+ if (conn == NULL) {
+ DBG_WARNING("Connection not open\n");
+ return false;
+ }
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Invalid vuid sent */
+ DBG_WARNING("Invalid vuid %llu used on share %s.\n",
+ (unsigned long long)vuid,
+ lp_const_servicename(snum));
+ return false;
+ }
+
+ ok = push_sec_ctx();
+ if (!ok) {
+ return false;
+ }
+
+ push_conn_ctx();
+
+ ok = change_to_user_impersonate(conn, session_info, vuid);
+ if (!ok) {
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return false;
+ }
+
+ return true;
+}
+
+bool become_user_without_service_by_fsp(struct files_struct *fsp)
+{
+ return become_user_without_service(fsp->conn, fsp->vuid);
+}
+
+bool become_user_without_service_by_session(connection_struct *conn,
+ const struct auth_session_info *session_info)
+{
+ bool ok;
+
+ SMB_ASSERT(conn != NULL);
+ SMB_ASSERT(session_info != NULL);
+
+ ok = push_sec_ctx();
+ if (!ok) {
+ return false;
+ }
+
+ push_conn_ctx();
+
+ ok = change_to_user_impersonate(conn, session_info, UID_FIELD_INVALID);
+ if (!ok) {
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return false;
+ }
+
+ return true;
+}
+
+bool unbecome_user_without_service(void)
+{
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return True;
+}
+
+/****************************************************************************
+ Return the current user we are running effectively as on this connection.
+ I'd like to make this return conn->session_info->unix_token->uid, but become_root()
+ doesn't alter this value.
+****************************************************************************/
+
+uid_t get_current_uid(connection_struct *conn)
+{
+ return current_user.ut.uid;
+}
+
+/****************************************************************************
+ Return the current group we are running effectively as on this connection.
+ I'd like to make this return conn->session_info->unix_token->gid, but become_root()
+ doesn't alter this value.
+****************************************************************************/
+
+gid_t get_current_gid(connection_struct *conn)
+{
+ return current_user.ut.gid;
+}
+
+/****************************************************************************
+ Return the UNIX token we are running effectively as on this connection.
+ I'd like to make this return &conn->session_info->unix_token-> but become_root()
+ doesn't alter this value.
+****************************************************************************/
+
+const struct security_unix_token *get_current_utok(connection_struct *conn)
+{
+ return &current_user.ut;
+}
+
+/****************************************************************************
+ Return the Windows token we are running effectively as on this connection.
+ If this is currently a NULL token as we're inside become_root() - a temporary
+ UNIX security override, then we search up the stack for the previous active
+ token.
+****************************************************************************/
+
+const struct security_token *get_current_nttok(connection_struct *conn)
+{
+ if (current_user.nt_user_token) {
+ return current_user.nt_user_token;
+ }
+ return sec_ctx_active_token();
+}