/** Process an entry modification operation
 *
 * @note This is a callback for the sync_demux function.
 *
 * @param[in] conn	the sync belongs to.
 * @param[in] config	of the sync that received an entry.
 * @param[in] sync_id	of the sync that received an entry.
 * @param[in] phase	Refresh phase the sync is currently in.
 * @param[in] uuid	of the entry.
 * @param[in] msg	containing the entry.
 * @param[in] state	The type of modification we need to perform to our
 *			representation of the entry.
 * @param[in] user_ctx	The listener.
 * @return
 *	- 0 on success.
 *	- -1 on failure.
 */
static int _proto_ldap_entry(fr_ldap_connection_t *conn, sync_config_t const *config,
			     int sync_id, UNUSED sync_phases_t phase,
			     uint8_t const uuid[SYNC_UUID_LENGTH], LDAPMessage *msg,
			     sync_states_t state, void *user_ctx)
{
	rad_listen_t		*listen = talloc_get_type_abort(user_ctx, rad_listen_t);
	proto_ldap_inst_t	*inst = talloc_get_type_abort(listen->data, proto_ldap_inst_t);
	fr_ldap_map_exp_t	expanded;
	REQUEST			*request;

	request = proto_ldap_request_setup(listen, inst, sync_id);
	if (!request) return -1;

	proto_ldap_attributes_add(request, config);
	request->packet->code = state;

	/*
	 *	Add the entry DN and attributes
	 */
	if (msg) {
		char *entry_dn;
		VALUE_PAIR *vp;

		entry_dn = ldap_get_dn(conn->handle, msg);

		MEM(pair_update_request(&vp, attr_ldap_sync_entry_dn) >= 0);
		ldap_memfree(entry_dn);

		MEM(pair_update_request(&vp, attr_ldap_sync_entry_uuid) >= 0);
		fr_pair_value_memcpy(vp, uuid, SYNC_UUID_LENGTH);
	}

	/*
	 *	Apply the attribute map
	 */
	if (fr_ldap_map_expand(&expanded, request, config->entry_map) < 0) {
	error:
		talloc_free(request);
		return -1;
	}
	if (fr_ldap_map_do(request, conn, NULL, &expanded, msg) < 0) goto error;

//	request_enqueue(request);

	return 0;
}
/** Enque a new cookie store request
 *
 * Create a new request containing the cookie we received from the LDAP server. This allows
 * the administrator to store the cookie and provide it on a future call to
 * #proto_ldap_cookie_load.
 *
 * @note This is a callback for the sync_demux function.
 *
 * @param[in] conn	the cookie was received on.
 * @param[in] config	of the LDAP sync.
 * @param[in] sync_id	sync number (msgid) of the sync within the context of the connection.
 * @param[in] cookie	received from the LDAP server.
 * @param[in] user_ctx	listener.
 * @return
 *	- 0 on success.
 *	- -1 on failure
 */
static int _proto_ldap_cookie_store(UNUSED fr_ldap_connection_t *conn, sync_config_t const *config,
			      	    int sync_id, uint8_t const *cookie, void *user_ctx)
{
	rad_listen_t		*listen = talloc_get_type_abort(user_ctx, rad_listen_t);
	proto_ldap_inst_t	*inst = talloc_get_type_abort(listen->data, proto_ldap_inst_t);
	REQUEST			*request;
	VALUE_PAIR		*vp;

	request = proto_ldap_request_setup(listen, inst, sync_id);
	if (!request) return -1;

	proto_ldap_attributes_add(request, config);

	MEM(pair_update_request(&vp, attr_ldap_sync_cookie) >= 0);
	fr_pair_value_memcpy(vp, cookie, talloc_array_length(cookie));

	request->packet->code = LDAP_SYNC_CODE_COOKIE_STORE;

//	request_enqueue(request);

	return 0;
}
/** Add attributes describing the sync to the request
 *
 * Adds:
 * - LDAP-Sync-DN     - The DN we're searching on (not the DN of any received object).
 * - LDAP-Sync-Filter - The filter for the search.
 * - LDAP-Sync-Attr   - The attributes we retrieved.
 *
 * @param[in] request	The current request.
 * @param[in] config	Configuration of the sync.
 * @return
 *	- 0 on success.
 *	- -1 on failure.
 */
