/** 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; }
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; }