Mercurial > dovecot > original-hg > dovecot-1.2
view src/auth/passdb-checkpassword.c @ 6429:65c69a53a7be HEAD
Replaced my Copyright notices. The year range always ends with 2007 now.
My name was replaced with "Dovecot authors". In many cases I didn't really
even own the copyright, so this is more correct.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sun, 16 Sep 2007 14:34:22 +0300 |
parents | 047d0d8bbf0a |
children | 7ed926ed7aa4 |
line wrap: on
line source
/* Copyright (c) 2004-2007 Dovecot authors, see the included COPYING file */ #include "common.h" #ifdef PASSDB_CHECKPASSWORD #include "lib-signals.h" #include "buffer.h" #include "str.h" #include "ioloop.h" #include "hash.h" #include "env-util.h" #include "passdb.h" #include "safe-memset.h" #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> struct checkpassword_passdb_module { struct passdb_module module; const char *checkpassword_path, *checkpassword_reply_path; struct hash_table *clients; }; struct chkpw_auth_request { int fd_out, fd_in; struct io *io_out, *io_in; pid_t pid; string_t *input_buf; char *password; unsigned int write_pos; struct auth_request *request; verify_plain_callback_t *callback; int exit_status; unsigned int exited:1; }; static void checkpassword_request_close(struct chkpw_auth_request *request) { if (request->io_in != NULL) io_remove(&request->io_in); if (request->io_out != NULL) io_remove(&request->io_out); if (request->fd_in != -1) { if (close(request->fd_in) < 0) i_error("checkpassword: close() failed: %m"); request->fd_in = -1; } if (request->fd_out != -1) { if (close(request->fd_out) < 0) i_error("checkpassword: close() failed: %m"); } } static void checkpassword_request_finish(struct chkpw_auth_request *request, enum passdb_result result) { struct passdb_module *_module = request->request->passdb->passdb; struct checkpassword_passdb_module *module = (struct checkpassword_passdb_module *)_module; hash_remove(module->clients, POINTER_CAST(request->pid)); if (result == PASSDB_RESULT_OK) { if (strchr(str_c(request->input_buf), '\n') != NULL) { auth_request_log_error(request->request, "checkpassword", "LF characters in checkpassword reply"); result = PASSDB_RESULT_INTERNAL_FAILURE; } else { auth_request_set_fields(request->request, t_strsplit(str_c(request->input_buf), "\t"), NULL); } } request->callback(result, request->request); auth_request_unref(&request->request); checkpassword_request_close(request); if (request->input_buf != NULL) str_free(&request->input_buf); safe_memset(request->password, 0, strlen(request->password)); i_free(request->password); i_free(request); } static void checkpassword_request_half_finish(struct chkpw_auth_request *request) { if (!request->exited || request->fd_in != -1) return; switch (request->exit_status) { /* vpopmail exit codes: */ case 3: /* password fail / vpopmail user not found */ case 12: /* null user name given */ case 13: /* null password given */ case 15: /* user has no password */ case 20: /* invalid user/domain characters */ case 21: /* system user not found */ case 22: /* system user shadow entry not found */ case 23: /* system password fail */ /* standard checkpassword exit codes: */ case 1: /* (1 is additionally defined in vpopmail for "pop/smtp/webmal/ imap/access denied") */ auth_request_log_info(request->request, "checkpassword", "Login failed (status=%d)", request->exit_status); checkpassword_request_finish(request, PASSDB_RESULT_PASSWORD_MISMATCH); break; case 0: if (request->input_buf != NULL) { checkpassword_request_finish(request, PASSDB_RESULT_OK); break; } /* missing input - fall through */ case 2: /* checkpassword is called with wrong parameters? unlikely */ case 111: /* temporary problem, treat as internal error */ default: /* whatever error.. */ auth_request_log_error(request->request, "checkpassword", "Child %s exited with status %d", dec2str(request->pid), request->exit_status); checkpassword_request_finish(request, PASSDB_RESULT_INTERNAL_FAILURE); break; } } static void sigchld_handler(int signo ATTR_UNUSED, void *context) { struct checkpassword_passdb_module *module = context; struct chkpw_auth_request *request; int status; pid_t pid; /* FIXME: if we ever do some other kind of forking, this needs fixing */ while ((pid = waitpid(-1, &status, WNOHANG)) != 0) { if (pid == -1) { if (errno != ECHILD && errno != EINTR) i_error("waitpid() failed: %m"); return; } request = hash_lookup(module->clients, POINTER_CAST(pid)); if (request == NULL) { /* unknown child finished */ if (WIFSIGNALED(status)) { i_error("checkpassword: Unknown child %s died " "with signal %d", dec2str(pid), WTERMSIG(status)); } continue; } if (WIFSIGNALED(status)) { i_error("checkpassword: Child %s died with signal %d", dec2str(pid), WTERMSIG(status)); } else if (WIFEXITED(status)) { request->exited = TRUE; request->exit_status = WEXITSTATUS(status); auth_request_log_debug(request->request, "checkpassword", "exit_status=%d", request->exit_status); checkpassword_request_half_finish(request); request = NULL; } else { /* shouldn't happen */ auth_request_log_debug(request->request, "checkpassword", "Child exited with status=%d", status); } if (request != NULL) { checkpassword_request_finish(request, PASSDB_RESULT_INTERNAL_FAILURE); } } } static void env_put_extra_fields(const char *extra_fields) { const char *const *tmp; const char *key, *p; for (tmp = t_strsplit(extra_fields, "\t"); *tmp != NULL; tmp++) { key = t_str_ucase(t_strcut(*tmp, '=')); p = strchr(*tmp, '='); if (p == NULL) env_put(t_strconcat(key, "=1", NULL)); else env_put(t_strconcat(key, p, NULL)); } } static void ATTR_NORETURN checkpassword_verify_plain_child(struct auth_request *request, struct checkpassword_passdb_module *module, int fd_in, int fd_out) { const char *cmd, *const *args; if (dup2(fd_out, 3) < 0 || dup2(fd_in, 4) < 0) { auth_request_log_error(request, "checkpassword", "dup2() failed: %m"); } else { /* Besides passing the standard username and password in a pipe, also pass some other possibly interesting information via environment. Use UCSPI names for local/remote IPs. */ env_put("PROTO=TCP"); /* UCSPI */ env_put(t_strconcat("SERVICE=", request->service, NULL)); if (request->local_ip.family != 0) { env_put(t_strconcat("TCPLOCALIP=", net_ip2addr(&request->local_ip), NULL)); /* FIXME: for backwards compatibility only, remove some day */ env_put(t_strconcat("LOCAL_IP=", net_ip2addr(&request->local_ip), NULL)); } if (request->remote_ip.family != 0) { env_put(t_strconcat("TCPREMOTEIP=", net_ip2addr(&request->remote_ip), NULL)); /* FIXME: for backwards compatibility only, remove some day */ env_put(t_strconcat("REMOTE_IP=", net_ip2addr(&request->remote_ip), NULL)); } if (request->master_user != NULL) { env_put(t_strconcat("MASTER_USER=", request->master_user, NULL)); } if (request->extra_fields != NULL) { const char *fields = auth_stream_reply_export(request->extra_fields); /* extra fields could come from master db */ env_put_extra_fields(fields); } /* very simple argument splitting. */ cmd = t_strconcat(module->checkpassword_path, " ", module->checkpassword_reply_path, NULL); auth_request_log_debug(request, "checkpassword", "execute: %s", cmd); args = t_strsplit(cmd, " "); execv(args[0], (char **)args); auth_request_log_error(request, "checkpassword", "execv(%s) failed: %m", args[0]); } exit(2); } static void checkpassword_child_input(struct chkpw_auth_request *request) { unsigned char buf[1024]; ssize_t ret; ret = read(request->fd_in, buf, sizeof(buf)); if (ret <= 0) { if (ret < 0) { auth_request_log_error(request->request, "checkpassword", "read() failed: %m"); } auth_request_log_debug(request->request, "checkpassword", "Received no input"); checkpassword_request_close(request); checkpassword_request_half_finish(request); } else { if (request->input_buf == NULL) request->input_buf = str_new(default_pool, 512); str_append_n(request->input_buf, buf, ret); auth_request_log_debug(request->request, "checkpassword", "Received input: %s", str_c(request->input_buf)); } } static void checkpassword_child_output(struct chkpw_auth_request *request) { /* Send: username \0 password \0 timestamp \0. Must be 512 bytes or less. The "timestamp" parameter is actually useful only for APOP authentication. We don't support it, so keep it empty */ struct auth_request *auth_request = request->request; buffer_t *buf; const unsigned char *data; size_t size; ssize_t ret; buf = buffer_create_dynamic(pool_datastack_create(), 512+1); buffer_append(buf, auth_request->user, strlen(auth_request->user)+1); buffer_append(buf, request->password, strlen(request->password)+1); buffer_append_c(buf, '\0'); data = buffer_get_data(buf, &size); if (size > 512) { auth_request_log_error(request->request, "checkpassword", "output larger than 512 bytes: %"PRIuSIZE_T, size); checkpassword_request_finish(request, PASSDB_RESULT_INTERNAL_FAILURE); return; } ret = write(request->fd_out, data + request->write_pos, size - request->write_pos); if (ret <= 0) { if (ret < 0) { auth_request_log_error(request->request, "checkpassword", "write() failed: %m"); } checkpassword_request_finish(request, PASSDB_RESULT_INTERNAL_FAILURE); return; } request->write_pos += ret; if (request->write_pos < size) return; io_remove(&request->io_out); if (close(request->fd_out) < 0) i_error("checkpassword: close() failed: %m"); request->fd_out = -1; } static void checkpassword_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct passdb_module *_module = request->passdb->passdb; struct checkpassword_passdb_module *module = (struct checkpassword_passdb_module *)_module; struct chkpw_auth_request *chkpw_auth_request; int fd_in[2], fd_out[2]; pid_t pid; fd_in[0] = -1; if (pipe(fd_in) < 0 || pipe(fd_out) < 0) { auth_request_log_error(request, "checkpassword", "pipe() failed: %m"); callback(PASSDB_RESULT_INTERNAL_FAILURE, request); if (fd_in[0] != -1) { (void)close(fd_in[0]); (void)close(fd_in[1]); } return; } pid = fork(); if (pid == -1) { auth_request_log_error(request, "checkpassword", "fork() failed: %m"); callback(PASSDB_RESULT_INTERNAL_FAILURE, request); (void)close(fd_in[0]); (void)close(fd_in[1]); (void)close(fd_out[0]); (void)close(fd_out[1]); return; } if (pid == 0) { (void)close(fd_in[0]); (void)close(fd_out[1]); checkpassword_verify_plain_child(request, module, fd_in[1], fd_out[0]); /* not reached */ } if (close(fd_in[1]) < 0) { auth_request_log_error(request, "checkpassword", "close(fd_in[1]) failed: %m"); } if (close(fd_out[0]) < 0) { auth_request_log_error(request, "checkpassword", "close(fd_out[0]) failed: %m"); } auth_request_ref(request); chkpw_auth_request = i_new(struct chkpw_auth_request, 1); chkpw_auth_request->fd_in = fd_in[0]; chkpw_auth_request->fd_out = fd_out[1]; chkpw_auth_request->pid = pid; chkpw_auth_request->password = i_strdup(password); chkpw_auth_request->request = request; chkpw_auth_request->callback = callback; chkpw_auth_request->io_in = io_add(fd_in[0], IO_READ, checkpassword_child_input, chkpw_auth_request); chkpw_auth_request->io_out = io_add(fd_out[1], IO_WRITE, checkpassword_child_output, chkpw_auth_request); hash_insert(module->clients, POINTER_CAST(pid), chkpw_auth_request); } static struct passdb_module * checkpassword_preinit(struct auth_passdb *auth_passdb, const char *args) { struct checkpassword_passdb_module *module; module = p_new(auth_passdb->auth->pool, struct checkpassword_passdb_module, 1); module->checkpassword_path = p_strdup(auth_passdb->auth->pool, args); module->checkpassword_reply_path = PKG_LIBEXECDIR"/checkpassword-reply"; module->clients = hash_create(default_pool, default_pool, 0, NULL, NULL); return &module->module; } static void checkpassword_init(struct passdb_module *module, const char *args ATTR_UNUSED) { lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, module); } static void checkpassword_deinit(struct passdb_module *_module) { struct checkpassword_passdb_module *module = (struct checkpassword_passdb_module *)_module; struct hash_iterate_context *iter; void *key, *value; lib_signals_unset_handler(SIGCHLD, sigchld_handler, module); iter = hash_iterate_init(module->clients); while (hash_iterate(iter, &key, &value)) { checkpassword_request_finish(value, PASSDB_RESULT_INTERNAL_FAILURE); } hash_iterate_deinit(&iter); hash_destroy(&module->clients); } struct passdb_module_interface passdb_checkpassword = { "checkpassword", checkpassword_preinit, checkpassword_init, checkpassword_deinit, checkpassword_verify_plain, NULL, NULL }; #endif