diff options
Diffstat (limited to 'src/auth/auth-request-fields.c')
-rw-r--r-- | src/auth/auth-request-fields.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/src/auth/auth-request-fields.c b/src/auth/auth-request-fields.c new file mode 100644 index 0000000..590e671 --- /dev/null +++ b/src/auth/auth-request-fields.c @@ -0,0 +1,525 @@ +/* Copyright (c) 2002-2020 Dovecot authors, see the included COPYING file */ + +#define AUTH_REQUEST_FIELDS_CONST + +#include "auth-common.h" +#include "str.h" +#include "strescape.h" +#include "str-sanitize.h" +#include "auth-request.h" +#include "userdb-template.h" + +void auth_request_fields_init(struct auth_request *request) +{ + request->fields.extra_fields = auth_fields_init(request->pool); + if (request->mech != NULL) { + request->fields.mech_name = request->mech->mech_name; + event_add_str(request->event, "mechanism", + request->mech->mech_name); + } + /* Default to "insecure" until it's changed later */ + event_add_str(request->event, "transport", "insecure"); +} + +static void +auth_str_add_keyvalue(string_t *dest, const char *key, const char *value) +{ + str_append_c(dest, '\t'); + str_append(dest, key); + if (value != NULL) { + str_append_c(dest, '='); + str_append_tabescaped(dest, value); + } +} + +static void +auth_request_export_fields(string_t *dest, struct auth_fields *auth_fields, + const char *prefix) +{ + const ARRAY_TYPE(auth_field) *fields = auth_fields_export(auth_fields); + const struct auth_field *field; + + array_foreach(fields, field) { + str_printfa(dest, "\t%s%s", prefix, field->key); + if (field->value != NULL) { + str_append_c(dest, '='); + str_append_tabescaped(dest, field->value); + } + } +} + +void auth_request_export(struct auth_request *request, string_t *dest) +{ + const struct auth_request_fields *fields = &request->fields; + + str_append(dest, "user="); + str_append_tabescaped(dest, fields->user); + + auth_str_add_keyvalue(dest, "service", fields->service); + + if (fields->master_user != NULL) + auth_str_add_keyvalue(dest, "master-user", fields->master_user); + auth_str_add_keyvalue(dest, "original-username", + fields->original_username); + if (fields->requested_login_user != NULL) { + auth_str_add_keyvalue(dest, "requested-login-user", + fields->requested_login_user); + } + + if (fields->local_ip.family != 0) { + auth_str_add_keyvalue(dest, "lip", + net_ip2addr(&fields->local_ip)); + } + if (fields->remote_ip.family != 0) { + auth_str_add_keyvalue(dest, "rip", + net_ip2addr(&fields->remote_ip)); + } + if (fields->local_port != 0) + str_printfa(dest, "\tlport=%u", fields->local_port); + if (fields->remote_port != 0) + str_printfa(dest, "\trport=%u", fields->remote_port); + if (fields->real_local_ip.family != 0) { + auth_str_add_keyvalue(dest, "real_lip", + net_ip2addr(&fields->real_local_ip)); + } + if (fields->real_remote_ip.family != 0) { + auth_str_add_keyvalue(dest, "real_rip", + net_ip2addr(&fields->real_remote_ip)); + } + if (fields->real_local_port != 0) + str_printfa(dest, "\treal_lport=%u", fields->real_local_port); + if (fields->real_remote_port != 0) + str_printfa(dest, "\treal_rport=%u", fields->real_remote_port); + if (fields->local_name != 0) { + str_append(dest, "\tlocal_name="); + str_append_tabescaped(dest, fields->local_name); + } + if (fields->session_id != NULL) { + str_append(dest, "\tsession="); + str_append_tabescaped(dest, fields->session_id); + } + if (event_want_debug(request->event)) + str_append(dest, "\tdebug"); + switch (fields->secured) { + case AUTH_REQUEST_SECURED_NONE: break; + case AUTH_REQUEST_SECURED: str_append(dest, "\tsecured"); break; + case AUTH_REQUEST_SECURED_TLS: str_append(dest, "\tsecured=tls"); break; + default: break; + } + if (fields->skip_password_check) + str_append(dest, "\tskip-password-check"); + if (fields->delayed_credentials != NULL) + str_append(dest, "\tdelayed-credentials"); + if (fields->valid_client_cert) + str_append(dest, "\tvalid-client-cert"); + if (fields->no_penalty) + str_append(dest, "\tno-penalty"); + if (fields->successful) + str_append(dest, "\tsuccessful"); + if (fields->mech_name != NULL) + auth_str_add_keyvalue(dest, "mech", fields->mech_name); + if (fields->client_id != NULL) + auth_str_add_keyvalue(dest, "client_id", fields->client_id); + /* export passdb extra fields */ + auth_request_export_fields(dest, fields->extra_fields, "passdb_"); + /* export any userdb fields */ + if (fields->userdb_reply != NULL) + auth_request_export_fields(dest, fields->userdb_reply, "userdb_"); +} + +bool auth_request_import_info(struct auth_request *request, + const char *key, const char *value) +{ + struct auth_request_fields *fields = &request->fields; + struct event *event = request->event; + + i_assert(value != NULL); + + /* authentication and user lookups may set these */ + if (strcmp(key, "service") == 0) { + fields->service = p_strdup(request->pool, value); + event_add_str(event, "service", value); + } else if (strcmp(key, "lip") == 0) { + if (net_addr2ip(value, &fields->local_ip) < 0) + return TRUE; + event_add_str(event, "local_ip", value); + if (fields->real_local_ip.family == 0) + auth_request_import_info(request, "real_lip", value); + } else if (strcmp(key, "rip") == 0) { + if (net_addr2ip(value, &fields->remote_ip) < 0) + return TRUE; + event_add_str(event, "remote_ip", value); + if (fields->real_remote_ip.family == 0) + auth_request_import_info(request, "real_rip", value); + } else if (strcmp(key, "lport") == 0) { + if (net_str2port(value, &fields->local_port) < 0) + return TRUE; + event_add_int(event, "local_port", fields->local_port); + if (fields->real_local_port == 0) + auth_request_import_info(request, "real_lport", value); + } else if (strcmp(key, "rport") == 0) { + if (net_str2port(value, &fields->remote_port) < 0) + return TRUE; + event_add_int(event, "remote_port", fields->remote_port); + if (fields->real_remote_port == 0) + auth_request_import_info(request, "real_rport", value); + } else if (strcmp(key, "real_lip") == 0) { + if (net_addr2ip(value, &fields->real_local_ip) == 0) + event_add_str(event, "real_local_ip", value); + } else if (strcmp(key, "real_rip") == 0) { + if (net_addr2ip(value, &fields->real_remote_ip) == 0) + event_add_str(event, "real_remote_ip", value); + } else if (strcmp(key, "real_lport") == 0) { + if (net_str2port(value, &fields->real_local_port) == 0) + event_add_int(event, "real_local_port", + fields->real_local_port); + } else if (strcmp(key, "real_rport") == 0) { + if (net_str2port(value, &fields->real_remote_port) == 0) + event_add_int(event, "real_remote_port", + fields->real_remote_port); + } else if (strcmp(key, "local_name") == 0) { + fields->local_name = p_strdup(request->pool, value); + event_add_str(event, "local_name", value); + } else if (strcmp(key, "session") == 0) { + fields->session_id = p_strdup(request->pool, value); + event_add_str(event, "session", value); + } else if (strcmp(key, "debug") == 0) + event_set_forced_debug(request->event, TRUE); + else if (strcmp(key, "client_id") == 0) { + fields->client_id = p_strdup(request->pool, value); + event_add_str(event, "client_id", value); + } else if (strcmp(key, "forward_fields") == 0) { + auth_fields_import_prefixed(fields->extra_fields, + "forward_", value, 0); + /* make sure the forward_ fields aren't deleted by + auth_fields_rollback() if the first passdb lookup fails. */ + auth_fields_snapshot(fields->extra_fields); + } else + return FALSE; + /* NOTE: keep in sync with auth_request_export() */ + return TRUE; +} + +bool auth_request_import_auth(struct auth_request *request, + const char *key, const char *value) +{ + struct auth_request_fields *fields = &request->fields; + + i_assert(value != NULL); + + if (auth_request_import_info(request, key, value)) + return TRUE; + + /* auth client may set these */ + if (strcmp(key, "secured") == 0) { + if (strcmp(value, "tls") == 0) { + fields->secured = AUTH_REQUEST_SECURED_TLS; + event_add_str(request->event, "transport", "TLS"); + } else { + fields->secured = AUTH_REQUEST_SECURED; + event_add_str(request->event, "transport", "trusted"); + } + } + else if (strcmp(key, "final-resp-ok") == 0) + fields->final_resp_ok = TRUE; + else if (strcmp(key, "no-penalty") == 0) + fields->no_penalty = TRUE; + else if (strcmp(key, "valid-client-cert") == 0) + fields->valid_client_cert = TRUE; + else if (strcmp(key, "cert_username") == 0) { + if (request->set->ssl_username_from_cert && *value != '\0') { + /* get username from SSL certificate. it overrides + the username given by the auth mechanism. */ + auth_request_set_username_forced(request, value); + fields->cert_username = TRUE; + } + } else { + return FALSE; + } + return TRUE; +} + +bool auth_request_import(struct auth_request *request, + const char *key, const char *value) +{ + struct auth_request_fields *fields = &request->fields; + + i_assert(value != NULL); + + if (auth_request_import_auth(request, key, value)) + return TRUE; + + /* for communication between auth master and worker processes */ + if (strcmp(key, "user") == 0) + auth_request_set_username_forced(request, value); + else if (strcmp(key, "master-user") == 0) { + fields->master_user = p_strdup(request->pool, value); + event_add_str(request->event, "master_user", value); + } else if (strcmp(key, "original-username") == 0) { + fields->original_username = p_strdup(request->pool, value); + event_add_str(request->event, "original_user", value); + } else if (strcmp(key, "requested-login-user") == 0) + auth_request_set_login_username_forced(request, value); + else if (strcmp(key, "successful") == 0) + auth_request_set_auth_successful(request); + else if (strcmp(key, "skip-password-check") == 0) + auth_request_set_password_verified(request); + else if (strcmp(key, "delayed-credentials") == 0) { + /* just make passdb_handle_credentials() work identically in + auth-worker as it does in auth-master. the worker shouldn't + care about the actual contents of the credentials. */ + fields->delayed_credentials = &uchar_nul; + fields->delayed_credentials_size = 1; + } else if (strcmp(key, "mech") == 0) { + fields->mech_name = p_strdup(request->pool, value); + event_add_str(request->event, "mechanism", value); + } else if (str_begins(key, "passdb_")) + auth_fields_add(fields->extra_fields, key+7, value, 0); + else if (str_begins(key, "userdb_")) { + if (fields->userdb_reply == NULL) + auth_request_init_userdb_reply(request, FALSE); + auth_fields_add(fields->userdb_reply, key+7, value, 0); + } else + return FALSE; + + return TRUE; +} + +static int +auth_request_fix_username(struct auth_request *request, const char **username, + const char **error_r) +{ + const struct auth_settings *set = request->set; + unsigned char *p; + char *user; + + if (*set->default_realm != '\0' && + strchr(*username, '@') == NULL) { + user = p_strconcat(unsafe_data_stack_pool, *username, "@", + set->default_realm, NULL); + } else { + user = t_strdup_noconst(*username); + } + + for (p = (unsigned char *)user; *p != '\0'; p++) { + if (set->username_translation_map[*p & 0xff] != 0) + *p = set->username_translation_map[*p & 0xff]; + if (set->username_chars_map[*p & 0xff] == 0) { + *error_r = t_strdup_printf( + "Username character disallowed by auth_username_chars: " + "0x%02x (username: %s)", *p, + str_sanitize(*username, 128)); + return -1; + } + } + + if (*set->username_format != '\0') { + /* username format given, put it through variable expansion. + we'll have to temporarily replace request->user to get + %u to be the wanted username */ + const char *error; + string_t *dest; + + dest = t_str_new(256); + unsigned int count = 0; + const struct var_expand_table *table = + auth_request_get_var_expand_table_full(request, + user, NULL, &count); + if (auth_request_var_expand_with_table(dest, + set->username_format, request, + table, NULL, &error) <= 0) { + *error_r = t_strdup_printf( + "Failed to expand username_format=%s: %s", + set->username_format, error); + } + user = str_c_modifiable(dest); + } + + if (user[0] == '\0') { + /* Some PAM plugins go nuts with empty usernames */ + *error_r = "Empty username"; + return -1; + } + *username = user; + return 0; +} + +bool auth_request_set_username(struct auth_request *request, + const char *username, const char **error_r) +{ + const struct auth_settings *set = request->set; + const char *p, *login_username = NULL; + + if (*set->master_user_separator != '\0' && !request->userdb_lookup) { + /* check if the username contains a master user */ + p = strchr(username, *set->master_user_separator); + if (p != NULL) { + /* it does, set it. */ + login_username = t_strdup_until(username, p); + + /* username is the master user */ + username = p + 1; + } + } + + if (request->fields.original_username == NULL) { + /* the username may change later, but we need to use this + username when verifying at least DIGEST-MD5 password. */ + request->fields.original_username = + p_strdup(request->pool, username); + event_add_str(request->event, "original_user", + request->fields.original_username); + } + if (request->fields.cert_username) { + /* cert_username overrides the username given by + authentication mechanism. but still do checks and + translations to it. */ + username = request->fields.user; + } + + if (auth_request_fix_username(request, &username, error_r) < 0) { + request->fields.user = NULL; + event_field_clear(request->event, "user"); + return FALSE; + } + auth_request_set_username_forced(request, username); + if (request->fields.translated_username == NULL) { + /* similar to original_username, but after translations */ + request->fields.translated_username = request->fields.user; + event_add_str(request->event, "translated_user", + request->fields.translated_username); + } + request->user_changed_by_lookup = TRUE; + + if (login_username != NULL) { + if (!auth_request_set_login_username(request, + login_username, + error_r)) + return FALSE; + } + return TRUE; +} + +void auth_request_set_username_forced(struct auth_request *request, + const char *username) +{ + i_assert(username != NULL); + + request->fields.user = p_strdup(request->pool, username); + event_add_str(request->event, "user", request->fields.user); +} + +void auth_request_set_login_username_forced(struct auth_request *request, + const char *username) +{ + i_assert(username != NULL); + + request->fields.requested_login_user = + p_strdup(request->pool, username); + event_add_str(request->event, "login_user", + request->fields.requested_login_user); +} + +bool auth_request_set_login_username(struct auth_request *request, + const char *username, + const char **error_r) +{ + struct auth_passdb *master_passdb; + + if (username[0] == '\0') { + *error_r = "Master user login attempted to use empty login username"; + return FALSE; + } + + if (strcmp(username, request->fields.user) == 0) { + /* The usernames are the same, we don't really wish to log + in as someone else */ + return TRUE; + } + + /* lookup request->user from masterdb first */ + master_passdb = auth_request_get_auth(request)->masterdbs; + if (master_passdb == NULL) { + *error_r = "Master user login attempted without master passdbs"; + return FALSE; + } + request->passdb = master_passdb; + + if (auth_request_fix_username(request, &username, error_r) < 0) { + request->fields.requested_login_user = NULL; + event_field_clear(request->event, "login_user"); + return FALSE; + } + auth_request_set_login_username_forced(request, username); + + e_debug(request->event, + "%sMaster user lookup for login: %s", + auth_request_get_log_prefix_db(request), + request->fields.requested_login_user); + return TRUE; +} + +void auth_request_master_user_login_finish(struct auth_request *request) +{ + if (request->failed) + return; + + /* master login successful. update user and master_user variables. */ + e_info(authdb_event(request), + "Master user logging in as %s", + request->fields.requested_login_user); + + request->fields.master_user = request->fields.user; + event_add_str(request->event, "master_user", + request->fields.master_user); + + auth_request_set_username_forced(request, + request->fields.requested_login_user); + request->fields.translated_username = request->fields.requested_login_user; + event_add_str(request->event, "translated_user", + request->fields.translated_username); + request->fields.requested_login_user = NULL; + event_field_clear(request->event, "login_user"); +} + +void auth_request_set_realm(struct auth_request *request, const char *realm) +{ + i_assert(realm != NULL); + + request->fields.realm = p_strdup(request->pool, realm); + event_add_str(request->event, "realm", request->fields.realm); +} + +void auth_request_set_auth_successful(struct auth_request *request) +{ + request->fields.successful = TRUE; +} + +void auth_request_set_password_verified(struct auth_request *request) +{ + request->fields.skip_password_check = TRUE; +} + +void auth_request_init_userdb_reply(struct auth_request *request, + bool add_default_fields) +{ + const char *error; + + request->fields.userdb_reply = auth_fields_init(request->pool); + if (add_default_fields) { + if (userdb_template_export(request->userdb->default_fields_tmpl, + request, &error) < 0) { + e_error(authdb_event(request), + "Failed to expand default_fields: %s", error); + } + } +} + +void auth_request_set_delayed_credentials(struct auth_request *request, + const unsigned char *credentials, + size_t size) +{ + request->fields.delayed_credentials = + p_memdup(request->pool, credentials, size); + request->fields.delayed_credentials_size = size; +} |