static int proto_ldap_attributes_add(REQUEST *request, sync_config_t const *config)
{
	VALUE_PAIR *vp;

	MEM(pair_add_request(&vp, attr_ldap_sync_dn) == 0);
	fr_pair_value_strcpy(vp, config->base_dn);

	if (config->filter) {
		MEM(pair_update_request(&vp, attr_ldap_sync_filter) >= 0);
		fr_pair_value_strcpy(vp, config->filter);
	}
	if (config->attrs) {
		char const *attrs_p;

		for (attrs_p = *config->attrs; *attrs_p; attrs_p++) {
			MEM(pair_add_request(&vp, attr_ldap_sync_attr) == 0);
			fr_pair_value_strcpy(vp, attrs_p);
		}
	}

	return 0;
}
Exemple #4
0
static rlm_rcode_t cache_merge(rlm_cache_t const *inst, REQUEST *request, rlm_cache_entry_t *c)
{
	VALUE_PAIR	*vp;
	vp_map_t	*map;
	int		merged = 0;

	RDEBUG2("Merging cache entry into request");
	RINDENT();
	for (map = c->maps; map; map = map->next) {
		/*
		 *	The only reason that the application of a map entry
		 *	can fail, is if the destination list or request
		 *	isn't valid. For now we don't consider this fatal
		 *	and continue merging the rest of the maps.
		 */
		if (map_to_request(request, map, map_to_vp, NULL) < 0) {
			char buffer[1024];

			map_snprint(buffer, sizeof(buffer), map);
			REXDENT();
			RDEBUG2("Skipping %s", buffer);
			RINDENT();
			continue;
		}
		merged++;
	}
	REXDENT();

	if (inst->config.stats) {
		rad_assert(request->packet != NULL);
		MEM(pair_update_request(&vp, attr_cache_entry_hits) >= 0);
		vp->vp_uint32 = c->hits;
	}

	return merged > 0 ?
		RLM_MODULE_UPDATED :
		RLM_MODULE_OK;
}
/** Record session state changes
 *
 * Called by OpenSSL whenever the session state changes, an alert is received or an error occurs.
 *
 * @param[in] ssl	session.
 * @param[in] where	Which context the callback is being called in.
 *			See https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_set_info_callback.html
 *			for additional info.
 * @param[in] ret	0 if an error occurred, or the alert type if an alert was received.
 */
