Exemple #1
0
bool kpasswdd_process(struct kdc_server *kdc,
		      TALLOC_CTX *mem_ctx,
		      DATA_BLOB *input,
		      DATA_BLOB *reply,
		      struct tsocket_address *peer_addr,
		      struct tsocket_address *my_addr,
		      int datagram_reply)
{
	bool ret;
	const uint16_t header_len = 6;
	uint16_t len;
	uint16_t ap_req_len;
	uint16_t krb_priv_len;
	uint16_t version;
	NTSTATUS nt_status;
	DATA_BLOB ap_req, krb_priv_req;
	DATA_BLOB krb_priv_rep = data_blob(NULL, 0);
	DATA_BLOB ap_rep = data_blob(NULL, 0);
	DATA_BLOB kpasswd_req, kpasswd_rep;
	struct cli_credentials *server_credentials;
	struct gensec_security *gensec_security;
	TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);

	char *keytab_name;

	if (!tmp_ctx) {
		return false;
	}

	/* Be parinoid.  We need to ensure we don't just let the
	 * caller lead us into a buffer overflow */
	if (input->length <= header_len) {
		talloc_free(tmp_ctx);
		return false;
	}

	len = RSVAL(input->data, 0);
	if (input->length != len) {
		talloc_free(tmp_ctx);
		return false;
	}

	/* There are two different versions of this protocol so far,
	 * plus others in the standards pipe.  Fortunetly they all
	 * take a very similar framing */
	version = RSVAL(input->data, 2);
	ap_req_len = RSVAL(input->data, 4);
	if ((ap_req_len >= len) || (ap_req_len + header_len) >= len) {
		talloc_free(tmp_ctx);
		return false;
	}

	krb_priv_len = len - ap_req_len;
	ap_req = data_blob_const(&input->data[header_len], ap_req_len);
	krb_priv_req = data_blob_const(&input->data[header_len + ap_req_len], krb_priv_len);

	server_credentials = cli_credentials_init(tmp_ctx);
	if (!server_credentials) {
		DEBUG(1, ("Failed to init server credentials\n"));
		return false;
	}

	/* We want the credentials subsystem to use the krb5 context
	 * we already have, rather than a new context */
	cli_credentials_set_krb5_context(server_credentials, kdc->smb_krb5_context);
	cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx);

	keytab_name = talloc_asprintf(server_credentials, "HDB:samba4&%p", kdc->base_ctx);

	cli_credentials_set_username(server_credentials, "kadmin/changepw", CRED_SPECIFIED);
	ret = cli_credentials_set_keytab_name(server_credentials, kdc->task->event_ctx, kdc->task->lp_ctx, keytab_name, CRED_SPECIFIED);
	if (ret != 0) {
		ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
						       KRB5_KPASSWD_HARDERROR,
						       talloc_asprintf(mem_ctx,
								       "Failed to obtain server credentials for kadmin/changepw: %s\n",
								       nt_errstr(nt_status)),
						       &krb_priv_rep);
		ap_rep.length = 0;
		if (ret) {
			goto reply;
		}
		talloc_free(tmp_ctx);
		return ret;
	}

	/* We don't strictly need to call this wrapper, and could call
	 * gensec_server_start directly, as we have no need for NTLM
	 * and we have a PAC, but this ensures that the wrapper can be
	 * safely extended for other helpful things in future */
	nt_status = samba_server_gensec_start(tmp_ctx, kdc->task->event_ctx,
					      kdc->task->msg_ctx,
					      kdc->task->lp_ctx,
					      server_credentials,
					      "kpasswd",
					      &gensec_security);
	if (!NT_STATUS_IS_OK(nt_status)) {
		talloc_free(tmp_ctx);
		return false;
	}

	/* The kerberos PRIV packets include these addresses.  MIT
	 * clients check that they are present */
#if 0
	/* Skip this part for now, it breaks with a NetAPP filer and
	 * in any case where the client address is behind NAT.  If
	 * older MIT clients need this, we might have to insert more
	 * complex code */

	nt_status = gensec_set_local_address(gensec_security, peer_addr);
	if (!NT_STATUS_IS_OK(nt_status)) {
		talloc_free(tmp_ctx);
		return false;
	}
