/
niceSOFT
/
dovecot
Обзор
Документация
Войти
/
niceSOFT
/
dovecot
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/lib-auth-client/auth-client-request.c
639 строк
19 KB
Michael M Slusarz
COPYING: Simplify and standardize copyright handling
03 июл 2026, 02:25
03 июл 2026, 02:25
2a5809c
Код
Авторство
О чём код?
/* Copyright (c) Dovecot authors, see top-level COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "strescape.h" #include "base64.h" #include "ostream.h" #include "auth-client-private.h" #include "strfuncs.h" static void auth_client_request_fail_conn_lost(struct auth_client_request *request); static void auth_client_request_handle_input(struct auth_client_request **_request, enum auth_request_status status, const char *base64_data, const char *const *args, bool final); static void auth_server_send_new_request(struct auth_client_connection *conn, struct auth_client_request *request, const struct auth_request_info *info) { string_t *str; str = t_str_new(512); str_printfa(str, "AUTH\t%u\t", request->id); str_append_tabescaped(str, info->mech); str_append(str, "\tprotocol="); str_append_tabescaped(str, info->protocol); event_add_str(request->event, "mechanism", info->mech); event_add_str(request->event, "protocol", info->protocol); str_append(str, "\tfinal-resp-ok"); if ((info->flags & AUTH_REQUEST_FLAG_CONN_SECURED) != 0) { str_append(str, "\tsecured"); if ((info->flags & AUTH_REQUEST_FLAG_CONN_SECURED_TLS) != 0) { str_append(str, "=tls"); event_add_str(request->event, "transport", "TLS"); } else { event_add_str(request->event, "transport", "secured"); } } else { i_assert((info->flags & AUTH_REQUEST_FLAG_CONN_SECURED_TLS) == 0); event_add_str(request->event, "transport", "insecure"); } if ((info->flags & AUTH_REQUEST_FLAG_NO_PENALTY) != 0) str_append(str, "\tno-penalty"); if ((info->flags & AUTH_REQUEST_FLAG_VALID_CLIENT_CERT) != 0) str_append(str, "\tvalid-client-cert"); if ((info->flags & AUTH_REQUEST_FLAG_DEBUG) != 0) str_append(str, "\tdebug"); if (info->session_id != NULL) { str_append(str, "\tsession="); str_append_tabescaped(str, info->session_id); event_add_str(request->event, "session", info->session_id); } if (info->cert_username != NULL) { str_append(str, "\tcert_username="); str_append_tabescaped(str, info->cert_username); event_add_str(request->event, "certificate_user", info->cert_username); } if (info->ssl_client_cert_fp != NULL) { str_append(str, "\tssl_client_cert_fp="); str_append_tabescaped(str, info->ssl_client_cert_fp); event_add_str(request->event, "ssl_client_cert_fp", info->ssl_client_cert_fp); } if (info->ssl_client_cert_pubkey_fp != NULL) { str_append(str, "\tssl_client_cert_pubkey_fp="); str_append_tabescaped(str, info->ssl_client_cert_pubkey_fp); event_add_str(request->event, "ssl_client_cert_pubkey_fp", info->ssl_client_cert_pubkey_fp); } if (info->local_ip.family != 0) { str_printfa(str, "\tlip=%s", net_ip2addr(&info->local_ip)); event_add_ip(request->event, "local_ip", &info->local_ip); } if (info->remote_ip.family != 0) { str_printfa(str, "\trip=%s", net_ip2addr(&info->remote_ip)); event_add_ip(request->event, "remote_ip", &info->remote_ip); } if (info->local_port != 0) { str_printfa(str, "\tlport=%u", info->local_port); event_add_int(request->event, "local_port", info->local_port); } if (info->remote_port != 0) { str_printfa(str, "\trport=%u", info->remote_port); event_add_int(request->event, "remote_port", info->remote_port); } if (info->ssl_ja3_hash != NULL) { str_append(str, "\tssl_ja3_hash="); str_append_tabescaped(str,info->ssl_ja3_hash); event_add_str(request->event, "ssl_ja3_hash", info->ssl_ja3_hash); } if (info->real_local_ip.family != 0) { event_add_ip(request->event, "real_local_ip", &info->real_local_ip); } if (info->real_remote_ip.family != 0) { event_add_ip(request->event, "real_remote_ip", &info->real_remote_ip); } if (info->real_local_port != 0) { event_add_int(request->event, "real_local_port", info->real_local_port); } if (info->real_remote_port != 0) { event_add_int(request->event, "real_remote_port", info->real_remote_port); } /* send the real_* variants only when they differ from the unreal ones */ if (info->real_local_ip.family != 0 && !net_ip_compare(&info->real_local_ip, &info->local_ip)) { str_printfa(str, "\treal_lip=%s", net_ip2addr(&info->real_local_ip)); } if (info->real_remote_ip.family != 0 && !net_ip_compare(&info->real_remote_ip, &info->remote_ip)) { str_printfa(str, "\treal_rip=%s", net_ip2addr(&info->real_remote_ip)); } if (info->real_local_port != 0 && info->real_local_port != info->local_port) str_printfa(str, "\treal_lport=%u", info->real_local_port); if (info->real_remote_port != 0 && info->real_remote_port != info->remote_port) str_printfa(str, "\treal_rport=%u", info->real_remote_port); if (info->local_name != NULL && *info->local_name != '\0') { str_append(str, "\tlocal_name="); str_append_tabescaped(str, info->local_name); event_add_str(request->event, "local_name", info->local_name); } if (info->ssl_cipher_bits != 0 && info->ssl_cipher != NULL) { event_add_str(request->event, "tls_cipher", info->ssl_cipher); event_add_int(request->event, "tls_cipher_bits", info->ssl_cipher_bits); if (info->ssl_pfs != NULL) { event_add_str(request->event, "tls_pfs", info->ssl_pfs); } } if (info->ssl_protocol != NULL) { event_add_str(request->event, "tls_protocol", info->ssl_protocol); } if (info->client_id != NULL && *info->client_id != '\0') { str_append(str, "\tclient_id="); str_append_tabescaped(str, info->client_id); event_add_str(request->event, "client_id", info->client_id); } if (info->forward_fields != NULL && info->forward_fields[0] != NULL) { string_t *forward = t_str_new(64); str_append_tabescaped(forward, info->forward_fields[0]); for (unsigned int i = 1; info->forward_fields[i] != NULL; i++) { str_append_c(forward, '\t'); str_append_tabescaped(forward, info->forward_fields[i]); } str_append(str, "\tforward_fields="); str_append_tabescaped(str, str_c(forward)); } if (array_is_created(&info->extra_fields)) { const char *const *fieldp; array_foreach(&info->extra_fields, fieldp) { str_append_c(str, '\t'); str_append_tabescaped(str, *fieldp); } } if (info->initial_resp_base64 != NULL) { str_append(str, "\tresp="); str_append_tabescaped(str, info->initial_resp_base64); } str_append_c(str, '\n'); struct event_passthrough *e = event_create_passthrough(request->event)-> set_name("auth_client_request_started"); e_debug(e->event(), "Started request"); if (!conn->connected) { e_error(request->event, "Error sending request to auth server: connection lost"); /* try to reconnect */ request->to_fail = timeout_add_short( 0, auth_client_request_fail_conn_lost, request); } else if (o_stream_send(conn->conn.output, str_data(str), str_len(str)) < 0) { e_error(request->event, "Error sending request to auth server: %m"); } else { request->sent = TRUE; } } struct auth_client_request * auth_client_request_new(struct auth_client *client, const struct auth_request_info *request_info, auth_request_callback_t *callback, void *context) { struct auth_client_request *request; pool_t pool; pool = pool_alloconly_create("auth client request", 512); request = p_new(pool, struct auth_client_request, 1); request->pool = pool; request->conn = client->conn; request->flags = request_info->flags; request->final_status = AUTH_REQUEST_STATUS_CONTINUE; request->callback = callback; request->context = context; request->id = auth_client_connection_add_request(request->conn, request); request->created = ioloop_time; request->event = event_create(client->event); event_add_int(request->event, "id", request->id); event_set_append_log_prefix(request->event, t_strdup_printf("request [%u]: ", request->id)); T_BEGIN { auth_server_send_new_request(request->conn, request, request_info); } T_END; return request; } void auth_client_request_enable_channel_binding( struct auth_client_request *request, auth_channel_binding_callback_t *callback, void *context) { request->cbinding_callback = callback; request->cbinding_context = context; } static void call_callback(struct auth_client_request *request, enum auth_request_status status, const char *log_error, const char *data_base64, const char *const *args) { static const char *const temp_failure_args[] = { "code="AUTH_CLIENT_FAIL_CODE_TEMPFAIL, NULL }; auth_request_callback_t *callback = request->callback; if (status != AUTH_REQUEST_STATUS_CONTINUE) { request->callback = NULL; if (args == NULL && status != AUTH_REQUEST_STATUS_OK) args = temp_failure_args; } callback(request, status, log_error, data_base64, args, request->context); } static void auth_client_request_fail_conn_lost(struct auth_client_request *request) { struct auth_client_connection *conn = request->conn; timeout_remove(&request->to_fail); struct event_passthrough *e = event_create_passthrough(request->event)-> set_name("auth_client_request_finished"); e->add_str("error", "Lost connection to server"); e_debug(e->event(), "Lost connection to server"); call_callback(request, AUTH_REQUEST_STATUS_INTERNAL_FAIL, "Lost connection to server", NULL, NULL); conn->to = timeout_add_short(0, auth_server_reconnect_timeout, conn); } static void auth_client_request_free(struct auth_client_request **_request) { struct auth_client_request *request = *_request; *_request = NULL; if (!auth_client_connection_remove_request(request->conn, request)) { /* Auth request was already sent to the server. Delay freeing it until we get a response or we get disconnected. */ return; } timeout_remove(&request->to_fail); timeout_remove(&request->to_final); event_unref(&request->event); pool_unref(&request->pool); } void auth_client_request_abort(struct auth_client_request **_request, const char *reason) { struct auth_client_request *request = *_request; *_request = NULL; struct event_passthrough *e = event_create_passthrough(request->event)-> set_name("auth_client_request_finished"); e->add_str("error", reason); e_debug(e->event(), "Aborted: %s", reason); auth_client_send_cancel(request->conn->client, request->id); call_callback(request, AUTH_REQUEST_STATUS_ABORT, "Aborted", NULL, NULL); auth_client_request_free(&request); } static void auth_client_request_fail(struct auth_client_request **_request, enum auth_request_status status, const char *reason) ATTR_NULL(3) { struct auth_client_request *request = *_request; const char *args[2] = { NULL, NULL }; *_request = NULL; i_assert(status == AUTH_REQUEST_STATUS_FAIL || status == AUTH_REQUEST_STATUS_INTERNAL_FAIL); struct event_passthrough *e = event_create_passthrough(request->event)-> set_name("auth_client_request_finished"); e->add_str("error", reason); e_debug(e->event(), "Failed: %s", reason); args[0] = t_strconcat("reason=", reason, NULL); auth_client_send_cancel(request->conn->client, request->id); call_callback(request, status, reason, NULL, args); auth_client_request_free(&request); } unsigned int auth_client_request_get_id(struct auth_client_request *request) { return request->id; } unsigned int auth_client_request_get_server_pid(struct auth_client_request *request) { return request->conn->server_pid; } const char *auth_client_request_get_cookie(struct auth_client_request *request) { return request->conn->cookie; } bool auth_client_request_is_aborted(struct auth_client_request *request) { return request->callback == NULL; } time_t auth_client_request_get_create_time(struct auth_client_request *request) { return request->created; } static void args_parse_user(struct auth_client_request *request, const char *key, const char *value) { if (strcmp(key, "user") == 0) event_add_str(request->event, "user", value); else if (strcmp(key, "original_user") == 0) event_add_str(request->event, "original_user", value); else if (strcmp(key, "auth_user") == 0) event_add_str(request->event, "auth_user", value); } static void auth_client_request_final(struct auth_client_request *request) { timeout_remove(&request->to_final); i_assert(request->final_status != AUTH_REQUEST_STATUS_CONTINUE); auth_client_request_handle_input(&request, request->final_status, NULL, request->final_args, FALSE); } void auth_client_request_continue(struct auth_client_request *request, const char *data_base64) { struct const_iovec iov[4]; const char *prefix, *cbinding = ""; if (request->final_status != AUTH_REQUEST_STATUS_CONTINUE) { request->to_final = timeout_add_short( 0, auth_client_request_final, request); return; } if (!request->conn->connected) { e_error(request->event, "Error sending continue request to auth server: " "connection lost"); return; } prefix = t_strdup_printf("CONT\t%u\t", request->id); if (request->cbinding_data != NULL) { const buffer_t *cbdata = request->cbinding_data; string_t *cbdata_b64 = t_base64_encode(0, 0, cbdata->data, cbdata->used); cbinding = t_strconcat("\tchannel_binding=", str_c(cbdata_b64), NULL); } iov[0].iov_base = prefix; iov[0].iov_len = strlen(prefix); if (data_base64 == NULL) { /* Send out-of-band response */ iov[1].iov_base = "#"; iov[1].iov_len = 1; } else { /* Send normal SASL response */ iov[1].iov_base = data_base64; iov[1].iov_len = strlen(data_base64); } iov[2].iov_base = cbinding; iov[2].iov_len = strlen(cbinding); iov[3].iov_base = "\n"; iov[3].iov_len = 1; struct event_passthrough *e = event_create_passthrough(request->event)-> set_name("auth_client_request_continued"); e_debug(e->event(), "Continue request"); if (o_stream_sendv(request->conn->conn.output, iov, 4) < 0) { e_error(request->event, "Error sending continue request to auth server: %m"); } request->cbinding_data = NULL; } static void auth_client_request_handle_input(struct auth_client_request **_request, enum auth_request_status status, const char *base64_data, const char *const *args, bool final) { struct auth_client_request *request = *_request; const char *const *tmp; const char *cbinding_type = NULL; struct event_passthrough *e; if (auth_client_request_is_aborted(request)) { /* aborted already */ auth_client_request_free(_request); return; } if (status == AUTH_REQUEST_STATUS_CONTINUE) { for (tmp = args; tmp != NULL && *tmp != NULL; tmp++) { if (str_begins(*tmp, "channel_binding=", &cbinding_type)) break; } args = NULL; } if (cbinding_type != NULL) { const buffer_t *data; const char *error; if (request->cbinding_callback == NULL) { auth_client_request_fail( &request, AUTH_REQUEST_STATUS_INTERNAL_FAIL, "Channel binding not supported"); return; } if (request->cbinding_callback(cbinding_type, request->cbinding_context, &data, &error) < 0) { auth_client_request_fail( &request, AUTH_REQUEST_STATUS_FAIL, t_strdup_printf("Channel binding failed: %s", error)); return; } request->cbinding_data = buffer_create_dynamic(request->pool, data->used); buffer_append_buf(request->cbinding_data, data, 0, SIZE_MAX); } const char *reason = NULL; switch (status) { case AUTH_REQUEST_STATUS_CONTINUE: e = event_create_passthrough(request->event)-> set_name("auth_client_request_challenged"); break; default: e = event_create_passthrough(request->event)-> set_name("auth_client_request_finished"); for (tmp = args; tmp != NULL && *tmp != NULL; tmp++) { const char *key; const char *value; t_split_key_value_eq(*tmp, &key, &value); if (str_begins(key, "event_", &key)) event_add_str(request->event, key, value); else if (strcmp(key, "reason") == 0) reason = value; else args_parse_user(request, key, value); } break; } const char *log_error = NULL; switch (status) { case AUTH_REQUEST_STATUS_OK: e_debug(e->event(), "Finished"); break; case AUTH_REQUEST_STATUS_CONTINUE: if (base64_data == NULL) { /* Received a challenge outside the normal SASL interaction. This is used to obtain out-of-band data such as the channel binding when the auth service messages exchanged as part of the normal SASL interaction don't provide a means to piggy-back the data, such as when channel binding data is needed for composing the first server SASL challenge. Therefore, we reply to the challenge immediately without doing the SASL callback. The SASL response is empty, but any requested fields are included. */ e_debug(e->event(), "Got out-of-band challenge"); i_assert(!final); auth_client_request_continue(request, NULL); return; } if (!final) e_debug(e->event(), "Got challenge"); else e_debug(e->event(), "Created final challenge"); break; case AUTH_REQUEST_STATUS_FAIL: log_error = reason != NULL && reason[0] != '\0' ? reason : "Authentication failed"; e->add_str("error", log_error); e_debug(e->event(), "Finished"); break; case AUTH_REQUEST_STATUS_INTERNAL_FAIL: log_error = "Internal failure"; e->add_str("error", log_error); e_debug(e->event(), "Finished"); break; case AUTH_REQUEST_STATUS_ABORT: i_unreached(); } call_callback(request, status, log_error, base64_data, args); if (status != AUTH_REQUEST_STATUS_CONTINUE) auth_client_request_free(_request); } void auth_client_request_server_input(struct auth_client_request **_request, enum auth_request_status status, const char *const *args) { struct auth_client_request *request = *_request; const char *const *tmp, *base64_data = NULL; bool final = FALSE; if (auth_client_request_is_aborted(request)) { /* aborted already */ auth_client_request_free(_request); return; } switch (status) { case AUTH_REQUEST_STATUS_FAIL: case AUTH_REQUEST_STATUS_OK: case AUTH_REQUEST_STATUS_INTERNAL_FAIL: for (tmp = args; *tmp != NULL; tmp++) { const char *key; const char *value; t_split_key_value_eq(*tmp, &key, &value); if (strcmp(key, "resp") == 0) base64_data = value; } if (base64_data == NULL || (status == AUTH_REQUEST_STATUS_OK && HAS_ALL_BITS(request->flags, AUTH_REQUEST_FLAG_SUPPORT_FINAL_RESP))) break; request->final_status = status; request->final_args = p_strarray_dup(request->pool, args); status = AUTH_REQUEST_STATUS_CONTINUE; args = NULL; final = TRUE; break; case AUTH_REQUEST_STATUS_CONTINUE: base64_data = args[0]; if (strcmp(base64_data, "#") == 0) { /* Out-of-band challenge */ base64_data = NULL; } args++; break; case AUTH_REQUEST_STATUS_ABORT: i_unreached(); } auth_client_request_handle_input(_request, status, base64_data, args, final); } void auth_client_send_cancel(struct auth_client *client, unsigned int id) { if (!client->conn->connected) { e_error(client->conn->conn.event, "Error sending cancel request to auth server: " "connection lost"); return; } const char *str = t_strdup_printf("CANCEL\t%u\n", id); if (o_stream_send_str(client->conn->conn.output, str) < 0) { e_error(client->conn->conn.event, "Error sending cancel request to auth server: %m"); } }