void tls_session_info_cb(SSL const *ssl, int where, int ret)
{
	char const	*role, *state;
	REQUEST		*request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST);

	if ((where & ~SSL_ST_MASK) & SSL_ST_CONNECT) {
		role = "Client ";
	} else if (((where & ~SSL_ST_MASK)) & SSL_ST_ACCEPT) {
		role = "Server ";
	} else {
		role = "";
	}

	state = SSL_state_string_long(ssl);
	state = state ? state : "<INVALID>";

	if ((where & SSL_CB_LOOP) || (where & SSL_CB_HANDSHAKE_START) || (where & SSL_CB_HANDSHAKE_DONE)) {
		if (RDEBUG_ENABLED3) {
			char const *abbrv = SSL_state_string(ssl);
			size_t len;

			/*
			 *	Trim crappy OpenSSL state strings...
			 */
			len = strlen(abbrv);
			if ((len > 1) && (abbrv[len - 1] == ' ')) len--;

			RDEBUG3("Handshake state [%.*s] - %s%s", (int)len, abbrv, role, state);
		} else {
			RDEBUG2("Handshake state - %s%s", role, state);
		}
		return;
	}

	if (where & SSL_CB_ALERT) {
		if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) return;

		if (where & SSL_CB_READ) {
			VALUE_PAIR *vp;

			REDEBUG("Client sent %s TLS alert: %s", SSL_alert_type_string_long(ret),
			        SSL_alert_desc_string_long(ret));

			/*
			 *	Offer helpful advice... Should be expanded.
			 */
			switch (ret & 0xff) {
			case TLS1_AD_UNKNOWN_CA:
				REDEBUG("Verify client has copy of CA certificate, and trusts CA");
				break;

			default:
				break;
			}

			MEM(pair_update_request(&vp, attr_tls_client_error_code) >= 0);
			vp->vp_uint8 = ret & 0xff;
			RDEBUG2("&TLS-Client-Error-Code := %pV", &vp->data);
		} else {
			REDEBUG("Sending client %s TLS alert: %s %i", SSL_alert_type_string_long(ret),
				SSL_alert_desc_string_long(ret), ret & 0xff);
		}
		return;
	}

	if (where & SSL_CB_EXIT) {
		if (ret == 0) {
			REDEBUG("Handshake exit state %s%s", role, state);
			return;
		}

		if (ret < 0) {
			if (SSL_want_read(ssl)) {
				RDEBUG2("Need more data from client"); /* State same as previous call, don't print */
				return;
			}
			REDEBUG("Handshake exit state %s%s", role, state);
		}
	}
}
/** Determine the PSK to use for an incoming connection
 *
 * @param[in] ssl		session.
 * @param[in] identity		The identity of the PSK to search for.
 * @param[out] psk		Where to write the PSK we found (if any).
 * @param[in] max_psk_len	The length of the buffer provided for PSK.
 * @return
 *	- 0 if no PSK matching identity was found.
 *	- >0 if a PSK matching identity was found (the length of bytes written to psk).
 */