#endif

	nt_status = gensec_set_local_address(gensec_security, my_addr);
	if (!NT_STATUS_IS_OK(nt_status)) {
		talloc_free(tmp_ctx);
		return false;
	}

	/* We want the GENSEC wrap calls to generate PRIV tokens */
	gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);

	nt_status = gensec_start_mech_by_name(gensec_security, "krb5");
	if (!NT_STATUS_IS_OK(nt_status)) {
		talloc_free(tmp_ctx);
		return false;
	}

	/* Accept the AP-REQ and generate teh AP-REP we need for the reply */
	nt_status = gensec_update(gensec_security, tmp_ctx, ap_req, &ap_rep);
	if (!NT_STATUS_IS_OK(nt_status) && !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {

		ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
						       KRB5_KPASSWD_HARDERROR,
						       talloc_asprintf(mem_ctx,
								       "gensec_update failed: %s",
								       nt_errstr(nt_status)),
						       &krb_priv_rep);
		ap_rep.length = 0;
		if (ret) {
			goto reply;
		}
		talloc_free(tmp_ctx);
		return ret;
	}

	/* Extract the data from the KRB-PRIV half of the message */
	nt_status = gensec_unwrap(gensec_security, tmp_ctx, &krb_priv_req, &kpasswd_req);
	if (!NT_STATUS_IS_OK(nt_status)) {
		ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
						       KRB5_KPASSWD_HARDERROR,
						       talloc_asprintf(mem_ctx,
								       "gensec_unwrap failed: %s",
								       nt_errstr(nt_status)),
						       &krb_priv_rep);
		ap_rep.length = 0;
		if (ret) {
			goto reply;
		}
		talloc_free(tmp_ctx);
		return ret;
	}

	/* Figure out something to do with it (probably changing a password...) */
	ret = kpasswd_process_request(kdc, tmp_ctx,
				      gensec_security,
				      version,
				      &kpasswd_req, &kpasswd_rep);
	if (!ret) {
		/* Argh! */
		return false;
	}

	/* And wrap up the reply: This ensures that the error message
	 * or success can be verified by the client */
	nt_status = gensec_wrap(gensec_security, tmp_ctx,
				&kpasswd_rep, &krb_priv_rep);
	if (!NT_STATUS_IS_OK(nt_status)) {
		ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
						       KRB5_KPASSWD_HARDERROR,
						       talloc_asprintf(mem_ctx,
								       "gensec_wrap failed: %s",
								       nt_errstr(nt_status)),
						       &krb_priv_rep);
		ap_rep.length = 0;
		if (ret) {
			goto reply;
		}
		talloc_free(tmp_ctx);
		return ret;
	}

reply:
	*reply = data_blob_talloc(mem_ctx, NULL, krb_priv_rep.length + ap_rep.length + header_len);
	if (!reply->data) {
		return false;
	}

	RSSVAL(reply->data, 0, reply->length);
	RSSVAL(reply->data, 2, 1); /* This is a version 1 reply, MS change/set or otherwise */
	RSSVAL(reply->data, 4, ap_rep.length);
	memcpy(reply->data + header_len,
	       ap_rep.data,
	       ap_rep.length);
	memcpy(reply->data + header_len + ap_rep.length,
	       krb_priv_rep.data,
	       krb_priv_rep.length);

	talloc_free(tmp_ctx);
	return ret;
}
Exemple #2
0
/*
  authorize a zone update
 */
