diff options
Diffstat (limited to 'src/auth/passdb-pam.c')
-rw-r--r-- | src/auth/passdb-pam.c | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/src/auth/passdb-pam.c b/src/auth/passdb-pam.c new file mode 100644 index 0000000..6a1032d --- /dev/null +++ b/src/auth/passdb-pam.c @@ -0,0 +1,401 @@ +/* + Based on auth_pam.c from popa3d by Solar Designer <solar@openwall.com>. + + You're allowed to do whatever you like with this software (including + re-distribution in source and/or binary form, with or without + modification), provided that credit is given where it is due and any + modified versions are marked as such. There's absolutely no warranty. +*/ + +#include "auth-common.h" +#include "passdb.h" + +#ifdef PASSDB_PAM + +#include "lib-signals.h" +#include "str.h" +#include "net.h" +#include "safe-memset.h" +#include "auth-cache.h" + +#include <sys/stat.h> + +#ifdef HAVE_SECURITY_PAM_APPL_H +# include <security/pam_appl.h> +#elif defined(HAVE_PAM_PAM_APPL_H) +# include <pam/pam_appl.h> +#endif + +#if defined(sun) || defined(__sun__) || defined(_HPUX_SOURCE) +# define pam_const +#else +# define pam_const const +#endif + +typedef pam_const void *pam_item_t; + +#define PASSDB_PAM_DEFAULT_MAX_REQUESTS 100 + +struct pam_passdb_module { + struct passdb_module module; + + const char *service_name, *pam_cache_key; + unsigned int requests_left; + + bool pam_setcred:1; + bool pam_session:1; + bool failure_show_msg:1; +}; + +struct pam_conv_context { + struct auth_request *request; + const char *pass; + const char *failure_msg; +}; + +static int +pam_userpass_conv(int num_msg, pam_const struct pam_message **msg, + struct pam_response **resp_r, void *appdata_ptr) +{ + /* @UNSAFE */ + struct pam_conv_context *ctx = appdata_ptr; + struct passdb_module *_passdb = ctx->request->passdb->passdb; + struct pam_passdb_module *passdb = (struct pam_passdb_module *)_passdb; + struct pam_response *resp; + char *string; + int i; + + *resp_r = NULL; + + resp = calloc(num_msg, sizeof(struct pam_response)); + if (resp == NULL) + i_fatal_status(FATAL_OUTOFMEM, "Out of memory"); + + for (i = 0; i < num_msg; i++) { + e_debug(authdb_event(ctx->request), + "#%d/%d style=%d msg=%s", i+1, num_msg, + msg[i]->msg_style, + msg[i]->msg != NULL ? msg[i]->msg : ""); + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_ON: + /* Assume we're asking for user. We might not ever + get here because PAM already knows the user. */ + string = strdup(ctx->request->fields.user); + if (string == NULL) + i_fatal_status(FATAL_OUTOFMEM, "Out of memory"); + break; + case PAM_PROMPT_ECHO_OFF: + /* Assume we're asking for password */ + if (passdb->failure_show_msg) + ctx->failure_msg = t_strdup(msg[i]->msg); + string = strdup(ctx->pass); + if (string == NULL) + i_fatal_status(FATAL_OUTOFMEM, "Out of memory"); + break; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + string = NULL; + break; + default: + while (--i >= 0) { + if (resp[i].resp != NULL) { + safe_memset(resp[i].resp, 0, + strlen(resp[i].resp)); + free(resp[i].resp); + } + } + + free(resp); + return PAM_CONV_ERR; + } + + resp[i].resp_retcode = PAM_SUCCESS; + resp[i].resp = string; + } + + *resp_r = resp; + return PAM_SUCCESS; +} + +static const char * +pam_get_missing_service_file_path(const char *service ATTR_UNUSED) +{ +#ifdef SUNPAM + /* Uses /etc/pam.conf - we're not going to parse that */ + return NULL; +#else + static bool service_checked = FALSE; + const char *path; + struct stat st; + + if (service_checked) { + /* check and complain only once */ + return NULL; + } + service_checked = TRUE; + + path = t_strdup_printf("/etc/pam.d/%s", service); + if (stat(path, &st) < 0 && errno == ENOENT) { + /* looks like it's missing. but before assuming that the system + even uses /etc/pam.d, make sure that it exists. */ + if (stat("/etc/pam.d", &st) == 0) + return path; + } + /* exists or is unknown */ + return NULL; +#endif +} + +static int try_pam_auth(struct auth_request *request, pam_handle_t *pamh, + const char *service) +{ + struct passdb_module *_module = request->passdb->passdb; + struct pam_passdb_module *module = (struct pam_passdb_module *)_module; + const char *path, *str; + pam_item_t item; + int status; + + if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) { + path = pam_get_missing_service_file_path(service); + switch (status) { + case PAM_USER_UNKNOWN: + str = "unknown user"; + break; + default: + str = t_strconcat("pam_authenticate() failed: ", + pam_strerror(pamh, status), NULL); + break; + } + if (path != NULL) { + /* log this as error, since it probably is */ + str = t_strdup_printf("%s (%s missing?)", str, path); + e_error(authdb_event(request), "%s", str); + } else if (status == PAM_AUTH_ERR) { + str = t_strconcat(str, " ("AUTH_LOG_MSG_PASSWORD_MISMATCH"?)", NULL); + if (request->set->debug_passwords) { + str = t_strconcat(str, " (given password: ", + request->mech_password, + ")", NULL); + } + e_info(authdb_event(request), "%s", str); + } else { + if (status == PAM_USER_UNKNOWN) + auth_request_log_unknown_user(request, AUTH_SUBSYS_DB); + else { + e_info(authdb_event(request), + "%s", str); + } + } + return status; + } + +#ifdef HAVE_PAM_SETCRED + if (module->pam_setcred) { + if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != + PAM_SUCCESS) { + e_error(authdb_event(request), + "pam_setcred() failed: %s", + pam_strerror(pamh, status)); + return status; + } + } +#endif + + if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) { + e_error(authdb_event(request), + "pam_acct_mgmt() failed: %s", + pam_strerror(pamh, status)); + return status; + } + + if (module->pam_session) { + if ((status = pam_open_session(pamh, 0)) != PAM_SUCCESS) { + e_error(authdb_event(request), + "pam_open_session() failed: %s", + pam_strerror(pamh, status)); + return status; + } + + if ((status = pam_close_session(pamh, 0)) != PAM_SUCCESS) { + e_error(authdb_event(request), + "pam_close_session() failed: %s", + pam_strerror(pamh, status)); + return status; + } + } + + status = pam_get_item(pamh, PAM_USER, &item); + if (status != PAM_SUCCESS) { + e_error(authdb_event(request), + "pam_get_item(PAM_USER) failed: %s", + pam_strerror(pamh, status)); + return status; + } + auth_request_set_field(request, "user", item, NULL); + return PAM_SUCCESS; +} + +static void set_pam_items(struct auth_request *request, pam_handle_t *pamh) +{ + const char *host; + + /* These shouldn't fail, and we don't really care if they do. */ + host = net_ip2addr(&request->fields.remote_ip); + if (host[0] != '\0') + (void)pam_set_item(pamh, PAM_RHOST, host); + (void)pam_set_item(pamh, PAM_RUSER, request->fields.user); + /* TTY is needed by eg. pam_access module */ + (void)pam_set_item(pamh, PAM_TTY, "dovecot"); +} + +static enum passdb_result +pam_verify_plain_call(struct auth_request *request, const char *service, + const char *password) +{ + pam_handle_t *pamh; + struct pam_conv_context ctx; + struct pam_conv conv; + enum passdb_result result; + int status, status2; + + conv.conv = pam_userpass_conv; + conv.appdata_ptr = &ctx; + + i_zero(&ctx); + ctx.request = request; + ctx.pass = password; + + status = pam_start(service, request->fields.user, &conv, &pamh); + if (status != PAM_SUCCESS) { + e_error(authdb_event(request), + "pam_start() failed: %s", + pam_strerror(pamh, status)); + return PASSDB_RESULT_INTERNAL_FAILURE; + } + + set_pam_items(request, pamh); + status = try_pam_auth(request, pamh, service); + if ((status2 = pam_end(pamh, status)) != PAM_SUCCESS) { + e_error(authdb_event(request), + "pam_end() failed: %s", + pam_strerror(pamh, status2)); + return PASSDB_RESULT_INTERNAL_FAILURE; + } + + switch (status) { + case PAM_SUCCESS: + result = PASSDB_RESULT_OK; + break; + case PAM_USER_UNKNOWN: + result = PASSDB_RESULT_USER_UNKNOWN; + break; + case PAM_NEW_AUTHTOK_REQD: + case PAM_ACCT_EXPIRED: + result = PASSDB_RESULT_PASS_EXPIRED; + break; + default: + result = PASSDB_RESULT_PASSWORD_MISMATCH; + break; + } + + if (result != PASSDB_RESULT_OK && ctx.failure_msg != NULL) { + auth_request_set_field(request, "reason", + ctx.failure_msg, NULL); + } + return result; +} + +static void +pam_verify_plain(struct auth_request *request, const char *password, + verify_plain_callback_t *callback) +{ + struct passdb_module *_module = request->passdb->passdb; + struct pam_passdb_module *module = (struct pam_passdb_module *)_module; + enum passdb_result result; + const char *service, *error; + + if (module->requests_left > 0) { + if (--module->requests_left == 0) + worker_restart_request = TRUE; + } + + if (t_auth_request_var_expand(module->service_name, request, NULL, + &service, &error) <= 0) { + e_debug(authdb_event(request), + "Failed to expand service %s: %s", + module->service_name, error); + callback(PASSDB_RESULT_INTERNAL_FAILURE, request); + return; + } + + e_debug(authdb_event(request), + "lookup service=%s", service); + + result = pam_verify_plain_call(request, service, password); + callback(result, request); +} + +static struct passdb_module * +pam_preinit(pool_t pool, const char *args) +{ + struct pam_passdb_module *module; + const char *const *t_args; + int i; + + module = p_new(pool, struct pam_passdb_module, 1); + module->service_name = "dovecot"; + /* we're caching the password by using directly the plaintext password + given by the auth mechanism */ + module->module.default_pass_scheme = "PLAIN"; + module->module.blocking = TRUE; + module->requests_left = PASSDB_PAM_DEFAULT_MAX_REQUESTS; + + t_args = t_strsplit_spaces(args, " "); + for(i = 0; t_args[i] != NULL; i++) { + /* -session for backwards compatibility */ + if (strcmp(t_args[i], "-session") == 0 || + strcmp(t_args[i], "session=yes") == 0) + module->pam_session = TRUE; + else if (strcmp(t_args[i], "setcred=yes") == 0) + module->pam_setcred = TRUE; + else if (str_begins(t_args[i], "cache_key=")) { + module->module.default_cache_key = + auth_cache_parse_key(pool, t_args[i] + 10); + } else if (strcmp(t_args[i], "blocking=yes") == 0) { + /* ignore, for backwards compatibility */ + } else if (strcmp(t_args[i], "failure_show_msg=yes") == 0) { + module->failure_show_msg = TRUE; + } else if (strcmp(t_args[i], "*") == 0) { + /* for backwards compatibility */ + module->service_name = "%Ls"; + } else if (str_begins(t_args[i], "max_requests=")) { + if (str_to_uint(t_args[i] + 13, + &module->requests_left) < 0) { + i_error("pam: Invalid requests_left value: %s", + t_args[i] + 13); + } + } else if (t_args[i+1] == NULL) { + module->service_name = p_strdup(pool, t_args[i]); + } else { + i_fatal("pam: Unknown setting: %s", t_args[i]); + } + } + return &module->module; +} + +struct passdb_module_interface passdb_pam = { + "pam", + + pam_preinit, + NULL, + NULL, + + pam_verify_plain, + NULL, + NULL +}; +#else +struct passdb_module_interface passdb_pam = { + .name = "pam" +}; +#endif |