unsigned int tls_session_psk_server_cb(SSL *ssl, const char *identity,
				       unsigned char *psk, unsigned int max_psk_len)
{
	unsigned int	psk_len = 0;
	fr_tls_conf_t	*conf;
	REQUEST		*request;

	conf = (fr_tls_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
	if (!conf) return 0;

	request = (REQUEST *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST);
	if (request && conf->psk_query) {
		size_t hex_len;
		VALUE_PAIR *vp;
		char buffer[2 * PSK_MAX_PSK_LEN + 4]; /* allow for too-long keys */

		/*
		 *	The passed identity is weird.  Deny it.
		 */
		if (!session_psk_identity_is_safe(identity)) {
			RWDEBUG("Invalid characters in PSK identity %s", identity);
			return 0;
		}

		MEM(pair_update_request(&vp, attr_tls_psk_identity) >= 0);
		if (fr_pair_value_from_str(vp, identity, -1, '\0', true) < 0) {
			RPWDEBUG2("Failed parsing TLS PSK Identity");
			talloc_free(vp);
			return 0;
		}

		hex_len = xlat_eval(buffer, sizeof(buffer), request, conf->psk_query, NULL, NULL);
		if (!hex_len) {
			RWDEBUG("PSK expansion returned an empty string.");
			return 0;
		}

		/*
		 *	The returned key is truncated at MORE than
		 *	OpenSSL can handle.  That way we can detect
		 *	the truncation, and complain about it.
		 */
		if (hex_len > (2 * max_psk_len)) {
			RWDEBUG("Returned PSK is too long (%u > %u)", (unsigned int) hex_len, 2 * max_psk_len);
			return 0;
		}

		/*
		 *	Leave the TLS-PSK-Identity in the request, and
		 *	convert the expansion from printable string
		 *	back to hex.
		 */
		return fr_hex2bin(psk, max_psk_len, buffer, hex_len);
	}

	if (!conf->psk_identity) {
		DEBUG("No static PSK identity set.  Rejecting the user");
		return 0;
	}

	/*
	 *	No REQUEST, or no dynamic query.  Just look for a
	 *	static identity.
	 */
	if (strcmp(identity, conf->psk_identity) != 0) {
		ERROR("Supplied PSK identity %s does not match configuration.  Rejecting.",
		      identity);
		return 0;
	}

	psk_len = strlen(conf->psk_password);
	if (psk_len > (2 * max_psk_len)) return 0;

	return fr_hex2bin(psk, max_psk_len, conf->psk_password, psk_len);
}
/** Continue a TLS handshake
 *
 * Advance the TLS handshake by feeding OpenSSL data from dirty_in,
 * and reading data from OpenSSL into dirty_out.
 *
 * @param request The current request.
 * @param session The current TLS session.
 * @return
 *	- 0 on error.
 *	- 1 on success.
 */
int tls_session_handshake(REQUEST *request, tls_session_t *session)
{
	int ret;

	/*
	 *	This is a logic error.  tls_session_handshake
	 *	must not be called if the handshake is
	 *	complete tls_session_recv must be
	 *	called instead.
	 */
	if (SSL_is_init_finished(session->ssl)) {
		REDEBUG("Attempted to continue TLS handshake, but handshake has completed");
		return 0;
	}

	if (session->invalid) {
		REDEBUG("Preventing invalid session from continuing");
		return 0;
	}

	/*
	 *	Feed dirty data into OpenSSL, so that is can either
	 *	process it as Application data (decrypting it)
	 *	or continue the TLS handshake.
	 */
	if (session->dirty_in.used) {
		ret = BIO_write(session->into_ssl, session->dirty_in.data, session->dirty_in.used);
		if (ret != (int)session->dirty_in.used) {
			REDEBUG("Failed writing %zd bytes to TLS BIO: %d", session->dirty_in.used, ret);
			record_init(&session->dirty_in);
			return 0;
		}
		record_init(&session->dirty_in);
	}

	/*
	 *	Magic/More magic? Although SSL_read is normally
	 *	used to read application data, it will also
	 *	continue the TLS handshake.  Removing this call will
	 *	cause the handshake to fail.
	 *
	 *	We don't ever expect to actually *receive* application
	 *	data here.
	 *
	 *	The reason why we call SSL_read instead of SSL_accept,
	 *	or SSL_connect, as it allows this function
	 *	to be used, irrespective or whether we're acting
	 *	as a client or a server.
	 *
	 *	If acting as a client SSL_set_connect_state must have
	 *	been called before this function.
	 *
	 *	If acting as a server SSL_set_accept_state must have
	 *	been called before this function.
	 */
	ret = SSL_read(session->ssl, session->clean_out.data + session->clean_out.used,
		       sizeof(session->clean_out.data) - session->clean_out.used);
	if (ret > 0) {
		session->clean_out.used += ret;
		return 1;
	}
	if (!tls_log_io_error(request, session, ret, "Failed in SSL_read")) return 0;

	/*
	 *	This only occurs once per session, where calling
	 *	SSL_read updates the state of the SSL session, setting
	 *	this flag to true.
	 *
	 *	Callbacks provide enough info so we don't need to
	 *	print debug statements when the handshake is in other
	 *	states.
	 */
	if (SSL_is_init_finished(session->ssl)) {
		SSL_CIPHER const *cipher;
		VALUE_PAIR *vp;
		char const *str_version;

		char cipher_desc[256], cipher_desc_clean[256];
		char *p = cipher_desc, *q = cipher_desc_clean;

		cipher = SSL_get_current_cipher(session->ssl);
		SSL_CIPHER_description(cipher, cipher_desc, sizeof(cipher_desc));

		/*
		 *	Cleanup the output from OpenSSL
		 *	Seems to print info in a tabular format.
		 */
		while (*p != '\0') {
			if (isspace(*p)) {
				*q++ = *p;
				while (isspace(*++p));
				continue;
			}
			*q++ = *p++;
		}
		*q = '\0';

		RDEBUG2("Cipher suite: %s", cipher_desc_clean);

		vp = fr_pair_afrom_num(request->state_ctx, 0, FR_TLS_SESSION_CIPHER_SUITE);
		if (vp) {
			fr_pair_value_strcpy(vp,  SSL_CIPHER_get_name(cipher));
			fr_pair_add(&request->state, vp);
			RDEBUG2("    &session-state:TLS-Session-Cipher-Suite := \"%s\"", vp->vp_strvalue);
		}

		switch (session->info.version) {
		case SSL2_VERSION:
			str_version = "SSL 2.0";
			break;
		case SSL3_VERSION:
			str_version = "SSL 3.0";
			break;
		case TLS1_VERSION:
			str_version = "TLS 1.0";
			break;
#ifdef TLS1_1_VERSION
		case TLS1_1_VERSION:
			str_version = "TLS 1.1";
			break;
#endif
#ifdef TLS1_2_VERSION
		case TLS1_2_VERSION:
			str_version = "TLS 1.2";
			break;
#endif
#ifdef TLS1_3_VERSON
		case TLS1_3_VERSION:
			str_version = "TLS 1.3";
			break;
#endif
		default:
			str_version = "UNKNOWN";
			break;
		}

		vp = fr_pair_afrom_num(request->state_ctx, 0, FR_TLS_SESSION_VERSION);
		if (vp) {
			fr_pair_value_strcpy(vp, str_version);
			fr_pair_add(&request->state, vp);
			RDEBUG2("    &session-state:TLS-Session-Version := \"%s\"", str_version);
		}

#if OPENSSL_VERSION_NUMBER >= 0x10001000L
		/*
		 *	Cache the SSL_SESSION pointer.
		 *
		 *	Which contains all the data we need for session resumption.
		 */
		if (!session->ssl_session) {
			session->ssl_session = SSL_get_session(session->ssl);
			if (!session->ssl_session) {
				REDEBUG("Failed getting TLS session");
				return 0;
			}
		}

		if (RDEBUG_ENABLED3) {
			BIO *ssl_log = BIO_new(BIO_s_mem());

			if (ssl_log) {
				if (SSL_SESSION_print(ssl_log, session->ssl_session) == 1) {
					SSL_DRAIN_ERROR_QUEUE(RDEBUG3, "", ssl_log);
				} else {
					RDEBUG3("Failed retrieving session data");
				}
				BIO_free(ssl_log);
			}
		}
#endif

		/*
		 *	Session was resumed, add attribute to mark it as such.
		 */
		if (SSL_session_reused(session->ssl)) {
			/*
			 *	Mark the request as resumed.
			 */
			MEM(pair_update_request(&vp, attr_eap_session_resumed) >= 0);
			vp->vp_bool = true;
		}
	}

	/*
	 *	Get data to pack and send back to the TLS peer.
	 */
	ret = BIO_ctrl_pending(session->from_ssl);
	if (ret > 0) {
		ret = BIO_read(session->from_ssl, session->dirty_out.data,
			       sizeof(session->dirty_out.data));
		if (ret > 0) {
			session->dirty_out.used = ret;
		} else if (BIO_should_retry(session->from_ssl)) {
			record_init(&session->dirty_in);
			RDEBUG2("Asking for more data in tunnel");
			return 1;

		} else {
			tls_log_error(NULL, NULL);
			record_init(&session->dirty_in);
			return 0;
		}
	} else {
		/* Its clean application data, do whatever we want */
		record_init(&session->clean_out);
	}

	/*
	 *	W would prefer to latch on info.content_type but
	 *	(I think its...) tls_session_msg_cb() updates it
	 *	after the call to tls_session_handshake_alert()
	 */
	if (session->handshake_alert.level) {
		/*
		 * FIXME RFC 4851 section 3.6.1 - peer might ACK alert and include a restarted ClientHello
		 *                                 which eap_tls_session_status() will fail on
		 */
		session->info.content_type = SSL3_RT_ALERT;

		session->dirty_out.data[0] = session->info.content_type;
		session->dirty_out.data[1] = 3;
		session->dirty_out.data[2] = 1;
		session->dirty_out.data[3] = 0;
		session->dirty_out.data[4] = 2;
		session->dirty_out.data[5] = session->handshake_alert.level;
		session->dirty_out.data[6] = session->handshake_alert.description;

		session->dirty_out.used = 7;

		session->handshake_alert.level = 0;
	}

	/* We are done with dirty_in, reinitialize it */
	record_init(&session->dirty_in);
	return 1;
}