_PUBLIC_ isc_boolean_t dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
				    const char *type, const char *key, uint32_t keydatalen, uint8_t *keydata,
				    void *dbdata)
{
	struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
	TALLOC_CTX *tmp_ctx;
	DATA_BLOB ap_req;
	struct cli_credentials *server_credentials;
	char *keytab_name;
	int ret;
	int ldb_ret;
	NTSTATUS nt_status;
	struct gensec_security *gensec_ctx;
	struct auth_session_info *session_info;
	struct ldb_dn *dn;
	isc_result_t result;
	struct ldb_result *res;
	const char * attrs[] = { NULL };
	uint32_t access_mask;

	/* Remove cached credentials, if any */
	if (state->session_info) {
		talloc_free(state->session_info);
		state->session_info = NULL;
	}
	if (state->update_name) {
		talloc_free(state->update_name);
		state->update_name = NULL;
	}

	tmp_ctx = talloc_new(NULL);
	if (tmp_ctx == NULL) {
		state->log(ISC_LOG_ERROR, "samba_dlz: no memory");
		return ISC_FALSE;
	}

	ap_req = data_blob_const(keydata, keydatalen);
	server_credentials = cli_credentials_init(tmp_ctx);
	if (!server_credentials) {
		state->log(ISC_LOG_ERROR, "samba_dlz: failed to init server credentials");
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}

	cli_credentials_set_krb5_context(server_credentials, state->smb_krb5_ctx);
	cli_credentials_set_conf(server_credentials, state->lp);

	keytab_name = talloc_asprintf(tmp_ctx, "file:%s/dns.keytab",
					lpcfg_private_dir(state->lp));
	ret = cli_credentials_set_keytab_name(server_credentials, state->lp, keytab_name,
						CRED_SPECIFIED);
	if (ret != 0) {
		state->log(ISC_LOG_ERROR, "samba_dlz: failed to obtain server credentials from %s",
			   keytab_name);
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}
	talloc_free(keytab_name);

	nt_status = gensec_server_start(tmp_ctx,
					lpcfg_gensec_settings(tmp_ctx, state->lp),
					state->auth_context, &gensec_ctx);
	if (!NT_STATUS_IS_OK(nt_status)) {
		state->log(ISC_LOG_ERROR, "samba_dlz: failed to start gensec server");
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}

	gensec_set_credentials(gensec_ctx, server_credentials);

	nt_status = gensec_start_mech_by_name(gensec_ctx, "spnego");
	if (!NT_STATUS_IS_OK(nt_status)) {
		state->log(ISC_LOG_ERROR, "samba_dlz: failed to start spnego");
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}

	nt_status = gensec_update(gensec_ctx, tmp_ctx, state->ev_ctx, ap_req, &ap_req);
	if (!NT_STATUS_IS_OK(nt_status)) {
		state->log(ISC_LOG_ERROR, "samba_dlz: spnego update failed");
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}

	nt_status = gensec_session_info(gensec_ctx, tmp_ctx, &session_info);
	if (!NT_STATUS_IS_OK(nt_status)) {
		state->log(ISC_LOG_ERROR, "samba_dlz: failed to create session info");
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}

	/* Get the DN from name */
	result = b9_find_name_dn(state, name, tmp_ctx, &dn);
	if (result != ISC_R_SUCCESS) {
		state->log(ISC_LOG_ERROR, "samba_dlz: failed to find name %s", name);
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}

	/* make sure the dn exists, or find parent dn in case new object is being added */
	ldb_ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_BASE,
				attrs, "objectClass=dnsNode");
	if (ldb_ret == LDB_ERR_NO_SUCH_OBJECT) {
		ldb_dn_remove_child_components(dn, 1);
		access_mask = SEC_ADS_CREATE_CHILD;
		talloc_free(res);
	} else if (ldb_ret == LDB_SUCCESS) {
		access_mask = SEC_STD_REQUIRED | SEC_ADS_SELF_WRITE;
		talloc_free(res);
	} else {
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}

	/* Do ACL check */
	ldb_ret = dsdb_check_access_on_dn(state->samdb, tmp_ctx, dn,
						session_info->security_token,
						access_mask, NULL);
	if (ldb_ret != LDB_SUCCESS) {
		state->log(ISC_LOG_INFO,
			"samba_dlz: disallowing update of signer=%s name=%s type=%s error=%s",
			signer, name, type, ldb_strerror(ldb_ret));
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}

	/* Cache session_info, so it can be used in the actual add/delete operation */
	state->update_name = talloc_strdup(state, name);
	if (state->update_name == NULL) {
		state->log(ISC_LOG_ERROR, "samba_dlz: memory allocation error");
		talloc_free(tmp_ctx);
		return ISC_FALSE;
	}
	state->session_info = talloc_steal(state, session_info);

	state->log(ISC_LOG_INFO, "samba_dlz: allowing update of signer=%s name=%s tcpaddr=%s type=%s key=%s",
		   signer, name, tcpaddr, type, key);

	talloc_free(tmp_ctx);
	return ISC_TRUE;
}