From: Stephan Bosch Date: Fri, 14 Sep 2012 18:48:45 +0000 (+0300) Subject: Added support to perform token-based service process authentication. X-Git-Tag: 2.2.alpha1~209 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ab90f702ceedb7ba445a9a592be0b213b27cbafa;p=thirdparty%2Fdovecot%2Fcore.git Added support to perform token-based service process authentication. Creates hidden SASL method DOVECOT-TOKEN. This method is not available on the normal auth login socket and thus never presented to clients. Creates new auth socket type 'tokenlogin'. This otherwise normal login socket only offers authentication using the DOVECOT-TOKEN mechanism. Creates new token-login directory in base_dir to separate token logins from normal logins. This directory is otherwise completely identical to the normal login dir, i.e. it contains sockets for the service backends, used to chroot login processes to, etc. Makes default login socket configurable. Performs some minor changes to src/login-common to build very sparse protocols, e.g. avoid the need to implement methods that are not needed. --- diff --git a/src/auth/Makefile.am b/src/auth/Makefile.am index 5e6efab95a..e2e338ae4a 100644 --- a/src/auth/Makefile.am +++ b/src/auth/Makefile.am @@ -71,6 +71,7 @@ auth_SOURCES = \ auth-request-handler.c \ auth-settings.c \ auth-stream.c \ + auth-token.c \ auth-worker-client.c \ auth-worker-server.c \ db-checkpassword.c \ @@ -93,6 +94,7 @@ auth_SOURCES = \ mech-rpa.c \ mech-apop.c \ mech-winbind.c \ + mech-dovecot-token.c \ passdb.c \ passdb-blocking.c \ passdb-bsdauth.c \ @@ -136,6 +138,7 @@ headers = \ auth-request-handler.h \ auth-settings.h \ auth-stream.h \ + auth-token.h \ auth-worker-client.h \ auth-worker-server.h \ db-dict.h \ diff --git a/src/auth/auth-client-connection.c b/src/auth/auth-client-connection.c index 6a9b524077..138ae6f33b 100644 --- a/src/auth/auth-client-connection.c +++ b/src/auth/auth-client-connection.c @@ -125,7 +125,7 @@ auth_client_input_cpid(struct auth_client_connection *conn, const char *args) /* handshake complete, we can now actually start serving requests */ conn->refcount++; conn->request_handler = - auth_request_handler_create(auth_callback, conn, + auth_request_handler_create(conn->token_auth, auth_callback, conn, !conn->login_requests ? NULL : auth_master_request_callback); auth_request_handler_set(conn->request_handler, conn->connect_uid, pid); @@ -294,10 +294,11 @@ static void auth_client_input(struct auth_client_connection *conn) } void auth_client_connection_create(struct auth *auth, int fd, - bool login_requests) + bool login_requests, bool token_auth) { static unsigned int connect_uid_counter = 0; struct auth_client_connection *conn; + const char *mechanisms; string_t *str; conn = i_new(struct auth_client_connection, 1); @@ -305,6 +306,7 @@ void auth_client_connection_create(struct auth *auth, int fd, conn->refcount = 1; conn->connect_uid = ++connect_uid_counter; conn->login_requests = login_requests; + conn->token_auth = token_auth; random_fill(conn->cookie, sizeof(conn->cookie)); conn->fd = fd; @@ -317,11 +319,18 @@ void auth_client_connection_create(struct auth *auth, int fd, DLLIST_PREPEND(&auth_client_connections, conn); + if (token_auth) { + mechanisms = t_strconcat("MECH\t", + mech_dovecot_token.mech_name, "\n", NULL); + } else { + mechanisms = str_c(auth->reg->handshake); + } + str = t_str_new(128); str_printfa(str, "VERSION\t%u\t%u\n%sSPID\t%s\nCUID\t%u\nCOOKIE\t", AUTH_CLIENT_PROTOCOL_MAJOR_VERSION, AUTH_CLIENT_PROTOCOL_MINOR_VERSION, - str_c(auth->reg->handshake), my_pid, conn->connect_uid); + mechanisms, my_pid, conn->connect_uid); binary_to_hex_append(str, conn->cookie, sizeof(conn->cookie)); str_append(str, "\nDONE\n"); diff --git a/src/auth/auth-client-connection.h b/src/auth/auth-client-connection.h index e9b66e5bd3..93a906d2ca 100644 --- a/src/auth/auth-client-connection.h +++ b/src/auth/auth-client-connection.h @@ -20,10 +20,11 @@ struct auth_client_connection { unsigned int login_requests:1; unsigned int version_received:1; + unsigned int token_auth:1; }; void auth_client_connection_create(struct auth *auth, int fd, - bool login_requests); + bool login_requests, bool token_auth); void auth_client_connection_destroy(struct auth_client_connection **conn); struct auth_client_connection * diff --git a/src/auth/auth-master-connection.c b/src/auth/auth-master-connection.c index 8f3f100df7..88ef38efa4 100644 --- a/src/auth/auth-master-connection.c +++ b/src/auth/auth-master-connection.c @@ -95,12 +95,12 @@ static bool master_input_request(struct auth_master_connection *conn, const char *args) { struct auth_client_connection *client_conn; - const char *const *list; + const char *const *list, *const *params; unsigned int id, client_pid, client_id; uint8_t cookie[MASTER_AUTH_COOKIE_SIZE]; buffer_t buf; - /* */ + /* [] */ list = t_strsplit_tab(args); if (str_array_length(list) < 4 || str_to_uint(list[0], &id) < 0 || @@ -115,6 +115,7 @@ master_input_request(struct auth_master_connection *conn, const char *args) i_error("BUG: Master sent broken REQUEST cookie"); return FALSE; } + params = list + 4; client_conn = auth_client_connection_lookup(client_pid); if (client_conn == NULL) { @@ -128,7 +129,7 @@ master_input_request(struct auth_master_connection *conn, const char *args) o_stream_nsend_str(conn->output, t_strdup_printf("FAIL\t%u\n", id)); } else if (!auth_request_handler_master_request( - client_conn->request_handler, conn, id, client_id)) { + client_conn->request_handler, conn, id, client_id, params)) { i_error("Master requested auth for non-login client %u", client_pid); o_stream_nsend_str(conn->output, diff --git a/src/auth/auth-request-handler.c b/src/auth/auth-request-handler.c index 0afb875537..4e63e5ae62 100644 --- a/src/auth/auth-request-handler.c +++ b/src/auth/auth-request-handler.c @@ -6,11 +6,13 @@ #include "aqueue.h" #include "base64.h" #include "hash.h" +#include "network.h" #include "str.h" #include "str-sanitize.h" #include "master-interface.h" #include "auth-penalty.h" #include "auth-request.h" +#include "auth-token.h" #include "auth-master-connection.h" #include "auth-request-handler.h" @@ -31,6 +33,7 @@ struct auth_request_handler { auth_request_callback_t *master_callback; unsigned int destroyed:1; + unsigned int token_auth:1; }; static ARRAY(struct auth_request *) auth_failures_arr; @@ -41,8 +44,8 @@ static void auth_failure_timeout(void *context) ATTR_NULL(1); #undef auth_request_handler_create struct auth_request_handler * -auth_request_handler_create(auth_request_callback_t *callback, void *context, - auth_request_callback_t *master_callback) +auth_request_handler_create(bool token_auth, auth_request_callback_t *callback, + void *context, auth_request_callback_t *master_callback) { struct auth_request_handler *handler; pool_t pool; @@ -56,6 +59,7 @@ auth_request_handler_create(auth_request_callback_t *callback, void *context, handler->callback = callback; handler->context = context; handler->master_callback = master_callback; + handler->token_auth = token_auth; return handler; } @@ -461,13 +465,24 @@ bool auth_request_handler_auth_begin(struct auth_request_handler *handler, return FALSE; } - mech = mech_module_find(list[1]); - if (mech == NULL) { - /* unsupported mechanism */ - i_error("BUG: Authentication client %u requested unsupported " - "authentication mechanism %s", handler->client_pid, - str_sanitize(list[1], MAX_MECH_NAME_LEN)); - return FALSE; + if (handler->token_auth) { + mech = &mech_dovecot_token; + if (strcmp(list[1], mech->mech_name) != 0) { + /* unsupported mechanism */ + i_error("BUG: Authentication client %u requested invalid " + "authentication mechanism %s (DOVECOT-TOKEN required)", + handler->client_pid, str_sanitize(list[1], MAX_MECH_NAME_LEN)); + return FALSE; + } + } else { + mech = mech_module_find(list[1]); + if (mech == NULL) { + /* unsupported mechanism */ + i_error("BUG: Authentication client %u requested unsupported " + "authentication mechanism %s", handler->client_pid, + str_sanitize(list[1], MAX_MECH_NAME_LEN)); + return FALSE; + } } request = auth_request_new(mech); @@ -664,8 +679,19 @@ static void userdb_callback(enum userdb_result result, auth_stream_reply_add(request->userdb_reply, "anonymous", NULL); } + auth_stream_reply_import(reply, auth_stream_reply_export(request->userdb_reply)); + + /* generate auth_token when master service provided session_pid */ + if (request->session_pid != (pid_t)-1) { + const char *auth_token = + auth_token_get(request->service, + dec2str(request->session_pid), + request->user, + request->session_id); + auth_stream_reply_add(reply, "auth_token", auth_token); + } break; } handler->master_callback(reply, request->master); @@ -675,13 +701,27 @@ static void userdb_callback(enum userdb_result result, auth_request_handler_unref(&handler); } +static bool +auth_master_request_failed(struct auth_request_handler *handler, + struct auth_master_connection *master, + struct auth_stream_reply *reply, unsigned int id) +{ + auth_stream_reply_add(reply, "FAIL", NULL); + auth_stream_reply_add(reply, NULL, dec2str(id)); + if (handler->master_callback == NULL) + return FALSE; + handler->master_callback(reply, master); + return TRUE; +} + bool auth_request_handler_master_request(struct auth_request_handler *handler, struct auth_master_connection *master, - unsigned int id, - unsigned int client_id) + unsigned int id, unsigned int client_id, + const char *const *params) { struct auth_request *request; struct auth_stream_reply *reply; + struct net_unix_cred cred; reply = auth_stream_reply_init(pool_datastack_create()); @@ -689,17 +729,38 @@ bool auth_request_handler_master_request(struct auth_request_handler *handler, if (request == NULL) { i_error("Master request %u.%u not found", handler->client_pid, client_id); - auth_stream_reply_add(reply, "FAIL", NULL); - auth_stream_reply_add(reply, NULL, dec2str(id)); - if (handler->master_callback == NULL) - return FALSE; - handler->master_callback(reply, master); - return TRUE; + return auth_master_request_failed(handler, master, reply, id); } auth_request_ref(request); auth_request_handler_remove(handler, request); + for (; *params != NULL; params++) { + const char *name, *param = strchr(*params, '='); + + if (param == NULL) { + name = *params; + param = ""; + } else { + name = t_strdup_until(*params, param); + param++; + } + + (void)auth_request_import_master(request, name, param); + } + + /* verify session pid if specified and possible */ + if (request->session_pid != (pid_t)-1 && + net_getunixcred(master->fd, &cred) == 0 && + cred.pid != (pid_t)-1 && request->session_pid != cred.pid) { + i_error("Session pid %ld provided by master for request %u.%u " + "did not match peer credentials (pid=%ld, uid=%ld)", + (long)request->session_pid, + handler->client_pid, client_id, + (long)cred.pid, (long)cred.uid); + return auth_master_request_failed(handler, master, reply, id); + } + if (request->state != AUTH_REQUEST_STATE_FINISHED || !request->successful) { i_error("Master requested unfinished authentication request " diff --git a/src/auth/auth-request-handler.h b/src/auth/auth-request-handler.h index 08e60f5d29..7fcadb1955 100644 --- a/src/auth/auth-request-handler.h +++ b/src/auth/auth-request-handler.h @@ -15,10 +15,10 @@ typedef void auth_request_callback_t(struct auth_stream_reply *reply, void *context); struct auth_request_handler * -auth_request_handler_create(auth_request_callback_t *callback, void *context, - auth_request_callback_t *master_callback); -#define auth_request_handler_create(callback, context, master_callback)\ - auth_request_handler_create( \ +auth_request_handler_create(bool token_auth, auth_request_callback_t *callback, + void *context, auth_request_callback_t *master_callback); +#define auth_request_handler_create(token_auth, callback, context, master_callback)\ + auth_request_handler_create(token_auth, \ (auth_request_callback_t *)callback, \ (void *)((char*)context + \ CALLBACK_TYPECHECK(callback, void (*)( \ @@ -46,8 +46,8 @@ unsigned int auth_request_handler_get_request_count(struct auth_request_handler *handler); bool auth_request_handler_master_request(struct auth_request_handler *handler, struct auth_master_connection *master, - unsigned int id, - unsigned int client_id); + unsigned int id, unsigned int client_id, + const char *const *params); void auth_request_handler_cancel_request(struct auth_request_handler *handler, unsigned int client_id); diff --git a/src/auth/auth-request.c b/src/auth/auth-request.c index 67f21fe984..be605e89fa 100644 --- a/src/auth/auth-request.c +++ b/src/auth/auth-request.c @@ -56,6 +56,7 @@ auth_request_new(const struct mech_module *mech) request->refcount = 1; request->last_access = ioloop_time; + request->session_pid = (pid_t)-1; request->set = global_auth_settings; request->mech = mech; @@ -77,6 +78,7 @@ struct auth_request *auth_request_new_dummy(void) request->refcount = 1; request->last_access = ioloop_time; + request->session_pid = (pid_t)-1; request->set = global_auth_settings; return request; } @@ -279,6 +281,20 @@ bool auth_request_import_auth(struct auth_request *request, return TRUE; } +bool auth_request_import_master(struct auth_request *request, + const char *key, const char *value) +{ + pid_t pid; + + /* master request lookups may set these */ + if (strcmp(key, "session_pid") == 0) { + if (str_to_pid(value, &pid) == 0) + request->session_pid = pid; + } else + return FALSE; + return TRUE; +} + bool auth_request_import(struct auth_request *request, const char *key, const char *value) { diff --git a/src/auth/auth-request.h b/src/auth/auth-request.h index e95eef1782..dd74b059e3 100644 --- a/src/auth/auth-request.h +++ b/src/auth/auth-request.h @@ -73,6 +73,7 @@ struct auth_request { unsigned int client_pid; unsigned int id; time_t last_access; + pid_t session_pid; const char *service, *mech_name, *session_id; struct ip_addr local_ip, remote_ip; @@ -157,6 +158,8 @@ bool auth_request_import_info(struct auth_request *request, const char *key, const char *value); bool auth_request_import_auth(struct auth_request *request, const char *key, const char *value); +bool auth_request_import_master(struct auth_request *request, + const char *key, const char *value); void auth_request_initial(struct auth_request *request); void auth_request_continue(struct auth_request *request, diff --git a/src/auth/auth-settings.c b/src/auth/auth-settings.c index 1e753c9506..49d3b180e1 100644 --- a/src/auth/auth-settings.c +++ b/src/auth/auth-settings.c @@ -17,6 +17,7 @@ static bool auth_userdb_settings_check(void *_set, pool_t pool, const char **err /* */ static struct file_listener_settings auth_unix_listeners_array[] = { { "login/login", 0666, "", "" }, + { "token-login/token-login", 0666, "", "" }, { "auth-login", 0600, "$default_internal_user", "" }, { "auth-client", 0600, "", "" }, { "auth-userdb", 0666, "$default_internal_user", "" }, @@ -27,7 +28,8 @@ static struct file_listener_settings *auth_unix_listeners[] = { &auth_unix_listeners_array[1], &auth_unix_listeners_array[2], &auth_unix_listeners_array[3], - &auth_unix_listeners_array[4] + &auth_unix_listeners_array[4], + &auth_unix_listeners_array[5] }; static buffer_t auth_unix_listeners_buf = { auth_unix_listeners, sizeof(auth_unix_listeners), { 0, } diff --git a/src/auth/auth-token.c b/src/auth/auth-token.c new file mode 100644 index 0000000000..45f252dd95 --- /dev/null +++ b/src/auth/auth-token.c @@ -0,0 +1,183 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +/* Auth process maintains a random secret. Once a user authenticates the + response to the REQUEST command from a master service is augmented with an + auth_token value. This token is the SHA1 hash of the secret, the service + name and the username of the user that just logged in. Using this token the + service (e.g. imap) can login to another service (e.g. imap-urlauth) to + gain access to resources that require additional privileges (e.g. another + user's e-mail). +*/ + +#include "auth-common.h" +#include "hex-binary.h" +#include "hmac-sha1.h" +#include "randgen.h" +#include "read-full.h" +#include "write-full.h" +#include "safe-memset.h" +#include "auth-settings.h" +#include "auth-token.h" + +#include +#include +#include +#include +#include +#include + +#define AUTH_TOKEN_SECRET_LEN 32 + +#define AUTH_TOKEN_SECRET_FNAME "auth-token-secret.dat" + +static unsigned char auth_token_secret[AUTH_TOKEN_SECRET_LEN]; + +static int +auth_token_read_secret(const char *path, + unsigned char secret_r[AUTH_TOKEN_SECRET_LEN]) +{ + struct stat st, lst; + int fd, ret; + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno != ENOENT) + i_error("open(%s) failed: %m", path); + return -1; + } + + if (fstat(fd, &st) < 0) { + i_error("fstat(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + + /* check secret len and file type */ + if (st.st_size != AUTH_TOKEN_SECRET_LEN || !S_ISREG(st.st_mode)) { + i_error("Corrupted token secret file: %s", path); + i_close_fd(&fd); + if (unlink(path) < 0) + i_error("unlink(%s) failed: %m", path); + return -1; + } + + /* verify that we're not dealing with a symbolic link */ + if (lstat(path, &lst) < 0) { + i_error("lstat(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + + /* check security parameters for compromise */ + if ((st.st_mode & 07777) != 0600 || st.st_uid != 0 || st.st_nlink > 1 || + !S_ISREG(lst.st_mode) || st.st_ino != lst.st_ino || + !CMP_DEV_T(st.st_dev, lst.st_dev)) { + i_error("Compromised token secret file: %s", path); + i_close_fd(&fd); + if (unlink(path) < 0) + i_error("unlink(%s) failed: %m", path); + return -1; + } + + /* FIXME: fail here to generate new secret if stored one is too old */ + + ret = read_full(fd, secret_r, AUTH_TOKEN_SECRET_LEN); + if (ret < 0) + i_error("read(%s) failed: %m", path); + else if (ret == 0) { + i_error("Token secret file unexpectedly shrank: %s", path); + ret = -1; + } + if (close(fd) < 0) + i_error("close(%s) failed: %m", path); + + if (global_auth_settings->debug) + i_debug("Read auth token secret from %s", path); + return ret; +} + +static int +auth_token_write_secret(const char *path, + const unsigned char secret[AUTH_TOKEN_SECRET_LEN]) +{ + const char *temp_path; + mode_t old_mask; + int fd, ret; + + i_assert(getuid() == 0); + + temp_path = t_strconcat(path, ".tmp", NULL); + + old_mask = umask(0); + fd = open(temp_path, O_WRONLY | O_CREAT | O_TRUNC, 0600); + umask(old_mask); + + if (fd == -1) { + i_error("open(%s) failed: %m", temp_path); + return -1; + } + + ret = write_full(fd, secret, AUTH_TOKEN_SECRET_LEN); + if (ret < 0) + i_error("write(%s) failed: %m", temp_path); + if (close(fd) < 0) { + i_error("close(%s) failed: %m", temp_path); + ret = -1; + } + + if (ret < 0) { + if (unlink(temp_path) < 0) + i_error("unlink(%s) failed: %m", temp_path); + return -1; + } + + if (rename(temp_path, path) < 0) { + i_error("rename(%s, %s) failed: %m", temp_path, path); + if (unlink(temp_path) < 0) + i_error("unlink(%s) failed: %m", temp_path); + return -1; + } + + if (global_auth_settings->debug) + i_debug("Wrote new auth token secret to %s", path); + return 0; +} + +void auth_token_init(void) +{ + const char *secret_path = + t_strconcat(global_auth_settings->base_dir, "/", + AUTH_TOKEN_SECRET_FNAME, NULL); + + if (auth_token_read_secret(secret_path, auth_token_secret) < 0) { + random_fill(auth_token_secret, sizeof(auth_token_secret)); + + if (auth_token_write_secret(secret_path, auth_token_secret) < 0) { + i_error("Failed to write auth token secret file; " + "returned tokens will be invalid once auth restarts"); + } + } +} + +void auth_token_deinit(void) +{ + /* not very useful, but we do it anyway */ + safe_memset(auth_token_secret, 0, sizeof(auth_token_secret)); +} + +const char *auth_token_get(const char *service, const char *session_pid, + const char *username, const char *session_id) +{ + struct hmac_sha1_context ctx; + unsigned char result[SHA1_RESULTLEN]; + + hmac_sha1_init(&ctx, username, strlen(username)); + hmac_sha1_update(&ctx, session_pid, strlen(session_pid)); + if (session_id != NULL && *session_id != '\0') + hmac_sha1_update(&ctx, session_id, strlen(session_id)); + hmac_sha1_update(&ctx, service, strlen(service)); + hmac_sha1_update(&ctx, auth_token_secret, sizeof(auth_token_secret)); + hmac_sha1_final(&ctx, result); + + return binary_to_hex(result, sizeof(result)); +} diff --git a/src/auth/auth-token.h b/src/auth/auth-token.h new file mode 100644 index 0000000000..3e427c0288 --- /dev/null +++ b/src/auth/auth-token.h @@ -0,0 +1,11 @@ +#ifndef AUTH_TOKEN_H +#define AUTH_TOKEN_H + +void auth_token_init(void); +void auth_token_deinit(void); + +const char *auth_token_get(const char *service, const char *session_pid, + const char *username, const char *session_id); + +#endif + diff --git a/src/auth/main.c b/src/auth/main.c index fd693c6fef..08e3a7d516 100644 --- a/src/auth/main.c +++ b/src/auth/main.c @@ -22,6 +22,7 @@ #include "mech.h" #include "auth.h" #include "auth-penalty.h" +#include "auth-token.h" #include "auth-request-handler.h" #include "auth-worker-server.h" #include "auth-worker-client.h" @@ -40,7 +41,9 @@ enum auth_socket_type { AUTH_SOCKET_LOGIN_CLIENT, AUTH_SOCKET_MASTER, AUTH_SOCKET_USERDB, - AUTH_SOCKET_POSTFIX + AUTH_SOCKET_POSTFIX, + AUTH_SOCKET_TOKEN, + AUTH_SOCKET_TOKEN_LOGIN }; struct auth_socket_listener { @@ -118,6 +121,10 @@ auth_socket_type_get(const char *path) return AUTH_SOCKET_USERDB; else if (strcmp(suffix, "postmap") == 0) return AUTH_SOCKET_POSTFIX; + else if (strcmp(suffix, "token") == 0) + return AUTH_SOCKET_TOKEN; + else if (strcmp(suffix, "tokenlogin") == 0) + return AUTH_SOCKET_TOKEN_LOGIN; else return AUTH_SOCKET_CLIENT; } @@ -198,6 +205,7 @@ static void main_preinit(void) mech_reg, services); listeners_init(); + auth_token_init(); /* Password lookups etc. may require roots, allow it. */ restrict_access_by_env(NULL, FALSE); @@ -264,6 +272,8 @@ static void main_deinit(void) auths_free(); dict_drivers_unregister_builtin(); + auth_token_deinit(); + auth_client_connections_destroy_all(); auth_master_connections_destroy_all(); auth_postfix_connections_destroy_all(); @@ -329,10 +339,16 @@ static void client_connected(struct master_service_connection *conn) (void)auth_postfix_connection_create(auth, conn->fd); break; case AUTH_SOCKET_LOGIN_CLIENT: - auth_client_connection_create(auth, conn->fd, TRUE); + auth_client_connection_create(auth, conn->fd, TRUE, FALSE); break; case AUTH_SOCKET_CLIENT: - auth_client_connection_create(auth, conn->fd, FALSE); + auth_client_connection_create(auth, conn->fd, FALSE, FALSE); + break; + case AUTH_SOCKET_TOKEN_LOGIN: + auth_client_connection_create(auth, conn->fd, TRUE, TRUE); + break; + case AUTH_SOCKET_TOKEN: + auth_client_connection_create(auth, conn->fd, FALSE, TRUE); break; default: i_unreached(); diff --git a/src/auth/mech-dovecot-token.c b/src/auth/mech-dovecot-token.c new file mode 100644 index 0000000000..2934f8deb1 --- /dev/null +++ b/src/auth/mech-dovecot-token.c @@ -0,0 +1,86 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +/* Used internally by Dovecot processes to authenticate against each others + (e.g. imap to imap-urlauth). See auth-token.c */ + +#include "auth-common.h" +#include "mech.h" +#include "safe-memset.h" +#include "auth-token.h" + +static void +mech_dovecot_token_auth_continue(struct auth_request *request, + const unsigned char *data, size_t data_size) +{ + const char *session_id, *username, *pid, *service, *error; + char *auth_token; + size_t i, len; + int count; + + /* service \0 pid \0 username \0 session_id \0 auth_token */ + service = (const char *) data; + session_id = username = pid = auth_token = NULL; + count = 0; + for (i = 0; i < data_size; i++) { + if (data[i] == '\0') { + count++; i++; + if (count == 1) + pid = (const char *)data + i; + else if (count == 2) + username = (const char *)data + i; + else if (count == 3) + session_id = (const char *)data + i; + else { + len = data_size - i; + auth_token = p_strndup(unsafe_data_stack_pool, + data+i, len); + break; + } + } + } + + if (count != 4) { + /* invalid input */ + auth_request_log_info(request, "dovecot-token", "invalid input"); + auth_request_fail(request); + } else if (!auth_request_set_username(request, username, &error)) { + /* invalid username */ + auth_request_log_info(request, "dovecot-token", "%s", error); + auth_request_fail(request); + } else { + const char *valid_token = + auth_token_get(service, pid, request->user, session_id); + + if (strcmp(auth_token, valid_token) == 0) + auth_request_success(request, NULL, 0); + else + auth_request_fail(request); + } + + /* make sure it's cleared */ + if (auth_token != NULL) + safe_memset(auth_token, 0, strlen(auth_token)); +} + +static struct auth_request *mech_dovecot_token_auth_new(void) +{ + struct auth_request *request; + pool_t pool; + + pool = pool_alloconly_create("dovecot_token_auth_request", 512); + request = p_new(pool, struct auth_request, 1); + request->pool = pool; + return request; +} + +const struct mech_module mech_dovecot_token = { + "DOVECOT-TOKEN", + + .flags = MECH_SEC_PRIVATE, + .passdb_need = MECH_PASSDB_NEED_NOTHING, + + mech_dovecot_token_auth_new, + mech_generic_auth_initial, + mech_dovecot_token_auth_continue, + mech_generic_auth_free +}; diff --git a/src/auth/mech.h b/src/auth/mech.h index ac0bb7faf2..74408caf5d 100644 --- a/src/auth/mech.h +++ b/src/auth/mech.h @@ -55,6 +55,8 @@ struct mechanisms_register { buffer_t *handshake; }; +extern const struct mech_module mech_dovecot_token; + void mech_register_module(const struct mech_module *module); void mech_unregister_module(const struct mech_module *module); const struct mech_module *mech_module_find(const char *name); diff --git a/src/imap/main.c b/src/imap/main.c index 259a062dd5..1e8fe1168d 100644 --- a/src/imap/main.c +++ b/src/imap/main.c @@ -321,6 +321,7 @@ int main(int argc, char *argv[]) memset(&login_set, 0, sizeof(login_set)); login_set.postlogin_timeout_secs = MASTER_POSTLOGIN_TIMEOUT_DEFAULT; + login_set.request_auth_token = TRUE; if (IS_STANDALONE() && getuid() == 0 && net_getpeername(1, NULL, NULL) == 0) { diff --git a/src/lib-master/master-login-auth.c b/src/lib-master/master-login-auth.c index a073ee53d2..4335efcaee 100644 --- a/src/lib-master/master-login-auth.c +++ b/src/lib-master/master-login-auth.c @@ -3,6 +3,7 @@ #include "lib.h" #include "network.h" #include "ioloop.h" +#include "hostpid.h" #include "istream.h" #include "ostream.h" #include "llist.h" @@ -53,6 +54,7 @@ struct master_login_auth { pid_t auth_server_pid; + unsigned int request_auth_token:1; unsigned int version_received:1; unsigned int spid_received:1; }; @@ -60,7 +62,8 @@ struct master_login_auth { static void master_login_auth_set_timeout(struct master_login_auth *auth); static void master_login_auth_check_spids(struct master_login_auth *auth); -struct master_login_auth *master_login_auth_init(const char *auth_socket_path) +struct master_login_auth * +master_login_auth_init(const char *auth_socket_path, bool request_auth_token) { struct master_login_auth *auth; pool_t pool; @@ -69,6 +72,7 @@ struct master_login_auth *master_login_auth_init(const char *auth_socket_path) auth = p_new(pool, struct master_login_auth, 1); auth->pool = pool; auth->auth_socket_path = p_strdup(pool, auth_socket_path); + auth->request_auth_token = request_auth_token; auth->refcount = 1; auth->fd = -1; hash_table_create_direct(&auth->requests, pool, 0); @@ -436,6 +440,8 @@ master_login_auth_send_request(struct master_login_auth *auth, str_printfa(str, "REQUEST\t%u\t%u\t%u\t", req->id, req->client_pid, req->auth_id); binary_to_hex_append(str, req->cookie, sizeof(req->cookie)); + if (auth->request_auth_token) + str_printfa(str, "\tsession_pid=%s", my_pid); str_append_c(str, '\n'); o_stream_nsend(auth->output, str_data(str), str_len(str)); } diff --git a/src/lib-master/master-login-auth.h b/src/lib-master/master-login-auth.h index 96aba28cb3..a823869529 100644 --- a/src/lib-master/master-login-auth.h +++ b/src/lib-master/master-login-auth.h @@ -7,7 +7,8 @@ typedef void master_login_auth_request_callback_t(const char *const *auth_args, const char *errormsg, void *context); -struct master_login_auth *master_login_auth_init(const char *auth_socket_path); +struct master_login_auth * +master_login_auth_init(const char *auth_socket_path, bool request_auth_token); void master_login_auth_deinit(struct master_login_auth **auth); void master_login_auth_disconnect(struct master_login_auth *auth); diff --git a/src/lib-master/master-login.c b/src/lib-master/master-login.c index 2eaaf35d33..feca9b7532 100644 --- a/src/lib-master/master-login.c +++ b/src/lib-master/master-login.c @@ -70,7 +70,8 @@ master_login_init(struct master_service *service, login->service = service; login->callback = set->callback; login->failure_callback = set->failure_callback; - login->auth = master_login_auth_init(set->auth_socket_path); + login->auth = master_login_auth_init(set->auth_socket_path, + set->request_auth_token); login->postlogin_socket_path = i_strdup(set->postlogin_socket_path); login->postlogin_timeout_secs = set->postlogin_timeout_secs; diff --git a/src/lib-master/master-login.h b/src/lib-master/master-login.h index 01e03f031a..4bd667729f 100644 --- a/src/lib-master/master-login.h +++ b/src/lib-master/master-login.h @@ -30,6 +30,8 @@ struct master_login_settings { master_login_callback_t *callback; master_login_failure_callback_t *failure_callback; + + unsigned int request_auth_token:1; }; struct master_login * diff --git a/src/lib-storage/mail-storage-service.c b/src/lib-storage/mail-storage-service.c index 0c13b40f5a..cd4096a72f 100644 --- a/src/lib-storage/mail-storage-service.c +++ b/src/lib-storage/mail-storage-service.c @@ -71,7 +71,7 @@ struct mail_storage_service_user { enum mail_storage_service_flags flags; struct ioloop_context *ioloop_ctx; - const char *log_prefix; + const char *log_prefix, *auth_token; const char *system_groups_user, *uid_source, *gid_source; const struct mail_user_settings *user_set; @@ -271,6 +271,8 @@ user_reply_handle(struct mail_storage_service_ctx *ctx, i_error("setpriority(%d) failed: %m", n); } #endif + } else if (strncmp(line, "auth_token=", 11) == 0) { + user->auth_token = p_strdup(user->pool, line+11); } else T_BEGIN { ret = set_line(ctx, user, line); } T_END; @@ -605,6 +607,7 @@ mail_storage_service_init_post(struct mail_storage_service_ctx *ctx, mail_user->uid = priv->uid == (uid_t)-1 ? geteuid() : priv->uid; mail_user->gid = priv->gid == (gid_t)-1 ? getegid() : priv->gid; mail_user->anonymous = user->anonymous; + mail_user->auth_token = p_strdup(mail_user->pool, user->auth_token); mail_set = mail_user_set_get_storage_set(mail_user); diff --git a/src/lib-storage/mail-user.h b/src/lib-storage/mail-user.h index 10018b365b..8d8a501bac 100644 --- a/src/lib-storage/mail-user.h +++ b/src/lib-storage/mail-user.h @@ -23,6 +23,8 @@ struct mail_user { gid_t gid; const char *service; struct ip_addr *local_ip, *remote_ip; + const char *auth_token; + const struct var_expand_table *var_expand_table; /* If non-NULL, fail the user initialization with this error. This could be set by plugins that need to fail the initialization. */ diff --git a/src/login-common/client-common-auth.c b/src/login-common/client-common-auth.c index 1ec9a5b8d3..6a1618f134 100644 --- a/src/login-common/client-common-auth.c +++ b/src/login-common/client-common-auth.c @@ -473,6 +473,7 @@ void client_auth_parse_response(struct client *client) static void client_auth_input(struct client *client) { + i_assert(client->v.auth_parse_response != NULL); client->v.auth_parse_response(client); } @@ -559,6 +560,7 @@ sasl_callback(struct client *client, enum sasl_server_reply sasl_reply, client_destroy_success(client, data); break; case SASL_SERVER_REPLY_CONTINUE: + i_assert(client->v.auth_send_challenge != NULL); client->v.auth_send_challenge(client, data); if (client->to_auth_waiting != NULL) diff --git a/src/login-common/client-common.c b/src/login-common/client-common.c index 5a87d72558..c423a97410 100644 --- a/src/login-common/client-common.c +++ b/src/login-common/client-common.c @@ -211,7 +211,8 @@ void client_destroy(struct client *client, const char *reason) if (client->login_proxy != NULL) login_proxy_free(&client->login_proxy); - client->v.destroy(client); + if (client->v.destroy != NULL) + client->v.destroy(client); if (client_unref(&client) && initial_service_count == 1) { /* as soon as this connection is done with proxying (or whatever), the process will die. there's no need for @@ -672,7 +673,8 @@ void client_notify_disconnect(struct client *client, const char *text) { if (!client->notified_disconnect) { - client->v.notify_disconnect(client, reason, text); + if (client->v.notify_disconnect != NULL) + client->v.notify_disconnect(client, reason, text); client->notified_disconnect = TRUE; } } @@ -680,7 +682,8 @@ void client_notify_disconnect(struct client *client, void client_notify_auth_ready(struct client *client) { if (!client->notified_auth_ready) { - client->v.notify_auth_ready(client); + if (client->v.notify_auth_ready != NULL) + client->v.notify_auth_ready(client); client->notified_auth_ready = TRUE; } } diff --git a/src/login-common/login-common.h b/src/login-common/login-common.h index 361d6cbdc0..42e3d67edd 100644 --- a/src/login-common/login-common.h +++ b/src/login-common/login-common.h @@ -12,6 +12,9 @@ #define AUTH_PLAINTEXT_DISABLED_MSG \ "Plaintext authentication disallowed on non-secure (SSL/TLS) connections." +#define LOGIN_DEFAULT_SOCKET "login" +#define LOGIN_TOKEN_DEFAULT_SOCKET "token-login" + struct login_binary { /* e.g. imap, pop3 */ const char *protocol; @@ -23,6 +26,9 @@ struct login_binary { /* e.g. 993, 995. if there is no ssl port, use 0. */ unsigned int default_ssl_port; + /* if value is NULL, LOGIN_DEFAULT_SOCKET is used as the default */ + const char *default_login_socket; + const struct client_vfuncs *client_vfuncs; void (*preinit)(void); void (*init)(void); diff --git a/src/login-common/main.c b/src/login-common/main.c index 28172eddc1..8a842621ba 100644 --- a/src/login-common/main.c +++ b/src/login-common/main.c @@ -21,7 +21,6 @@ #include #include -#define DEFAULT_LOGIN_SOCKET "login" #define AUTH_CLIENT_IDLE_TIMEOUT_MSECS (1000*60) struct login_access_lookup { @@ -368,10 +367,12 @@ int login_binary_run(const struct login_binary *binary, MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE; pool_t set_pool; bool allow_core_dumps = FALSE; - const char *login_socket = DEFAULT_LOGIN_SOCKET; + const char *login_socket; int c; login_binary = binary; + login_socket = binary->default_login_socket != NULL ? + binary->default_login_socket : LOGIN_DEFAULT_SOCKET; master_service = master_service_init(login_binary->process_name, service_flags, &argc, &argv, diff --git a/src/master/master-settings.c b/src/master/master-settings.c index e503427f6f..5fe25325fa 100644 --- a/src/master/master-settings.c +++ b/src/master/master-settings.c @@ -715,11 +715,36 @@ static void unlink_sockets(const char *path, const char *prefix) (void)closedir(dirp); } +static void +mkdir_login_dir(const struct master_settings *set, const char *login_dir) +{ + mode_t mode; + gid_t gid; + + if (settings_have_auth_unix_listeners_in(set, login_dir)) { + /* we are not using external authentication, so make sure the + login directory exists with correct permissions and it's + empty. with external auth we wouldn't want to delete + existing sockets or break the permissions required by the + auth server. */ + mode = login_want_core_dumps(set, &gid) ? 0770 : 0750; + if (safe_mkdir(login_dir, mode, master_uid, gid) == 0) { + i_warning("Corrected permissions for login directory " + "%s", login_dir); + } + + unlink_sockets(login_dir, ""); + } else { + /* still make sure that login directory exists */ + if (mkdir(login_dir, 0755) < 0 && errno != EEXIST) + i_fatal("mkdir(%s) failed: %m", login_dir); + } +} + void master_settings_do_fixes(const struct master_settings *set) { - const char *login_dir, *empty_dir; + const char *empty_dir; struct stat st; - gid_t gid; /* since base dir is under /var/run by default, it may have been deleted. */ @@ -741,25 +766,8 @@ void master_settings_do_fixes(const struct master_settings *set) if (mkdir_parents(set->state_dir, 0755) < 0 && errno != EEXIST) i_fatal("mkdir(%s) failed: %m", set->state_dir); - login_dir = t_strconcat(set->base_dir, "/login", NULL); - if (settings_have_auth_unix_listeners_in(set, login_dir)) { - /* we are not using external authentication, so make sure the - login directory exists with correct permissions and it's - empty. with external auth we wouldn't want to delete - existing sockets or break the permissions required by the - auth server. */ - mode_t mode = login_want_core_dumps(set, &gid) ? 0770 : 0750; - if (safe_mkdir(login_dir, mode, master_uid, gid) == 0) { - i_warning("Corrected permissions for login directory " - "%s", login_dir); - } - - unlink_sockets(login_dir, ""); - } else { - /* still make sure that login directory exists */ - if (mkdir(login_dir, 0755) < 0 && errno != EEXIST) - i_fatal("mkdir(%s) failed: %m", login_dir); - } + mkdir_login_dir(set, t_strconcat(set->base_dir, "/login", NULL)); + mkdir_login_dir(set, t_strconcat(set->base_dir, "/token-login", NULL)); empty_dir = t_strconcat(set->base_dir, "/empty", NULL); if (safe_mkdir(empty_dir, 0755, master_uid, getegid()) == 0) {