/* Copyright (c) 2000 Ingo Luetkebohle, All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CODE CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* $Id$ * mod_auth_pam for apache 2.0 * * Based on: v 1.1 from 07. July 2002 (for apache 1.3) * * * mod_auth_pam: * basic authentication against pluggable authentication module lib * * The authoritative homepage for this module is at * http://pam.sourceforge.net/ * * For assistance and support, contact the pam-list * https://listman.redhat.com/mailman/listinfo/pam-list * or the Help forum on SourceForge. * * Written by Ingo Luetkebohle, based upon mod_auth.c, with contributions * from Fredrik Ohrn (group performance), Dirk-Willem van Gulik (licensing * consultation) and Michael Johnson (example group implementation). * * Thanks to Andrew Morgan and the rest of the Linux-PAM developers who * provided invaluable development help and ideas. * * Changes: * 09-Jul-02: Ported to apache 2.02 (Dirk-Willem van Gulik, * ). * 07-Jul-02: License changed to allow redistribution * Performance improvement for group lookup (many thanks to Fredrik Ohrn for the patch) * 06-Dec-99: Special casing for Solaris 2.6 added * Added versioning message to headers * 14-Feb-99: Cleaned up the configuration directives and named them * in a more straightforward way * * incorporated getugroups patch from Klaus Wissmann * to look up supplementary groups from /etc/group * * 22-Jan-99: incorporated changes from Pavel Kankovsky * Enabler configuration directive now called AuthPAM_Enabled * Updated for Apache 1.3.x * 25-Sep-98: replaced pwdb groups routine with standard C * 19-Oct-97: made module fall through (if configured to do so), * even when a user of the given name is found in the PAM * databases * 17-Apr-97: fixed segfault that occured when Apache couldn't look * up the remote host name * removed annoying compiler warnings * * 05-Apr-97: made fall-through configurable with AuthPAM_Authorative * * 25-Mar-97: added support for transparent fall-through to other auth * modules with configurable fail delays * added acct_mgmt hook * added group support (through libpwdb) * * usage information: * * new-style (DSO and apache 2.0) * * compile with * apxs -c -lpam mod_auth_pam.c * * apxs -I/usr/local/include -L/usr/local/lib -c -lpam mod_auth_pam.c * * install with * apxs -i -a mod_auth_pam.so * * configure PAM by adding * /etc/pam.d/httpd * * with the appropriate pam modules (for starters, just copy over ftp) * * old-style (from Apache 1.2.x) * * 1. Configuration: * Module pam_auth_module mod_auth_pam.o * EXTRA_LIBS+= -lpam -ldl * * 2. Add an auth and an account entry for service type "httpd" * to your PAM configuration * * * configuration directives: * AuthFailDelay * number of mili-seconds to wait after a * failed authentication attempt. this is * a minimum value and may have been * increased by other pam apps. * defaults to 0 * REQUIRES lib_pam SUPPORT * * AuthPAM_Enabled on|off * If on, mod_auth_pam will try to authenticate * the user. * If off, mod_auth_pam will DECLINE immediately * instead of trying to authenticate the user. * This will make Apache try other modules. * Defaults to on * * AuthPAM_FallThrough * If on, makes mod_auth_pam DECLINE if it can't * the username, giving other modules a chance. * Please note that, if it DOES find the username, * and the password doesn't match, it will NOT * fall through but return "access denied" instead * Defaults to off * * AuthPAM_Authorative on|off DEPRECATED */ #include #include /* for getpwnam et.al. */ #include /* for getpwnam et.al. */ #include #include "ap_config.h" #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" #include /* change this to 0 on RedHat 4.x */ #define PAM_STRE_NEEDS_PAMH 1 #define VERSION "1.0a" module pam_auth_module; typedef struct { char *name, *pw; } auth_pam_userinfo; /* * Solaris 2.6.x has a broken conversation function and needs this * as a global variable * I refused to pollute code for other platforms with this, * so all Solaris 2.6 specific stuff is if'd like the following */ #if SOLARIS2 == 260 auth_pam_userinfo *global_userinfo; #endif /* * the pam_strerror function has different parameters in early PAM * versions */ #ifndef PAM_STRE_NEEDS_PAMH #define compat_pam_strerror(pamh, res) pam_strerror(res) #else #define compat_pam_strerror(pamh, res) pam_strerror(pamh, res) #endif /* * configuration directive handling */ typedef struct { int fail_delay, /* fail delay in ms -- needs library support */ fall_through, /* 1 to DECLINE instead of * HTTP_UNAUTHORIZEDif we can't find the * username (defaults to 0) */ enabled; /* 1 to use mod_auth_pam, 0 otherwise * (defaults to 1) */ } auth_pam_dir_config; static int auth_pam_init( apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, server_rec * s ) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "PAM: mod_auth_pam/" VERSION); return OK; } static void *create_auth_pam_dir_config(apr_pool_t * p, char *dummy) { auth_pam_dir_config *new = (auth_pam_dir_config *) apr_palloc(p, sizeof(auth_pam_dir_config)); new->fail_delay = 0; /* 0 ms */ new->fall_through = 0; /* off */ new->enabled = 1; /* on */ return new; } static command_rec auth_pam_cmds[] = { AP_INIT_TAKE1("AuthPAM_FailDelay", ap_set_int_slot, (void *) APR_OFFSETOF(auth_pam_dir_config, fail_delay), OR_AUTHCFG, "number of micro seconds to wait after failed authentication " "attempt. (default is 0.)"), AP_INIT_FLAG("AuthPAM_FallThrough", ap_set_flag_slot, (void *) APR_OFFSETOF(auth_pam_dir_config, fall_through), OR_AUTHCFG, "on|off - determines if other authentication methods are attempted " "if this one fails; (default is off.)"), AP_INIT_FLAG("AuthPAM_Enabled", ap_set_flag_slot, (void *) APR_OFFSETOF(auth_pam_dir_config, enabled), OR_AUTHCFG, "on|off - determines if PAM authentication is enabled. " "(default is on.)"), {NULL} }; /* * auth_pam_talker: supply authentication information to PAM when asked * * Assumptions: * A password is asked for by requesting input without echoing * A username is asked for by requesting input _with_ echoing * */ static int auth_pam_talker(int num_msg, const struct pam_message ** msg, struct pam_response ** resp, void *appdata_ptr) { unsigned short i = 0; auth_pam_userinfo *userinfo = (auth_pam_userinfo *) appdata_ptr; struct pam_response *response = 0; #if SOLARIS2 == 260 if (!userinfo) userinfo = global_userinfo; /* fprintf(stderr,"%s : %s", userinfo->name, userinfo->pw); */ #endif /* parameter sanity checking */ if (!resp || !msg || !userinfo) return PAM_CONV_ERR; /* allocate memory to store response */ response = malloc(num_msg * sizeof(struct pam_response)); if (!response) return PAM_CONV_ERR; /* copy values */ for (i = 0; i < num_msg; i++) { /* initialize to safe values */ response[i].resp_retcode = 0; response[i].resp = 0; /* select response based on requested output style */ switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_ON: /* on memory allocation failure, auth fails */ response[i].resp = strdup(userinfo->name); break; case PAM_PROMPT_ECHO_OFF: response[i].resp = strdup(userinfo->pw); break; default: if (response) free(response); return PAM_CONV_ERR; } } /* everything okay, set PAM response values */ *resp = response; return PAM_SUCCESS; } /* * These functions return 0 if client is OK, and proper error status * if not... either AUTH_REQUIRED, if we made a check, and it failed, or * SERVER_ERROR, if things are so totally confused that we couldn't * figure out how to tell if the client is authorized or not. * * If they return DECLINED, and all other modules also decline, that's * treated by the server core as a configuration error, logged and * reported as such. */ /* * Determine user ID, and check if it really is that user */ static int pam_auth_basic_user(request_rec * r) { int res = 0; /* mod_auth_pam specific */ auth_pam_userinfo userinfo = {NULL, NULL}; auth_pam_dir_config *conf = (auth_pam_dir_config *) ap_get_module_config(r->per_dir_config, &pam_auth_module); /* PAM specific */ struct pam_conv conv_info = {&auth_pam_talker, (void *) &userinfo}; pam_handle_t *pamh = NULL; #if SOLARIS2 == 260 global_userinfo = &userinfo; #endif /* enabled? */ if (!conf->enabled) return DECLINED; /* read sent pw */ if ((res = ap_get_basic_auth_pw(r, (const char **) &(userinfo.pw)))) return res; /* this is only set after get_basic_auth_pw was called */ userinfo.name = r->user; /* initialize pam */ if ((res = pam_start(pam_servicename, userinfo.name, &conv_info, &pamh)) != PAM_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "PAM: Could not start pam service: %s", compat_pam_strerror(pamh, res)); return HTTP_INTERNAL_SERVER_ERROR; } /* set fail delay */ #ifdef PAM_FAIL_DELAY pam_fail_delay(pamh, conf->fail_delay); #endif /* set remote user information */ /* * this seems to cause segfaults in lots of cases -- disabled for now * pam_set_item(pamh, PAM_USER, userinfo.name); pam_set_item(pamh, * PAM_RHOST, get_remote_host(r->connection, conf, REMOTE_NAME)); */ /* try to authenticate user, log error on failure */ if ((res = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK)) != PAM_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "PAM: user '%s' - not authenticated: %s", r->user, compat_pam_strerror(pamh, res)); if (conf->fall_through && (res == PAM_USER_UNKNOWN)) { /* we don't know about the user, but other auth modules might do */ pam_end(pamh, PAM_SUCCESS); return DECLINED; } else { pam_end(pamh, PAM_SUCCESS); ap_note_basic_auth_failure(r); return HTTP_UNAUTHORIZED; } /* endif fall_through */ } /* endif authenticate */ /* check that the account is healthy */ if ((res = pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK)) != PAM_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "PAM: user '%s' - invalid account: %s", r->user, compat_pam_strerror(pamh, res)); pam_end(pamh, PAM_SUCCESS); return HTTP_UNAUTHORIZED; } pam_end(pamh, PAM_SUCCESS); return OK; } static void pam_register_hooks(apr_pool_t * p) { ap_hook_post_config(auth_pam_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_check_user_id(pam_auth_basic_user, NULL, NULL, APR_HOOK_MIDDLE); } module AP_MODULE_DECLARE_DATA pam_auth_module = { STANDARD20_MODULE_STUFF, create_auth_pam_dir_config, /* dir config creater */ NULL, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ auth_pam_cmds, /* command table */ pam_register_hooks, /* register hooks */ };