/*
* auth_pam.c:
* authenticate using Pluggable Authentication Modules
*
* Copyright (c) 2001 Chris Lightfoot. All rights reserved.
*
*/
#ifdef HAVE_CONFIG_H
#include "configuration.h"
#endif /* HAVE_CONFIG_H */
#ifdef AUTH_PAM
static const char rcsid[] = "$Id: auth_pam.c,v 1.29 2003/09/27 15:33:57 chris Exp $";
#include <sys/types.h> /* BSD needs this here, it seems. */
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <security/pam_appl.h>
#include "auth_pam.h"
#include "authswitch.h"
#include "config.h"
#include "util.h"
/* auth_pam_conversation:
* PAM conversation function, used to transmit the password supplied by the
* user to the PAM modules for authentication. */
int auth_pam_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) {
const struct pam_message **m;
struct pam_response *r;
if (!num_msg || !msg || !appdata_ptr) return PAM_CONV_ERR;
*resp = (struct pam_response*)xcalloc(num_msg, sizeof(struct pam_response));
if (!*resp) return PAM_CONV_ERR;
/* Assume that any prompt is asking for a password */
for (m = msg, r = *resp; m < msg + num_msg; ++m, ++r) {
if ((*m)->msg_style == PAM_PROMPT_ECHO_OFF) {
r->resp = xstrdup((char*)appdata_ptr);
r->resp_retcode = 0;
}
}
return PAM_SUCCESS;
}
/* auth_pam_do_authentication FACILITY USER PASSWORD
* Tries to authenticate USER with PASSWORD using the named PAM FACILITY,
* returning nonzero on success or zero on failure. */
static int auth_pam_do_authentication(const char *facility, const char *user, const char *pass, const char *clienthost) {
struct pam_conv conv;
pam_handle_t *pamh = NULL;
int result = 0, r;
/* This will generate a warning on Solaris; I can't see an easy fix. */
conv.conv = auth_pam_conversation;
conv.appdata_ptr = (void*)pass;
r = pam_start(facility, user, &conv, &pamh);
if (r != PAM_SUCCESS) {
log_print(LOG_ERR, "auth_pam_new_user_pass: pam_start: %s", pam_strerror(pamh, r));
return 0;
}
/* We want to be able to test against the client IP; make the remote host
* information available to the PAM stack. */
r = pam_set_item(pamh, PAM_RHOST, clienthost);
if (r != PAM_SUCCESS) {
log_print(LOG_ERR, "auth_pam_new_user_pass: pam_start: %s", pam_strerror(pamh, r));
return 0;
}
/* Authenticate user. */
r = pam_authenticate(pamh, 0);
if (r == PAM_SUCCESS) {
/* OK, is the account presently allowed to log in? */
r = pam_acct_mgmt(pamh, PAM_SILENT);
if (r == PAM_SUCCESS)
/* Succeeded. */
result = 1;
else
/* Failed; account is disabled or something. */
log_print(LOG_ERR, "auth_pam_new_user_pass: pam_acct_mgmt(%s): %s", user, pam_strerror(pamh, r));
} else
/* User did not authenticate. */
log_print(LOG_ERR, "auth_pam_new_user_pass: pam_authenticate(%s): %s", user, pam_strerror(pamh, r));
r = pam_end(pamh, r);
if (r != PAM_SUCCESS) log_print(LOG_ERR, "auth_pam_new_user_pass: pam_end: %s", pam_strerror(pamh, r));
return result;
}
/* auth_pam_new_user_pass:
* Attempt to authenticate user and pass using PAM. This is not a
* virtual-domains authenticator, so it only looks at user. */
#ifdef REALLY_UGLY_PAM_HACK
pid_t auth_pam_child_pid;
#endif
authcontext auth_pam_new_user_pass(const char *user, const char *local_part, const char *domain, const char *pass, const char *clienthost, const char *serverhost) {
struct passwd pw, *pw2;
char *s;
int use_gid = 0;
gid_t gid = 99;
static const char *facility;
int authenticated = 0;
/* Check the this isn't a virtual-domain user. */
if (local_part) return NULL;
/* It is possible to use PAM to authenticate users who do not exist as
* system users. We support this by defining an auth-pam-mail-user
* configuration option which is used to obtain the user information
* for a non-system user to be authenticated against PAM. */
if (!(pw2 = getpwnam(user))) {
char *s;
if ((s = config_get_string("auth-pam-mail-user"))) {
uid_t u;
if (parse_uid(s, &u)) {
if (!(pw2 = getpwuid(u)))
log_print(LOG_ERR, _("auth_pam_new_user_pass: auth-pam-mail-user directive `%s' does not correspond to a real user"), s);
} else
log_print(LOG_ERR, _("auth_pam_new_user_pass: auth-pam-mail-user directive `%s' does not make sense"), s);
}
if (!pw2)
return NULL;
}
/* Copy the password structure, since it is in static storage and may
* get overwritten by calls in the PAM code. */
pw = *pw2;
/* pw now contains either the data for the real UNIX user named or the UNIX
* user given by the auth-pam-mail-user config option. */
/* Obtain facility name. */
if (!facility && !(facility = config_get_string("auth-pam-facility")))
facility = AUTH_PAM_FACILITY;
/* Obtain gid to use */
if ((s = config_get_string("auth-pam-mail-group"))) {
if (!parse_gid(s, &gid)) {
log_print(LOG_ERR, _("auth_pam_new_user_pass: auth-pam-mail-group directive `%s' does not make sense"), s);
return NULL;
}
use_gid = 1;
}
/*
* On many systems, PAM leaks memory, which is a problem for a daemon like
* tpop3d which does all authentication in the main daemon. So we
* optionally implement a really ugly hack where we fork a process in
* which to interact with PAM.
*/
#ifdef REALLY_UGLY_PAM_HACK
{
int pfd[2];
char res = 0;
ssize_t n;
/*
* The child process writes a byte zero into the pipe on failure or a
* one on success. Don't use the exit value because we don't want to
* have to piss about with the SIGCHLD handler.
*/
if (pipe(pfd) == -1)
log_print(LOG_ERR, "auth_pam_new_user_pass: pipe: %m");
else {
switch (auth_pam_child_pid = fork()) {
case 0:
close(pfd[0]);
if (xwrite(pfd[1], auth_pam_do_authentication(facility, user, pass, clienthost) ? "\001" : "\0", 1) == -1)
/* This is really bad. The parent may hang waiting for us. */
log_print(LOG_ERR, _("auth_pam_new_user_pass: (child process): write: %m"));
close(pfd[1]);
_exit(0);
case -1:
close(pfd[0]);
close(pfd[1]);
log_print(LOG_ERR, "auth_pam_new_user_pass: fork: %m");
break;
default:
close(pfd[1]);
while ((n = read(pfd[0], &res, 1)) == -1 && errno == EINTR);
close(pfd[0]);
if (n <= 0) {
/* Bad. Read error, child probably crashed. */
if (n == -1)
log_print(LOG_ERR, "auth_pam_new_user_pass: read: %m");
else
log_print(LOG_ERR, _("auth_pam_new_user_pass: authentication child did not send status (shouldn't happen)"));
authenticated = 0;
} else
/* Good. Byte returned. */
authenticated = res;
break;
}
}
}
#else
authenticated = auth_pam_do_authentication(facility, user, pass, clienthost);
#endif /* REALLY_UGLY_PAM_HACK */
if (authenticated)
return authcontext_new(pw.pw_uid, use_gid ? gid : pw.pw_gid, NULL, NULL, pw.pw_dir);
else
return NULL;
}
#endif /* AUTH_PAM */
syntax highlighted by Code2HTML, v. 0.9.1