Mercurial > dovecot > core-2.2
view src/auth/mech.c @ 2377:8f5be0be3199 HEAD
NTLM authentication. Patch by Andrey Panin
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Wed, 28 Jul 2004 18:39:29 +0300 |
parents | 13ed27a24f46 |
children | d47a550fc23b |
line wrap: on
line source
/* Copyright (C) 2002 Timo Sirainen */ #include "common.h" #include "ioloop.h" #include "buffer.h" #include "hash.h" #include "mech.h" #include "str.h" #include "var-expand.h" #include "auth-client-connection.h" #include "auth-master-connection.h" #include <stdlib.h> struct mech_module_list *mech_modules; const char *const *auth_realms; const char *default_realm; const char *anonymous_username; char username_chars[256]; static int set_use_cyrus_sasl; static int ssl_require_client_cert; static struct auth_client_request_reply failure_reply; static buffer_t *auth_failures_buf; static struct timeout *to_auth_failures; void mech_register_module(struct mech_module *module) { struct mech_module_list *list; list = i_new(struct mech_module_list, 1); list->module = *module; list->next = mech_modules; mech_modules = list; } void mech_unregister_module(struct mech_module *module) { struct mech_module_list **pos, *list; for (pos = &mech_modules; *pos != NULL; pos = &(*pos)->next) { if (strcmp((*pos)->module.mech_name, module->mech_name) == 0) { list = *pos; *pos = (*pos)->next; i_free(list); break; } } } const string_t *auth_mechanisms_get_list(void) { struct mech_module_list *list; string_t *str; str = t_str_new(128); for (list = mech_modules; list != NULL; list = list->next) str_append(str, list->module.mech_name); return str; } static struct mech_module *mech_module_find(const char *name) { struct mech_module_list *list; for (list = mech_modules; list != NULL; list = list->next) { if (strcasecmp(list->module.mech_name, name) == 0) return &list->module; } return NULL; } void mech_request_new(struct auth_client_connection *conn, struct auth_client_request_new *request, const unsigned char *data, mech_callback_t *callback) { struct mech_module *mech; struct auth_request *auth_request; size_t ip_size = 1; if (request->ip_family == AF_INET) ip_size = 4; else if (request->ip_family != 0) ip_size = sizeof(auth_request->local_ip.ip); else ip_size = 0; /* make sure data is NUL-terminated */ if (request->data_size <= ip_size*2 || request->initial_resp_idx == 0 || request->mech_idx >= request->data_size || request->protocol_idx >= request->data_size || request->initial_resp_idx > request->data_size || data[request->initial_resp_idx-1] != '\0') { i_error("BUG: Auth client %u sent corrupted request", conn->pid); failure_reply.id = request->id; callback(&failure_reply, NULL, conn); return; } mech = mech_module_find((const char *)data + request->mech_idx); if (mech == NULL) { /* unsupported mechanism */ i_error("BUG: Auth client %u requested unsupported " "auth mechanism %s", conn->pid, (const char *)data + request->mech_idx); failure_reply.id = request->id; callback(&failure_reply, NULL, conn); return; } #ifdef USE_CYRUS_SASL2 if (set_use_cyrus_sasl) auth_request = mech_cyrus_sasl_new(conn, request, callback); else #endif auth_request = mech->auth_new(); if (auth_request == NULL) return; auth_request->created = ioloop_time; auth_request->conn = conn; auth_request->id = request->id; auth_request->protocol = p_strdup(auth_request->pool, (const char *)data + request->protocol_idx); if (request->ip_family != 0) { auth_request->local_ip.family = request->ip_family; auth_request->remote_ip.family = request->ip_family; memcpy(&auth_request->local_ip.ip, data, ip_size); memcpy(&auth_request->remote_ip.ip, data + ip_size, ip_size); } if (ssl_require_client_cert && (request->flags & AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT) == 0) { /* we fail without valid certificate */ if (verbose) { i_info("ssl-cert-check(%s): " "Client didn't present valid SSL certificate", get_log_prefix(auth_request)); } auth_request_unref(auth_request); failure_reply.id = request->id; callback(&failure_reply, NULL, conn); return; } hash_insert(conn->auth_requests, POINTER_CAST(request->id), auth_request); if (!auth_request->auth_initial(auth_request, request, data, callback)) mech_request_free(auth_request, request->id); } void mech_request_continue(struct auth_client_connection *conn, struct auth_client_request_continue *request, const unsigned char *data, mech_callback_t *callback) { struct auth_request *auth_request; auth_request = hash_lookup(conn->auth_requests, POINTER_CAST(request->id)); if (auth_request == NULL) { /* timeouted */ failure_reply.id = request->id; callback(&failure_reply, NULL, conn); } else { if (!auth_request->auth_continue(auth_request, data, request->data_size, callback)) mech_request_free(auth_request, request->id); } } void mech_request_free(struct auth_request *auth_request, unsigned int id) { if (auth_request->conn != NULL) { hash_remove(auth_request->conn->auth_requests, POINTER_CAST(id)); } auth_request_unref(auth_request); } void mech_init_auth_client_reply(struct auth_client_request_reply *reply) { memset(reply, 0, sizeof(*reply)); reply->username_idx = (size_t)-1; reply->reply_idx = (size_t)-1; } void *mech_auth_success(struct auth_client_request_reply *reply, struct auth_request *auth_request, const void *data, size_t data_size) { buffer_t *buf; buf = buffer_create_dynamic(pool_datastack_create(), 256, (size_t)-1); reply->username_idx = 0; buffer_append(buf, auth_request->user, strlen(auth_request->user)+1); if (data_size == 0) reply->reply_idx = (size_t)-1; else { reply->reply_idx = buffer_get_used_size(buf); buffer_append(buf, data, data_size); } reply->result = AUTH_CLIENT_RESULT_SUCCESS; reply->data_size = buffer_get_used_size(buf); return buffer_get_modifyable_data(buf, NULL); } void mech_auth_finish(struct auth_request *auth_request, const void *data, size_t data_size, int success) { struct auth_client_request_reply reply; void *reply_data; if (!success) { /* failure. don't announce it immediately to avoid a) timing attacks, b) flooding */ buffer_append(auth_failures_buf, &auth_request, sizeof(auth_request)); return; } memset(&reply, 0, sizeof(reply)); reply.id = auth_request->id; reply.result = AUTH_CLIENT_RESULT_SUCCESS; reply_data = mech_auth_success(&reply, auth_request, data, data_size); auth_request->callback(&reply, reply_data, auth_request->conn); if (AUTH_MASTER_IS_DUMMY(auth_request->conn->master)) { /* we don't have master process, the request is no longer needed */ mech_request_free(auth_request, auth_request->id); } } int mech_is_valid_username(const char *username) { const unsigned char *p; for (p = (const unsigned char *)username; *p != '\0'; p++) { if (username_chars[*p & 0xff] == 0) return FALSE; } return TRUE; } void auth_request_ref(struct auth_request *request) { request->refcount++; } int auth_request_unref(struct auth_request *request) { if (--request->refcount > 0) return TRUE; request->auth_free(request); return FALSE; } static const char *escape_none(const char *str) { return str; } const struct var_expand_table * auth_request_get_var_expand_table(const struct auth_request *auth_request, const char *(*escape_func)(const char *)) { static struct var_expand_table static_tab[] = { { 'u', NULL }, { 'n', NULL }, { 'd', NULL }, { 'p', NULL }, { 'h', NULL }, { 'l', NULL }, { 'r', NULL }, { 'P', NULL }, { '\0', NULL } }; struct var_expand_table *tab; if (escape_func == NULL) escape_func = escape_none; tab = t_malloc(sizeof(static_tab)); memcpy(tab, static_tab, sizeof(static_tab)); tab[0].value = escape_func(auth_request->user); tab[1].value = escape_func(t_strcut(auth_request->user, '@')); tab[2].value = strchr(auth_request->user, '@'); if (tab[2].value != NULL) tab[2].value = escape_func(tab[2].value+1); tab[3].value = auth_request->protocol; /* tab[4] = we have no home dir */ if (auth_request->local_ip.family != 0) tab[5].value = net_ip2addr(&auth_request->local_ip); if (auth_request->remote_ip.family != 0) tab[6].value = net_ip2addr(&auth_request->remote_ip); tab[7].value = dec2str(auth_request->conn->pid); return tab; } const char *get_log_prefix(const struct auth_request *auth_request) { #define MAX_LOG_USERNAME_LEN 64 const char *p, *ip; string_t *str; str = t_str_new(64); if (auth_request->user == NULL) str_append(str, "?"); else { /* any control characters in username will be replaced by '?' */ for (p = auth_request->user; *p != '\0'; p++) { if ((unsigned char)*p < 32) break; } str_append_n(str, auth_request->user, (size_t)(p - auth_request->user)); for (; *p != '\0'; p++) { if ((unsigned char)*p < 32) str_append_c(str, '?'); else str_append_c(str, *p); } if (str_len(str) > MAX_LOG_USERNAME_LEN) { str_truncate(str, MAX_LOG_USERNAME_LEN); str_append(str, "..."); } } ip = net_ip2addr(&auth_request->remote_ip); if (ip != NULL) { str_append_c(str, ','); str_append(str, ip); } return str_c(str); } void auth_failure_buf_flush(void) { struct auth_request **auth_request; struct auth_client_request_reply reply; size_t i, size; auth_request = buffer_get_modifyable_data(auth_failures_buf, &size); size /= sizeof(*auth_request); memset(&reply, 0, sizeof(reply)); reply.result = AUTH_CLIENT_RESULT_FAILURE; for (i = 0; i < size; i++) { reply.id = auth_request[i]->id; auth_request[i]->callback(&reply, NULL, auth_request[i]->conn); mech_request_free(auth_request[i], auth_request[i]->id); } buffer_set_used_size(auth_failures_buf, 0); } static void auth_failure_timeout(void *context __attr_unused__) { auth_failure_buf_flush(); } extern struct mech_module mech_plain; extern struct mech_module mech_login; extern struct mech_module mech_apop; extern struct mech_module mech_cram_md5; extern struct mech_module mech_digest_md5; extern struct mech_module mech_ntlm; extern struct mech_module mech_anonymous; void mech_init(void) { const char *const *mechanisms; const char *env; mech_modules = NULL; memset(&failure_reply, 0, sizeof(failure_reply)); failure_reply.result = AUTH_CLIENT_RESULT_FAILURE; anonymous_username = getenv("ANONYMOUS_USERNAME"); if (anonymous_username != NULL && *anonymous_username == '\0') anonymous_username = NULL; /* register wanted mechanisms */ env = getenv("MECHANISMS"); if (env == NULL || *env == '\0') i_fatal("MECHANISMS environment is unset"); mechanisms = t_strsplit_spaces(env, " "); while (*mechanisms != NULL) { if (strcasecmp(*mechanisms, "PLAIN") == 0) mech_register_module(&mech_plain); else if (strcasecmp(*mechanisms, "LOGIN") == 0) mech_register_module(&mech_login); else if (strcasecmp(*mechanisms, "APOP") == 0) mech_register_module(&mech_apop); else if (strcasecmp(*mechanisms, "CRAM-MD5") == 0) mech_register_module(&mech_cram_md5); else if (strcasecmp(*mechanisms, "DIGEST-MD5") == 0) mech_register_module(&mech_digest_md5); else if (strcasecmp(*mechanisms, "NTLM") == 0) mech_register_module(&mech_ntlm); else if (strcasecmp(*mechanisms, "ANONYMOUS") == 0) { if (anonymous_username == NULL) { i_fatal("ANONYMOUS listed in mechanisms, " "but anonymous_username not given"); } mech_register_module(&mech_anonymous); } else { i_fatal("Unknown authentication mechanism '%s'", *mechanisms); } mechanisms++; } if (mech_modules == NULL) i_fatal("No authentication mechanisms configured"); /* get our realm - note that we allocate from data stack so this function should never be called inside I/O loop or anywhere else where t_pop() is called */ env = getenv("REALMS"); if (env == NULL) env = ""; auth_realms = t_strsplit_spaces(env, " "); default_realm = getenv("DEFAULT_REALM"); if (default_realm != NULL && *default_realm == '\0') default_realm = NULL; env = getenv("USERNAME_CHARS"); if (env == NULL || *env == '\0') { /* all chars are allowed */ memset(username_chars, 0xff, sizeof(username_chars)); } else { memset(username_chars, 0, sizeof(username_chars)); for (; *env != '\0'; env++) username_chars[((unsigned char)*env) & 0xff] = 0xff; } set_use_cyrus_sasl = getenv("USE_CYRUS_SASL") != NULL; #ifdef USE_CYRUS_SASL2 if (set_use_cyrus_sasl) mech_cyrus_sasl_init_lib(); #endif ssl_require_client_cert = getenv("SSL_REQUIRE_CLIENT_CERT") != NULL; auth_failures_buf = buffer_create_dynamic(default_pool, 1024, (size_t)-1); to_auth_failures = timeout_add(2000, auth_failure_timeout, NULL); } void mech_deinit(void) { timeout_remove(to_auth_failures); mech_unregister_module(&mech_plain); mech_unregister_module(&mech_login); mech_unregister_module(&mech_apop); mech_unregister_module(&mech_cram_md5); mech_unregister_module(&mech_digest_md5); mech_unregister_module(&mech_ntlm); mech_unregister_module(&mech_anonymous); }