Example #1
0
static int reply_spnego_kerberos(connection_struct *conn, 
				 char *inbuf, char *outbuf,
				 int length, int bufsize,
				 DATA_BLOB *secblob,
				 BOOL *p_invalidate_vuid)
{
	TALLOC_CTX *mem_ctx;
	DATA_BLOB ticket;
	char *client, *p, *domain;
	fstring netbios_domain_name;
	struct passwd *pw;
	fstring user;
	int sess_vuid;
	NTSTATUS ret;
	PAC_DATA *pac_data;
	DATA_BLOB ap_rep, ap_rep_wrapped, response;
	auth_serversupplied_info *server_info = NULL;
	DATA_BLOB session_key = data_blob(NULL, 0);
	uint8 tok_id[2];
	DATA_BLOB nullblob = data_blob(NULL, 0);
	fstring real_username;
	BOOL map_domainuser_to_guest = False;
	BOOL username_was_mapped;
	PAC_LOGON_INFO *logon_info = NULL;

	ZERO_STRUCT(ticket);
	ZERO_STRUCT(pac_data);
	ZERO_STRUCT(ap_rep);
	ZERO_STRUCT(ap_rep_wrapped);
	ZERO_STRUCT(response);

	/* Normally we will always invalidate the intermediate vuid. */
	*p_invalidate_vuid = True;

	mem_ctx = talloc_init("reply_spnego_kerberos");
	if (mem_ctx == NULL) {
		return ERROR_NT(nt_status_squash(NT_STATUS_NO_MEMORY));
	}

	if (!spnego_parse_krb5_wrap(*secblob, &ticket, tok_id)) {
		talloc_destroy(mem_ctx);
		return ERROR_NT(nt_status_squash(NT_STATUS_LOGON_FAILURE));
	}

	ret = ads_verify_ticket(mem_ctx, lp_realm(), 0, &ticket, &client, &pac_data, &ap_rep, &session_key);

	data_blob_free(&ticket);

	if (!NT_STATUS_IS_OK(ret)) {
#if 0
		/* Experiment that failed. See "only happens with a KDC" comment below. */

		if (NT_STATUS_EQUAL(ret, NT_STATUS_TIME_DIFFERENCE_AT_DC)) {

			/*
			 * Windows in this case returns NT_STATUS_MORE_PROCESSING_REQUIRED
			 * with a negTokenTarg blob containing an krb5_error struct ASN1 encoded
			 * containing KRB5KRB_AP_ERR_SKEW. The client then fixes its
			 * clock and continues rather than giving an error. JRA.
			 * -- Looks like this only happens with a KDC. JRA.
			 */

			BOOL ok = make_krb5_skew_error(&ap_rep);
			if (!ok) {
				talloc_destroy(mem_ctx);
				return ERROR_NT(nt_status_squash(NT_STATUS_LOGON_FAILURE));
			}
			ap_rep_wrapped = spnego_gen_krb5_wrap(ap_rep, TOK_ID_KRB_ERROR);
			response = spnego_gen_auth_response(&ap_rep_wrapped, ret, OID_KERBEROS5_OLD);
			reply_sesssetup_blob(conn, outbuf, response, NT_STATUS_MORE_PROCESSING_REQUIRED);

			/*
			 * In this one case we don't invalidate the intermediate vuid.
			 * as we're expecting the client to re-use it for the next
			 * sessionsetupX packet. JRA.
			 */

			*p_invalidate_vuid = False;

			data_blob_free(&ap_rep);
			data_blob_free(&ap_rep_wrapped);
			data_blob_free(&response);
			talloc_destroy(mem_ctx);
			return -1; /* already replied */
		}
#else
		if (!NT_STATUS_EQUAL(ret, NT_STATUS_TIME_DIFFERENCE_AT_DC)) {
			ret = NT_STATUS_LOGON_FAILURE;
		}
#endif
		DEBUG(1,("Failed to verify incoming ticket with error %s!\n", nt_errstr(ret)));	
		talloc_destroy(mem_ctx);
		return ERROR_NT(nt_status_squash(ret));
	}

	DEBUG(3,("Ticket name is [%s]\n", client));

	p = strchr_m(client, '@');
	if (!p) {
		DEBUG(3,("Doesn't look like a valid principal\n"));
		data_blob_free(&ap_rep);
		data_blob_free(&session_key);
		SAFE_FREE(client);
		talloc_destroy(mem_ctx);
		return ERROR_NT(nt_status_squash(NT_STATUS_LOGON_FAILURE));
	}

	*p = 0;

	/* save the PAC data if we have it */

	if (pac_data) {
		logon_info = get_logon_info_from_pac(pac_data);
		if (logon_info) {
			netsamlogon_cache_store( client, &logon_info->info3 );
		}
	}

	if (!strequal(p+1, lp_realm())) {
		DEBUG(3,("Ticket for foreign realm %s@%s\n", client, p+1));
		if (!lp_allow_trusted_domains()) {
			data_blob_free(&ap_rep);
			data_blob_free(&session_key);
			SAFE_FREE(client);
			talloc_destroy(mem_ctx);
			return ERROR_NT(nt_status_squash(NT_STATUS_LOGON_FAILURE));
		}
	}

	/* this gives a fully qualified user name (ie. with full realm).
	   that leads to very long usernames, but what else can we do? */

	domain = p+1;

	if (logon_info && logon_info->info3.hdr_logon_dom.uni_str_len) {

		unistr2_to_ascii(netbios_domain_name, &logon_info->info3.uni_logon_dom, -1);
		domain = netbios_domain_name;
		DEBUG(10, ("Mapped to [%s] (using PAC)\n", domain));

	} else {

		/* If we have winbind running, we can (and must) shorten the
		   username by using the short netbios name. Otherwise we will
		   have inconsistent user names. With Kerberos, we get the
		   fully qualified realm, with ntlmssp we get the short
		   name. And even w2k3 does use ntlmssp if you for example
		   connect to an ip address. */

		struct winbindd_request wb_request;
		struct winbindd_response wb_response;
		NSS_STATUS wb_result;

		ZERO_STRUCT(wb_request);
		ZERO_STRUCT(wb_response);

		DEBUG(10, ("Mapping [%s] to short name\n", domain));

		fstrcpy(wb_request.domain_name, domain);

		wb_result = winbindd_request_response(WINBINDD_DOMAIN_INFO,
					     &wb_request, &wb_response);

		if (wb_result == NSS_STATUS_SUCCESS) {

			fstrcpy(netbios_domain_name,
				wb_response.data.domain_info.name);
			domain = netbios_domain_name;

			DEBUG(10, ("Mapped to [%s] (using Winbind)\n", domain));
		} else {
			DEBUG(3, ("Could not find short name -- winbind "
				  "not running?\n"));
		}
	}

	fstr_sprintf(user, "%s%c%s", domain, *lp_winbind_separator(), client);
	
	/* lookup the passwd struct, create a new user if necessary */

	username_was_mapped = map_username( user );

	pw = smb_getpwnam( mem_ctx, user, real_username, True );

	if (pw) {
		/* if a real user check pam account restrictions */
		/* only really perfomed if "obey pam restriction" is true */
		/* do this before an eventual mappign to guest occurs */
		ret = smb_pam_accountcheck(pw->pw_name);
		if (  !NT_STATUS_IS_OK(ret)) {
			DEBUG(1, ("PAM account restriction prevents user login\n"));
			data_blob_free(&ap_rep);
			data_blob_free(&session_key);
			TALLOC_FREE(mem_ctx);
			return ERROR_NT(nt_status_squash(ret));
		}
	}

	if (!pw) {

		/* this was originally the behavior of Samba 2.2, if a user
		   did not have a local uid but has been authenticated, then 
		   map them to a guest account */

		if (lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_UID){ 
			map_domainuser_to_guest = True;
			fstrcpy(user,lp_guestaccount());
			pw = smb_getpwnam( mem_ctx, user, real_username, True );
		} 

		/* extra sanity check that the guest account is valid */

		if ( !pw ) {
			DEBUG(1,("Username %s is invalid on this system\n", user));
			SAFE_FREE(client);
			data_blob_free(&ap_rep);
			data_blob_free(&session_key);
			TALLOC_FREE(mem_ctx);
			return ERROR_NT(nt_status_squash(NT_STATUS_LOGON_FAILURE));
		}
	}

	/* setup the string used by %U */
	
	sub_set_smb_name( real_username );
	reload_services(True);

	if ( map_domainuser_to_guest ) {
		make_server_info_guest(&server_info);
	} else if (logon_info) {
		/* pass the unmapped username here since map_username() 
		   will be called again from inside make_server_info_info3() */
		
		ret = make_server_info_info3(mem_ctx, client, domain, 
					     &server_info, &logon_info->info3);
		if ( !NT_STATUS_IS_OK(ret) ) {
			DEBUG(1,("make_server_info_info3 failed: %s!\n",
				 nt_errstr(ret)));
			SAFE_FREE(client);
			data_blob_free(&ap_rep);
			data_blob_free(&session_key);
			TALLOC_FREE(mem_ctx);
			return ERROR_NT(nt_status_squash(ret));
		}

	} else {
		ret = make_server_info_pw(&server_info, real_username, pw);

		if ( !NT_STATUS_IS_OK(ret) ) {
			DEBUG(1,("make_server_info_pw failed: %s!\n",
				 nt_errstr(ret)));
			SAFE_FREE(client);
			data_blob_free(&ap_rep);
			data_blob_free(&session_key);
			TALLOC_FREE(mem_ctx);
			return ERROR_NT(nt_status_squash(ret));
		}

	        /* make_server_info_pw does not set the domain. Without this
		 * we end up with the local netbios name in substitutions for
		 * %D. */

		if (server_info->sam_account != NULL) {
			pdb_set_domain(server_info->sam_account, domain, PDB_SET);
		}
	}

	server_info->was_mapped |= username_was_mapped;
	
	/* we need to build the token for the user. make_server_info_guest()
	   already does this */
	
	if ( !server_info->ptok ) {
		ret = create_local_token( server_info );
		if ( !NT_STATUS_IS_OK(ret) ) {
			SAFE_FREE(client);
			data_blob_free(&ap_rep);
			data_blob_free(&session_key);
			TALLOC_FREE( mem_ctx );
			TALLOC_FREE( server_info );
			return ERROR_NT(nt_status_squash(ret));
		}
	}

	/* register_vuid keeps the server info */
	/* register_vuid takes ownership of session_key, no need to free after this.
 	   A better interface would copy it.... */
	sess_vuid = register_vuid(server_info, session_key, nullblob, client);

	SAFE_FREE(client);

	if (sess_vuid == UID_FIELD_INVALID ) {
		ret = NT_STATUS_LOGON_FAILURE;
	} else {
		/* current_user_info is changed on new vuid */
		reload_services( True );

		set_message(outbuf,4,0,True);
		SSVAL(outbuf, smb_vwv3, 0);
			
		if (server_info->guest) {
			SSVAL(outbuf,smb_vwv2,1);
		}
		
		SSVAL(outbuf, smb_uid, sess_vuid);

		sessionsetup_start_signing_engine(server_info, inbuf);
	}

        /* wrap that up in a nice GSS-API wrapping */
	if (NT_STATUS_IS_OK(ret)) {
		ap_rep_wrapped = spnego_gen_krb5_wrap(ap_rep, TOK_ID_KRB_AP_REP);
	} else {
		ap_rep_wrapped = data_blob(NULL, 0);
	}
	response = spnego_gen_auth_response(&ap_rep_wrapped, ret, OID_KERBEROS5_OLD);
	reply_sesssetup_blob(conn, outbuf, response, ret);

	data_blob_free(&ap_rep);
	data_blob_free(&ap_rep_wrapped);
	data_blob_free(&response);
	TALLOC_FREE(mem_ctx);

	return -1; /* already replied */
}
Example #2
0
static NTSTATUS smbd_smb2_session_setup_krb5(struct smbd_smb2_session *session,
					struct smbd_smb2_request *smb2req,
					uint8_t in_security_mode,
					const DATA_BLOB *secblob,
					const char *mechOID,
					uint16_t *out_session_flags,
					DATA_BLOB *out_security_buffer,
					uint64_t *out_session_id)
{
	DATA_BLOB ap_rep = data_blob_null;
	DATA_BLOB ap_rep_wrapped = data_blob_null;
	DATA_BLOB ticket = data_blob_null;
	DATA_BLOB session_key = data_blob_null;
	DATA_BLOB secblob_out = data_blob_null;
	uint8 tok_id[2];
	struct PAC_LOGON_INFO *logon_info = NULL;
	char *principal = NULL;
	char *user = NULL;
	char *domain = NULL;
	struct passwd *pw = NULL;
	NTSTATUS status;
	char *real_username;
	bool username_was_mapped = false;
	bool map_domainuser_to_guest = false;

	if (!spnego_parse_krb5_wrap(talloc_tos(), *secblob, &ticket, tok_id)) {
		status = NT_STATUS_LOGON_FAILURE;
		goto fail;
	}

	status = ads_verify_ticket(smb2req, lp_realm(), 0, &ticket,
				   &principal, &logon_info, &ap_rep,
				   &session_key, true);

	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(1,("smb2: Failed to verify incoming ticket with error %s!\n",
			nt_errstr(status)));
		if (!NT_STATUS_EQUAL(status, NT_STATUS_TIME_DIFFERENCE_AT_DC)) {
			status = NT_STATUS_LOGON_FAILURE;
		}
		goto fail;
	}

	status = get_user_from_kerberos_info(talloc_tos(),
					     session->sconn->remote_hostname,
					     principal, logon_info,
					     &username_was_mapped,
					     &map_domainuser_to_guest,
					     &user, &domain,
					     &real_username, &pw);
	if (!NT_STATUS_IS_OK(status)) {
		goto fail;
	}

	/* save the PAC data if we have it */
	if (logon_info) {
		netsamlogon_cache_store(user, &logon_info->info3);
	}

	/* setup the string used by %U */
	sub_set_smb_name(real_username);

	/* reload services so that the new %U is taken into account */
	reload_services(smb2req->sconn->msg_ctx, smb2req->sconn->sock, true);

	status = make_session_info_krb5(session,
					user, domain, real_username, pw,
					logon_info, map_domainuser_to_guest,
					username_was_mapped,
					&session_key,
					&session->session_info);
	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(1, ("smb2: make_server_info_krb5 failed\n"));
		goto fail;
	}

	if ((in_security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) ||
	     lp_server_signing() == Required) {
		session->do_signing = true;
	}

	if (security_session_user_level(session->session_info, NULL) < SECURITY_USER) {
		/* we map anonymous to guest internally */
		*out_session_flags |= SMB2_SESSION_FLAG_IS_GUEST;
		*out_session_flags |= SMB2_SESSION_FLAG_IS_NULL;
		/* force no signing */
		session->do_signing = false;
	}

	session->session_key = session->session_info->session_key;

	session->compat_vuser = talloc_zero(session, user_struct);
	if (session->compat_vuser == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto fail;
	}
	session->compat_vuser->auth_ntlmssp_state = NULL;
	session->compat_vuser->homes_snum = -1;
	session->compat_vuser->session_info = session->session_info;
	session->compat_vuser->session_keystr = NULL;
	session->compat_vuser->vuid = session->vuid;
	DLIST_ADD(session->sconn->smb1.sessions.validated_users, session->compat_vuser);

	if (security_session_user_level(session->session_info, NULL) >= SECURITY_USER) {
		session->compat_vuser->homes_snum =
			register_homes_share(session->session_info->unix_info->unix_name);
	}

	if (!session_claim(session->sconn, session->compat_vuser)) {
		DEBUG(1, ("smb2: Failed to claim session "
			"for vuid=%d\n",
			session->compat_vuser->vuid));
		goto fail;
	}

	session->status = NT_STATUS_OK;

	/*
	 * we attach the session to the request
	 * so that the response can be signed
	 */
	smb2req->session = session;
	if (session->do_signing) {
		smb2req->do_signing = true;
	}

	global_client_caps |= (CAP_LEVEL_II_OPLOCKS|CAP_STATUS32);
        status = NT_STATUS_OK;

	/* wrap that up in a nice GSS-API wrapping */
	ap_rep_wrapped = spnego_gen_krb5_wrap(talloc_tos(), ap_rep,
				TOK_ID_KRB_AP_REP);

	secblob_out = spnego_gen_auth_response(
					talloc_tos(),
					&ap_rep_wrapped,
					status,
					mechOID);

	*out_security_buffer = data_blob_talloc(smb2req,
						secblob_out.data,
						secblob_out.length);
	if (secblob_out.data && out_security_buffer->data == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto fail;
	}

	data_blob_free(&ap_rep);
	data_blob_free(&ap_rep_wrapped);
	data_blob_free(&ticket);
	data_blob_free(&session_key);
	data_blob_free(&secblob_out);

	*out_session_id = session->vuid;

	return NT_STATUS_OK;

  fail:

	data_blob_free(&ap_rep);
	data_blob_free(&ap_rep_wrapped);
	data_blob_free(&ticket);
	data_blob_free(&session_key);
	data_blob_free(&secblob_out);

	ap_rep_wrapped = data_blob_null;
	secblob_out = spnego_gen_auth_response(
					talloc_tos(),
					&ap_rep_wrapped,
					status,
					mechOID);

	*out_security_buffer = data_blob_talloc(smb2req,
						secblob_out.data,
						secblob_out.length);
	data_blob_free(&secblob_out);
	return status;
}
Example #3
0
/****************************************************************************
reply to a session setup spnego negotiate packet for kerberos
****************************************************************************/
static int reply_spnego_kerberos(connection_struct *conn, 
				 char *inbuf, char *outbuf,
				 int length, int bufsize,
				 DATA_BLOB *secblob)
{
	DATA_BLOB ticket;
	char *client, *p, *domain;
	fstring netbios_domain_name;
	struct passwd *pw;
	char *user;
	int sess_vuid;
	NTSTATUS ret;
	DATA_BLOB auth_data;
	DATA_BLOB ap_rep, ap_rep_wrapped, response;
	auth_serversupplied_info *server_info = NULL;
	DATA_BLOB session_key = data_blob(NULL, 0);
	uint8 tok_id[2];
	DATA_BLOB nullblob = data_blob(NULL, 0);
	fstring real_username;

	ZERO_STRUCT(ticket);
	ZERO_STRUCT(auth_data);
	ZERO_STRUCT(ap_rep);
	ZERO_STRUCT(ap_rep_wrapped);
	ZERO_STRUCT(response);

	if (!spnego_parse_krb5_wrap(*secblob, &ticket, tok_id)) {
		return ERROR_NT(NT_STATUS_LOGON_FAILURE);
	}

	ret = ads_verify_ticket(lp_realm(), &ticket, &client, &auth_data, &ap_rep, &session_key);

	data_blob_free(&ticket);

	if (!NT_STATUS_IS_OK(ret)) {
		DEBUG(1,("Failed to verify incoming ticket!\n"));	
		return ERROR_NT(NT_STATUS_LOGON_FAILURE);
	}

	data_blob_free(&auth_data);

	DEBUG(3,("Ticket name is [%s]\n", client));

	p = strchr_m(client, '@');
	if (!p) {
		DEBUG(3,("Doesn't look like a valid principal\n"));
		data_blob_free(&ap_rep);
		data_blob_free(&session_key);
		SAFE_FREE(client);
		return ERROR_NT(NT_STATUS_LOGON_FAILURE);
	}

	*p = 0;
	if (!strequal(p+1, lp_realm())) {
		DEBUG(3,("Ticket for foreign realm %s@%s\n", client, p+1));
		if (!lp_allow_trusted_domains()) {
			data_blob_free(&ap_rep);
			data_blob_free(&session_key);
			SAFE_FREE(client);
			return ERROR_NT(NT_STATUS_LOGON_FAILURE);
		}
	}

	/* this gives a fully qualified user name (ie. with full realm).
	   that leads to very long usernames, but what else can we do? */

	domain = p+1;

	{
		/* If we have winbind running, we can (and must) shorten the
		   username by using the short netbios name. Otherwise we will
		   have inconsistent user names. With Kerberos, we get the
		   fully qualified realm, with ntlmssp we get the short
		   name. And even w2k3 does use ntlmssp if you for example
		   connect to an ip address. */

		struct winbindd_request wb_request;
		struct winbindd_response wb_response;
		NSS_STATUS wb_result;

		ZERO_STRUCT(wb_request);
		ZERO_STRUCT(wb_response);

		DEBUG(10, ("Mapping [%s] to short name\n", domain));

		fstrcpy(wb_request.domain_name, domain);

		wb_result = winbindd_request(WINBINDD_DOMAIN_INFO,
					     &wb_request, &wb_response);

		if (wb_result == NSS_STATUS_SUCCESS) {

			fstrcpy(netbios_domain_name,
				wb_response.data.domain_info.name);
			domain = netbios_domain_name;

			DEBUG(10, ("Mapped to [%s]\n", domain));
		} else {
			DEBUG(3, ("Could not find short name -- winbind "
				  "not running?\n"));
		}
	}

	asprintf(&user, "%s%c%s", domain, *lp_winbind_separator(), client);
	
	/* lookup the passwd struct, create a new user if necessary */

	map_username( user );

	pw = smb_getpwnam( user, real_username, True );
	
	if (!pw) {
		DEBUG(1,("Username %s is invalid on this system\n",user));
		SAFE_FREE(user);
		SAFE_FREE(client);
		data_blob_free(&ap_rep);
		data_blob_free(&session_key);
		return ERROR_NT(NT_STATUS_LOGON_FAILURE);
	}

	/* setup the string used by %U */
	
	sub_set_smb_name( real_username );
	reload_services(True);
	
	if (!NT_STATUS_IS_OK(ret = make_server_info_pw(&server_info, real_username, pw))) 
	{
		DEBUG(1,("make_server_info_from_pw failed!\n"));
		SAFE_FREE(user);
		SAFE_FREE(client);
		data_blob_free(&ap_rep);
		data_blob_free(&session_key);
		return ERROR_NT(ret);
	}

        /* make_server_info_pw does not set the domain. Without this we end up
	 * with the local netbios name in substitutions for %D. */

        if (server_info->sam_account != NULL) {
                pdb_set_domain(server_info->sam_account, domain, PDB_SET);
        }

	/* register_vuid keeps the server info */
	/* register_vuid takes ownership of session_key, no need to free after this.
 	   A better interface would copy it.... */
	sess_vuid = register_vuid(server_info, session_key, nullblob, client);

	SAFE_FREE(user);
	SAFE_FREE(client);

	if (sess_vuid == -1) {
		ret = NT_STATUS_LOGON_FAILURE;
	} else {
		/* current_user_info is changed on new vuid */
		reload_services( True );

		set_message(outbuf,4,0,True);
		SSVAL(outbuf, smb_vwv3, 0);
			
		if (server_info->guest) {
			SSVAL(outbuf,smb_vwv2,1);
		}
		
		SSVAL(outbuf, smb_uid, sess_vuid);

		if (!server_info->guest && !srv_signing_started()) {
			/* We need to start the signing engine
			 * here but a W2K client sends the old
			 * "BSRSPYL " signature instead of the
			 * correct one. Subsequent packets will
			 * be correct.
			 */
		       	srv_check_sign_mac(inbuf, False);
		}
	}

        /* wrap that up in a nice GSS-API wrapping */
	if (NT_STATUS_IS_OK(ret)) {
		ap_rep_wrapped = spnego_gen_krb5_wrap(ap_rep, TOK_ID_KRB_AP_REP);
	} else {
		ap_rep_wrapped = data_blob(NULL, 0);
	}
	response = spnego_gen_auth_response(&ap_rep_wrapped, ret, OID_KERBEROS5_OLD);
	reply_sesssetup_blob(conn, outbuf, response, ret);

	data_blob_free(&ap_rep);
	data_blob_free(&ap_rep_wrapped);
	data_blob_free(&response);

	return -1; /* already replied */
}