/* 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. */ /* * v 1.1 from 07. July 2002 * * 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: * 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) * * compile with * apxs -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 "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #include /* change this to 0 on RedHat 4.x */ #define PAM_STRE_NEEDS_PAMH 1 #define VERSION "1.0a" module pam_auth_module; static const char *pam_servicename = "httpd", *valid_user = "valid-user"; 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 AUTH_REQUIRED if 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; void auth_pam_init(server_rec *s, pool *p) { ap_add_version_component("mod_auth_pam/" VERSION); } static void* create_auth_pam_dir_config(pool *p, char *dummy) { auth_pam_dir_config *new = (auth_pam_dir_config*) ap_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 char* auth_fail_delay(cmd_parms *cmd, auth_pam_dir_config *config, char *secs) { if(secs) config->fail_delay = atoi(secs); return NULL; } static char* auth_fall_through(cmd_parms *cmd, auth_pam_dir_config *config, int arg) { config->fall_through = arg; return NULL; } static char* auth_enable(cmd_parms *cmd, auth_pam_dir_config *config, int arg) { config->enabled = arg; return NULL; } static command_rec auth_pam_cmds[] = { { "AuthFailDelay", (const char*(*)())auth_fail_delay, 0, OR_AUTHCFG, TAKE1, "number of micro seconds to wait after failed authentication attempt. defau lt is 0" }, { "AuthPAM_Authorative", (const char*(*)())auth_fall_through, NULL, OR_AUTHCFG, FLAG, "no longer in use -- see AuthPAM_FallThrough instead" }, { "AuthPAM_FallThrough", (const char*(*)())auth_fall_through, NULL, OR_AUTHCFG, FLAG, "on|off - determines if other authentication methods are attempted if this one fails; default is off" }, { "AuthPAM_Enabled", (const char*(*)())auth_enable, NULL, OR_AUTHCFG, FLAG, "on|off - determines if PAM authentication is enabled; default is on" }, { 0 } }; /* * 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->connection->user; /* initialize pam */ if((res = pam_start(pam_servicename, userinfo.name, &conv_info, &pamh)) != PAM_SUCCESS) { ap_log_reason((char*)compat_pam_strerror(pamh, res), r->uri, r); return DECLINED; } /* 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_reason((char*)compat_pam_strerror(pamh, res), r->uri, r); 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 AUTH_REQUIRED; } /* 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_reason((char*)compat_pam_strerror(pamh, res), r->uri, r); pam_end(pamh, PAM_SUCCESS); return AUTH_REQUIRED; } pam_end(pamh, PAM_SUCCESS); return OK; } /* * Look if that user (as authenticated above) is allowed here */ static int pam_check_auth (request_rec *r) { register int i = 0; char method_restricted = 0, *line = 0, *word = 0; auth_pam_dir_config *conf = (auth_pam_dir_config*) ap_get_module_config(r->per_dir_config, &pam_auth_module); char *p_group = NULL; /* check for allowed users/group */ const array_header *reqs_arr = ap_requires (r); require_line *reqs = 0; /* enabled? */ if (!conf->enabled) return DECLINED; /* if any valid user suffices return success */ if (!reqs_arr) return (OK); /* otherwise */ reqs = (require_line*)reqs_arr->elts; /* loop over requirement lines */ for( i = 0; i < reqs_arr->nelts; i++) { /* if method of this requirement matches current method */ if (reqs[i].method_mask & (1 << r->method_number)) { method_restricted = 1; line = reqs[i].requirement; word = ap_getword(r->pool, (const char**)&line, ' '); /* if any user is ok */ if(!strcmp(word, valid_user)) return OK; /* loop over line */ if(!strcmp(word, "user")) { while(*line) { word = ap_getword_conf(r->pool, (const char**)&line); /* if username matches remote username */ if(!strcmp(r->connection->user, word)) /* return success */ return OK; } } /* end if user */ else if(!strcmp(word, "group")) { struct group *grent; char **members; if (!p_group) { struct passwd *pwent; if ((pwent = getpwnam (r->connection->user)) && (grent = getgrgid (pwent->pw_gid))) p_group = grent->gr_name; } while (*line) { word = ap_getword_conf(r->pool, (const char**)&line); if (p_group && !strcmp (p_group, word)) return OK; if ((grent = getgrnam (word)) && grent->gr_mem) { members = grent->gr_mem; while (*members) { if (!strcmp (*members, word)) return OK; members ++; } } } } /* end if group */ } } if (!method_restricted) return OK; ap_note_basic_auth_failure (r); return AUTH_REQUIRED; } module pam_auth_module = { STANDARD_MODULE_STUFF, auth_pam_init, /* initializer */ 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 */ NULL, /* handlers */ NULL, /* filename translation */ pam_auth_basic_user, /* check_user_id */ pam_check_auth, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL /* logger */ };