1 /* Copyright (c) 2000 Ingo Luetkebohle, All rights reserved.
2  *
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions
5  * are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  *
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in
12  *    the documentation and/or other materials provided with the
13  *    distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
16  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR OTHER CODE CONTRIBUTORS
19  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
20  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 /* $Id: mod_auth_pam.c,v 1.2.2.2 2002/08/24 14:22:23 netd Exp $
30  * mod_auth_pam for apache 2.0
31  *
32  * Based on: v 2.0-1.1 from 07. July 2002 for APACHE 2.0
33  *
34  *
35  * mod_auth_pam:
36  *  basic authentication against pluggable authentication module lib
37  *
38  * The authoritative homepage for this module is at
39  *	http://pam.sourceforge.net/
40  *
41  * For assistance and support, contact the pam-list
42  *	https://listman.redhat.com/mailman/listinfo/pam-list
43  * or the Help forum on SourceForge.
44  *
45  * Written by Ingo Luetkebohle, based upon mod_auth.c, with contributions
46  * from Fredrik Ohrn (group performance), Dirk-Willem van Gulik (licensing
47  * consultation) and Michael Johnson (example group implementation).
48  * Ported to Apache 2.0 by Dirk-Willem van Gulik.
49  *
50  * Thanks to Andrew Morgan and the rest of the Linux-PAM developers who
51  * provided invaluable development help and ideas.
52  *
53  * Changes:
54  *   24-Aug-02: Small bugfixes and build setup improved
55  *   09-Jul-02: Ported to apache 2.02 (Dirk-Willem van Gulik,
56  *              <dirkx@covalent.net>).
57  *   07-Jul-02: License changed to allow redistribution
58  *		Performance improvement for group lookup
59 		(many thanks to Fredrik Ohrn for the patch)
60  *   06-Dec-99: Special casing for Solaris 2.6 added
61  *              Added versioning message to headers
62  *   14-Feb-99: Cleaned up the configuration directives and named them
63  *		in a more straightforward way
64  *
65  *		incorporated getugroups patch from Klaus Wissmann <kw@aw.net>
66  *		to look up supplementary groups from /etc/group
67  *
68  *   22-Jan-99: incorporated changes from Pavel Kankovsky <kan@dcit.cz>
69  *		Enabler configuration directive now called AuthPAM_Enabled
70  *		Updated for Apache 1.3.x
71  *   25-Sep-98: replaced pwdb groups routine with standard C
72  *   19-Oct-97: made module fall through (if configured to do so),
73  * 		even when a user of the given name is found in the PAM
74  *		databases
75  *   17-Apr-97: fixed segfault that occured when Apache couldn't look
76  *              up the remote host name
77  *              removed annoying compiler warnings
78  *
79  *   05-Apr-97: made fall-through configurable with AuthPAM_Authorative
80  *
81  *   25-Mar-97: added support for transparent fall-through to other auth
82  *              modules with configurable fail delays
83  *              added acct_mgmt hook
84  *              added group support (through libpwdb)
85  *
86  * usage information:
87  *
88  * new-style (DSO and apache 2.0)
89  *
90  *	compile with
91  *		apxs -c -lpam mod_auth_pam.c
92  *
93  *		apxs -I/usr/local/include -L/usr/local/lib -c -lpam mod_auth_pam.c
94  *
95  * 	install with
96  *		apxs -i -a mod_auth_pam.so
97  *
98  *	configure PAM by adding
99  *		/etc/pam.d/httpd
100  *
101  *	with the appropriate pam modules (for starters, just copy over ftp)
102  *
103  *
104  * configuration directives:
105  * AuthFailDelay <msecs>
106  *                              number of mili-seconds to wait after a
107  *                              failed authentication attempt. this is
108  *                              a minimum value and may have been
109  *                              increased by other pam apps.
110  *                              defaults to 0
111  *				REQUIRES lib_pam SUPPORT
112  *
113  * AuthPAM_Enabled on|off
114  *                              If on, mod_auth_pam will try to authenticate
115  *				the user.
116  *				If off, mod_auth_pam will DECLINE immediately
117  *				instead of trying to authenticate the user.
118  *				This will make Apache try other modules.
119  *				Defaults to on
120  *
121  * AuthPAM_FallThrough
122  *				If on, makes mod_auth_pam DECLINE if it can't
123  *				the username, giving other modules a chance.
124  *				Please note that, if it DOES find the username,
125  *				and the password doesn't match, it will NOT
126  *				fall through but return "access denied" instead
127  *				Defaults to off
128  *
129  * AuthPAM_Authorative on|off   DEPRECATED
130  */
131 
132 #include <sys/types.h>
133 #include <pwd.h>		/* for getpwnam et.al. */
134 #include <grp.h>		/* for getpwnam et.al. */
135 #include <unistd.h>
136 
137 #include "ap_config.h"
138 #include "httpd.h"
139 #include "http_config.h"
140 #include "http_core.h"
141 #include "http_log.h"
142 #include "http_protocol.h"
143 #include "http_request.h"
144 
145 #include <security/pam_appl.h>
146 
147 /* change this to 0 on RedHat 4.x */
148 #define PAM_STRE_NEEDS_PAMH 1
149 #define VERSION "2.0-1.1"
150 
151 module auth_pam_module;
152 
153 static const char
154 	*pam_servicename = "httpd",
155 	*valid_user 	 = "valid-user";
156 
157 typedef struct {
158     char *name, *pw;
159 }      auth_pam_userinfo;
160 
161 /*
162  * Solaris 2.6.x has a broken conversation function and needs this
163  * as a global variable
164  * I refused to pollute code for other platforms with this,
165  * so all Solaris 2.6 specific stuff is if'd like the following
166  */
167 #if SOLARIS2 == 260
168 auth_pam_userinfo *global_userinfo;
169 #endif
170 
171 /*
172  * the pam_strerror function has different parameters in early PAM
173  * versions
174  */
175 #ifndef PAM_STRE_NEEDS_PAMH
176 #define compat_pam_strerror(pamh, res) pam_strerror(res)
177 #else
178 #define compat_pam_strerror(pamh, res) pam_strerror(pamh, res)
179 #endif
180 
181 /*
182  * configuration directive handling
183  */
184 
185 typedef struct {
186     int
187         fail_delay,		/* fail delay in ms -- needs library support */
188         fall_through,		/* 1 to DECLINE instead of
189 				 * HTTP_UNAUTHORIZEDif we can't find the
190 				 * username (defaults to 0) */
191         enabled;		/* 1 to use mod_auth_pam, 0 otherwise
192 				 * (defaults to 1) */
193 }      auth_pam_dir_config;
194 
195 static
auth_pam_init(apr_pool_t * p,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * s)196 int auth_pam_init(
197 		      apr_pool_t * p,
198 		      apr_pool_t * plog,
199 		      apr_pool_t * ptemp,
200 		      server_rec * s
201 )
202 {
203     ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "PAM: mod_auth_pam/" VERSION);
204 
205     return OK;
206 }
207 
208 static
create_auth_pam_dir_config(apr_pool_t * p,char * dummy)209 void *create_auth_pam_dir_config(apr_pool_t * p, char *dummy)
210 {
211     auth_pam_dir_config *new =
212     (auth_pam_dir_config *) apr_palloc(p, sizeof(auth_pam_dir_config));
213 
214     new->fail_delay = 0;	/* 0 ms */
215     new->fall_through = 0;	/* off */
216     new->enabled = 1;		/* on */
217     return new;
218 }
219 
220 static command_rec auth_pam_cmds[] = {
221 
222     AP_INIT_TAKE1("AuthPAM_FailDelay",
223 	ap_set_int_slot, (void *) APR_OFFSETOF(auth_pam_dir_config, fail_delay),
224 	OR_AUTHCFG,
225 	"number of micro seconds to wait after failed authentication "
226 	"attempt. (default is 0.)"),
227 
228     AP_INIT_FLAG("AuthPAM_FallThrough",
229 	 ap_set_flag_slot, (void *) APR_OFFSETOF(auth_pam_dir_config, fall_through),
230 	 OR_AUTHCFG,
231 	"on|off - determines if other authentication methods are attempted "
232 	 "if this one fails; (default is off.)"),
233 
234     AP_INIT_FLAG("AuthPAM_Enabled",
235 	 ap_set_flag_slot, (void *) APR_OFFSETOF(auth_pam_dir_config, enabled),
236 	 OR_AUTHCFG,
237 	 "on|off - determines if PAM authentication is enabled. "
238 	 "(default is on.)"),
239 
240     {NULL}
241 };
242 
243 /*
244  * auth_pam_talker: supply authentication information to PAM when asked
245  *
246  * Assumptions:
247  *   A password is asked for by requesting input without echoing
248  *   A username is asked for by requesting input _with_ echoing
249  *
250  */
251 static
auth_pam_talker(int num_msg,const struct pam_message ** msg,struct pam_response ** resp,void * appdata_ptr)252 int auth_pam_talker(int num_msg,
253 		        const struct pam_message ** msg,
254 		        struct pam_response ** resp,
255 		        void *appdata_ptr)
256 {
257     unsigned short i = 0;
258     auth_pam_userinfo *userinfo = (auth_pam_userinfo *) appdata_ptr;
259     struct pam_response *response = 0;
260 
261 #if SOLARIS2 == 260
262     if (!userinfo)
263 	userinfo = global_userinfo;
264     /* fprintf(stderr,"%s : %s", userinfo->name, userinfo->pw); */
265 #endif
266 
267     /* parameter sanity checking */
268     if (!resp || !msg || !userinfo)
269 	return PAM_CONV_ERR;
270 
271     /* allocate memory to store response */
272     response = malloc(num_msg * sizeof(struct pam_response));
273     if (!response)
274 	return PAM_CONV_ERR;
275 
276     /* copy values */
277     for (i = 0; i < num_msg; i++) {
278 	/* initialize to safe values */
279 	response[i].resp_retcode = 0;
280 	response[i].resp = 0;
281 
282 	/* select response based on requested output style */
283 	switch (msg[i]->msg_style) {
284 	case PAM_PROMPT_ECHO_ON:
285 	    /* on memory allocation failure, auth fails */
286 	    response[i].resp = strdup(userinfo->name);
287 	    break;
288 	case PAM_PROMPT_ECHO_OFF:
289 	    response[i].resp = strdup(userinfo->pw);
290 	    break;
291 	default:
292 	    if (response)
293 		free(response);
294 	    return PAM_CONV_ERR;
295 	}
296     }
297     /* everything okay, set PAM response values */
298     *resp = response;
299     return PAM_SUCCESS;
300 }
301 
302 /*
303  * These functions return 0 if client is OK, and proper error status
304  * if not... either AUTH_REQUIRED, if we made a check, and it failed, or
305  * SERVER_ERROR, if things are so totally confused that we couldn't
306  * figure out how to tell if the client is authorized or not.
307  *
308  * If they return DECLINED, and all other modules also decline, that's
309  * treated by the server core as a configuration error, logged and
310  * reported as such.
311  */
312 
313 /*
314  * Determine user ID, and check if it really is that user
315  */
316 static
pam_auth_basic_user(request_rec * r)317 int pam_auth_basic_user(request_rec * r)
318 {
319     int res = 0;
320     /* mod_auth_pam specific */
321     auth_pam_userinfo userinfo = {NULL, NULL};
322     auth_pam_dir_config *conf = (auth_pam_dir_config *)
323     ap_get_module_config(r->per_dir_config, &auth_pam_module);
324     /* PAM specific  */
325     struct pam_conv conv_info = {&auth_pam_talker, (void *) &userinfo};
326     pam_handle_t *pamh = NULL;
327 
328 #if SOLARIS2 == 260
329     global_userinfo = &userinfo;
330 #endif
331 
332     /* enabled? */
333     if (!conf->enabled)
334 	return DECLINED;
335 
336     /* read sent pw */
337     if ((res = ap_get_basic_auth_pw(r, (const char **) &(userinfo.pw))))
338 	return res;
339 
340     /* this is only set after get_basic_auth_pw was called */
341     userinfo.name = r->user;
342 
343     /* initialize pam */
344     if ((res = pam_start(pam_servicename,
345 			 userinfo.name,
346 			 &conv_info,
347 			 &pamh)) != PAM_SUCCESS) {
348 
349 	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
350 		      "PAM: Could not start pam service: %s",
351 		      compat_pam_strerror(pamh, res));
352 	return HTTP_INTERNAL_SERVER_ERROR;
353     }
354 
355     /* set fail delay */
356 #ifdef PAM_FAIL_DELAY
357     pam_fail_delay(pamh, conf->fail_delay);
358 #endif
359 
360     /* set remote user information */
361     /*
362      * this seems to cause segfaults in lots of cases -- disabled for now
363      * pam_set_item(pamh, PAM_USER, userinfo.name); pam_set_item(pamh,
364      * PAM_RHOST, get_remote_host(r->connection, conf, REMOTE_NAME));
365      */
366 
367     /* try to authenticate user, log error on failure */
368     if ((res = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK)) !=
369 	PAM_SUCCESS) {
370 	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
371 		      "PAM: user '%s' - not authenticated: %s", r->user, compat_pam_strerror(pamh, res));
372 
373 	if (conf->fall_through && (res == PAM_USER_UNKNOWN)) {
374 	    /* we don't know about the user, but other auth modules might do */
375 	    pam_end(pamh, PAM_SUCCESS);
376 	    return DECLINED;
377 	}
378 	else {
379 	    pam_end(pamh, PAM_SUCCESS);
380 	    ap_note_basic_auth_failure(r);
381 	    return HTTP_UNAUTHORIZED;
382 	}			/* endif fall_through */
383     }				/* endif authenticate */
384 
385     /* check that the account is healthy */
386     if ((res = pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK)) != PAM_SUCCESS) {
387 	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
388 		      "PAM: user '%s'  - invalid account: %s", r->user, compat_pam_strerror(pamh, res));
389 	pam_end(pamh, PAM_SUCCESS);
390 	return HTTP_UNAUTHORIZED;
391     }
392 
393     pam_end(pamh, PAM_SUCCESS);
394     return OK;
395 }
396 
pam_register_hooks(apr_pool_t * p)397 static void pam_register_hooks(apr_pool_t * p)
398 {
399     ap_hook_post_config(auth_pam_init, NULL, NULL, APR_HOOK_MIDDLE);
400     ap_hook_check_user_id(pam_auth_basic_user, NULL, NULL, APR_HOOK_MIDDLE);
401 }
402 
403 module AP_MODULE_DECLARE_DATA auth_pam_module = {
404     STANDARD20_MODULE_STUFF,
405     create_auth_pam_dir_config,	/* dir config creater */
406     NULL,			/* dir merger --- default is to override */
407     NULL,			/* server config */
408     NULL,			/* merge server config */
409     auth_pam_cmds,		/* command table */
410     pam_register_hooks,		/* register hooks */
411 };
412