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