passdb-pam.c revision eb9e1428a31ff3abf7c3eaf7aafc95eede90e86b
1607N/A/*
1607N/A Based on auth_pam.c from popa3d by Solar Designer <solar@openwall.com>.
1607N/A
1607N/A You're allowed to do whatever you like with this software (including
1607N/A re-distribution in source and/or binary form, with or without
1607N/A modification), provided that credit is given where it is due and any
1607N/A modified versions are marked as such. There's absolutely no warranty.
1607N/A*/
6983N/A
6983N/A#include "common.h"
1607N/A
1607N/A#ifdef PASSDB_PAM
1607N/A
1607N/A#include "lib-signals.h"
6983N/A#include "str.h"
6983N/A#include "var-expand.h"
6983N/A#include "network.h"
6983N/A#include "passdb.h"
1607N/A#include "safe-memset.h"
1607N/A#include "auth-cache.h"
1607N/A
1607N/A#include <stdlib.h>
1607N/A
5108N/A#ifdef HAVE_SECURITY_PAM_APPL_H
6238N/A# include <security/pam_appl.h>
1607N/A#elif defined(HAVE_PAM_PAM_APPL_H)
1607N/A# include <pam/pam_appl.h>
1607N/A#endif
1607N/A
1607N/A#if !defined(_SECURITY_PAM_APPL_H) && !defined(LINUX_PAM) && !defined(_OPENPAM)
2284N/A/* Sun's PAM doesn't use const. we use a bit dirty hack to check it.
3220N/A Originally it was just __sun__ check, but HP/UX also uses Sun's PAM
2543N/A so I thought this might work better. */
1607N/A# define linux_const
1607N/A#else
1607N/A# define linux_const const
1607N/A#endif
4129N/Atypedef linux_const void *pam_item_t;
4129N/A
1607N/Astruct pam_passdb_module {
1607N/A struct passdb_module module;
1607N/A
1607N/A const char *service_name, *pam_cache_key;
1607N/A
1939N/A unsigned int pam_setcred:1;
1939N/A unsigned int pam_session:1;
1607N/A unsigned int failure_show_msg:1;
2284N/A};
1607N/A
2543N/Astruct pam_conv_context {
1929N/A struct auth_request *request;
1607N/A const char *pass;
1607N/A const char *failure_msg;
1607N/A};
1607N/A
1607N/Astatic int
1607N/Apam_userpass_conv(int num_msg, linux_const struct pam_message **msg,
1607N/A struct pam_response **resp_r, void *appdata_ptr)
1607N/A{
2872N/A /* @UNSAFE */
1607N/A struct pam_conv_context *ctx = appdata_ptr;
1607N/A struct passdb_module *_passdb = ctx->request->passdb->passdb;
1607N/A struct pam_passdb_module *passdb = (struct pam_passdb_module *)_passdb;
2284N/A struct pam_response *resp;
1607N/A char *string;
1607N/A int i;
1607N/A
1607N/A *resp_r = NULL;
1607N/A
4129N/A resp = calloc(num_msg, sizeof(struct pam_response));
1607N/A if (resp == NULL)
1607N/A i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
1607N/A
1884N/A for (i = 0; i < num_msg; i++) {
1607N/A auth_request_log_debug(ctx->request, "pam",
1607N/A "#%d/%d style=%d msg=%s", i+1, num_msg,
2284N/A msg[i]->msg_style,
1607N/A msg[i]->msg != NULL ? msg[i]->msg : "");
1607N/A switch (msg[i]->msg_style) {
2644N/A case PAM_PROMPT_ECHO_ON:
2645N/A /* Assume we're asking for user. We might not ever
1607N/A get here because PAM already knows the user. */
1607N/A string = strdup(ctx->request->user);
2810N/A if (string == NULL)
3362N/A i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
1607N/A break;
1607N/A case PAM_PROMPT_ECHO_OFF:
1607N/A /* Assume we're asking for password */
1607N/A if (passdb->failure_show_msg)
2284N/A ctx->failure_msg = t_strdup(msg[i]->msg);
2284N/A string = strdup(ctx->pass);
2284N/A if (string == NULL)
2284N/A i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
2284N/A break;
2284N/A case PAM_ERROR_MSG:
1929N/A case PAM_TEXT_INFO:
1929N/A string = NULL;
1607N/A break;
1607N/A default:
1607N/A while (--i >= 0) {
1607N/A if (resp[i].resp != NULL) {
1607N/A safe_memset(resp[i].resp, 0,
1607N/A strlen(resp[i].resp));
1607N/A free(resp[i].resp);
1607N/A }
1607N/A }
1607N/A
1929N/A free(resp);
1929N/A return PAM_CONV_ERR;
1607N/A }
1929N/A
1929N/A resp[i].resp_retcode = PAM_SUCCESS;
1929N/A resp[i].resp = string;
1607N/A }
1607N/A
1607N/A *resp_r = resp;
1607N/A return PAM_SUCCESS;
1607N/A}
1607N/A
1607N/Astatic int try_pam_auth(struct auth_request *request, pam_handle_t *pamh)
1929N/A{
1929N/A struct passdb_module *_module = request->passdb->passdb;
1607N/A struct pam_passdb_module *module = (struct pam_passdb_module *)_module;
1607N/A pam_item_t item;
1607N/A int status;
1607N/A
1607N/A if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
1607N/A auth_request_log_error(request, "pam",
1607N/A "pam_authenticate() failed: %s",
1607N/A pam_strerror(pamh, status));
1607N/A return status;
1884N/A }
1884N/A
1607N/A#ifdef HAVE_PAM_SETCRED
1607N/A if (module->pam_setcred) {
1607N/A if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) !=
1607N/A PAM_SUCCESS) {
1607N/A auth_request_log_error(request, "pam",
1607N/A "pam_setcred() failed: %s",
1884N/A pam_strerror(pamh, status));
1884N/A return status;
1607N/A }
1607N/A }
1607N/A#endif
1607N/A
1607N/A if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
1607N/A auth_request_log_error(request, "pam",
1607N/A "pam_acct_mgmt() failed: %s",
1607N/A pam_strerror(pamh, status));
1607N/A return status;
1607N/A }
1607N/A
1607N/A if (module->pam_session) {
1607N/A if ((status = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
1607N/A auth_request_log_error(request, "pam",
1607N/A "pam_open_session() failed: %s",
1607N/A pam_strerror(pamh, status));
1607N/A return status;
1607N/A }
1607N/A
1607N/A if ((status = pam_close_session(pamh, 0)) != PAM_SUCCESS) {
1607N/A auth_request_log_error(request, "pam",
1607N/A "pam_close_session() failed: %s",
1607N/A pam_strerror(pamh, status));
1607N/A return status;
1607N/A }
1607N/A }
1884N/A
1884N/A status = pam_get_item(pamh, PAM_USER, &item);
1884N/A if (status != PAM_SUCCESS) {
1884N/A auth_request_log_error(request, "pam",
1884N/A "pam_get_item(PAM_USER) failed: %s",
1884N/A pam_strerror(pamh, status));
1607N/A return status;
1607N/A }
1607N/A auth_request_set_field(request, "user", item, NULL);
1607N/A return PAM_SUCCESS;
1607N/A}
1607N/A
1607N/Astatic void set_pam_items(struct auth_request *request, pam_handle_t *pamh)
1607N/A{
1607N/A const char *host;
1607N/A
1607N/A /* These shouldn't fail, and we don't really care if they do. */
1607N/A host = net_ip2addr(&request->remote_ip);
1607N/A if (host != NULL)
1607N/A (void)pam_set_item(pamh, PAM_RHOST, host);
1929N/A (void)pam_set_item(pamh, PAM_RUSER, request->user);
1607N/A /* TTY is needed by eg. pam_access module */
1607N/A (void)pam_set_item(pamh, PAM_TTY, "dovecot");
1607N/A}
1607N/A
1607N/Astatic enum passdb_result
1607N/Apam_verify_plain_call(struct auth_request *request, const char *service,
1607N/A const char *password)
1607N/A{
1607N/A pam_handle_t *pamh;
1607N/A struct pam_conv_context ctx;
1607N/A struct pam_conv conv;
1929N/A enum passdb_result result;
1607N/A int status, status2;
1607N/A
1607N/A conv.conv = pam_userpass_conv;
1607N/A conv.appdata_ptr = &ctx;
1607N/A
1607N/A memset(&ctx, 0, sizeof(ctx));
1607N/A ctx.request = request;
1607N/A ctx.pass = password;
1607N/A
1607N/A status = pam_start(service, request->user, &conv, &pamh);
1607N/A if (status != PAM_SUCCESS) {
1607N/A auth_request_log_error(request, "pam", "pam_start() failed: %s",
1607N/A pam_strerror(pamh, status));
1607N/A return PASSDB_RESULT_INTERNAL_FAILURE;
1607N/A }
1607N/A
1607N/A set_pam_items(request, pamh);
1607N/A status = try_pam_auth(request, pamh);
1607N/A if ((status2 = pam_end(pamh, status)) != PAM_SUCCESS) {
1607N/A auth_request_log_error(request, "pam", "pam_end() failed: %s",
1929N/A pam_strerror(pamh, status2));
1607N/A return PASSDB_RESULT_INTERNAL_FAILURE;
1607N/A }
1607N/A
1607N/A switch (status) {
1607N/A case PAM_SUCCESS:
1607N/A result = PASSDB_RESULT_OK;
1607N/A break;
1607N/A case PAM_USER_UNKNOWN:
1607N/A result = PASSDB_RESULT_USER_UNKNOWN;
1607N/A break;
1607N/A case PAM_NEW_AUTHTOK_REQD:
1607N/A case PAM_ACCT_EXPIRED:
1607N/A result = PASSDB_RESULT_PASS_EXPIRED;
1607N/A break;
1929N/A default:
1929N/A result = PASSDB_RESULT_PASSWORD_MISMATCH;
1929N/A break;
2284N/A }
2284N/A
1929N/A if (result != PASSDB_RESULT_OK && ctx.failure_msg != NULL) {
2548N/A auth_request_set_field(request, "reason",
2548N/A ctx.failure_msg, NULL);
2548N/A }
2548N/A return result;
2548N/A}
2548N/A
2548N/Astatic void
2548N/Apam_verify_plain(struct auth_request *request, const char *password,
2548N/A verify_plain_callback_t *callback)
2548N/A{
2548N/A struct passdb_module *_module = request->passdb->passdb;
2548N/A struct pam_passdb_module *module = (struct pam_passdb_module *)_module;
2548N/A enum passdb_result result;
2548N/A string_t *expanded_service;
2548N/A const char *service;
1929N/A
1929N/A expanded_service = t_str_new(64);
1929N/A var_expand(expanded_service, module->service_name,
1929N/A auth_request_get_var_expand_table(request, NULL));
1929N/A service = str_c(expanded_service);
2872N/A
2872N/A auth_request_log_debug(request, "pam", "lookup service=%s", service);
2872N/A
2872N/A result = pam_verify_plain_call(request, service, password);
2872N/A callback(result, request);
2872N/A}
2872N/A
2872N/Astatic struct passdb_module *
2872N/Apam_preinit(struct auth_passdb *auth_passdb, const char *args)
2872N/A{
2872N/A struct pam_passdb_module *module;
2872N/A const char *const *t_args;
2872N/A int i;
2872N/A
2284N/A module = p_new(auth_passdb->auth->pool, struct pam_passdb_module, 1);
2284N/A module->service_name = "dovecot";
1929N/A /* we're caching the password by using directly the plaintext password
1929N/A given by the auth mechanism */
2284N/A module->module.default_pass_scheme = "PLAIN";
2284N/A module->module.blocking = TRUE;
1929N/A
2284N/A t_push();
2284N/A t_args = t_strsplit_spaces(args, " ");
1929N/A for(i = 0; t_args[i] != NULL; i++) {
1929N/A /* -session for backwards compatibility */
2548N/A if (strcmp(t_args[i], "-session") == 0 ||
2872N/A strcmp(t_args[i], "session=yes") == 0)
2872N/A module->pam_session = TRUE;
2872N/A else if (strcmp(t_args[i], "setcred=yes") == 0)
2872N/A module->pam_setcred = TRUE;
2872N/A else if (strncmp(t_args[i], "cache_key=", 10) == 0) {
2872N/A module->module.cache_key =
2872N/A auth_cache_parse_key(auth_passdb->auth->pool,
2872N/A t_args[i] + 10);
2872N/A } else if (strcmp(t_args[i], "blocking=yes") == 0) {
2872N/A /* ignore, for backwards compatibility */
2872N/A } else if (strcmp(t_args[i], "failure_show_msg=yes") == 0) {
2872N/A module->failure_show_msg = TRUE;
2872N/A } else if (strcmp(t_args[i], "*") == 0) {
2872N/A /* for backwards compatibility */
2872N/A module->service_name = "%Ls";
2872N/A } else if (t_args[i+1] == NULL) {
2872N/A module->service_name =
2872N/A p_strdup(auth_passdb->auth->pool, t_args[i]);
2872N/A } else {
2872N/A i_fatal("Unexpected PAM parameter: %s", t_args[i]);
2872N/A }
2872N/A }
2872N/A t_pop();
2872N/A
1929N/A return &module->module;
1929N/A}
1929N/A
1929N/Astruct passdb_module_interface passdb_pam = {
1929N/A "pam",
1929N/A
1929N/A pam_preinit,
1929N/A NULL,
2872N/A NULL,
2872N/A
2872N/A pam_verify_plain,
1929N/A NULL,
1929N/A NULL
1929N/A};
2872N/A
1929N/A#endif
1929N/A