1 /*
2  * Copyright 2003,2004,2005,2006,2007,2008,2009,2010,2012,2013,2014 Red Hat, Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, and the entire permission notice in its entirety,
9  *    including the disclaimer of warranties.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. The name of the author may not be used to endorse or promote
14  *    products derived from this software without specific prior
15  *    written permission.
16  *
17  * ALTERNATIVELY, this product may be distributed under the terms of the
18  * GNU Lesser General Public License, in which case the provisions of the
19  * LGPL are required INSTEAD OF the above restrictions.
20  *
21  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
22  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
23  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
24  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "../config.h"
34 
35 #include <sys/types.h>
36 #include <errno.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 
41 #ifdef HAVE_SECURITY_PAM_APPL_H
42 #include <security/pam_appl.h>
43 #endif
44 
45 #ifdef HAVE_SECURITY_PAM_MODULES_H
46 #define PAM_SM_AUTH
47 #define PAM_SM_SESSION
48 #include <security/pam_modules.h>
49 #endif
50 
51 #include KRB5_H
52 
53 #include "conv.h"
54 #include "init.h"
55 #include "initopts.h"
56 #include "items.h"
57 #include "kuserok.h"
58 #include "log.h"
59 #include "options.h"
60 #include "prompter.h"
61 #include "session.h"
62 #include "sly.h"
63 #include "stash.h"
64 #include "tokens.h"
65 #include "userinfo.h"
66 #include "v5.h"
67 #include "xstr.h"
68 
69 int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,PAM_KRB5_MAYBE_CONST char ** argv)70 pam_sm_authenticate(pam_handle_t *pamh, int flags,
71 		    int argc, PAM_KRB5_MAYBE_CONST char **argv)
72 {
73 	PAM_KRB5_MAYBE_CONST char *user;
74 	krb5_context ctx;
75 	struct _pam_krb5_options *options;
76 	struct _pam_krb5_user_info *userinfo;
77 	struct _pam_krb5_stash *stash;
78 	krb5_get_init_creds_opt *gic_options;
79 	int i, retval, use_third_pass, prompted, prompt_result;
80 	char *first_pass, *second_pass;
81 
82 	/* Initialize Kerberos. */
83 	if (_pam_krb5_init_ctx(&ctx, argc, argv) != 0) {
84 		warn("error initializing Kerberos");
85 		return PAM_SERVICE_ERR;
86 	}
87 
88 	/* Get the user's name. */
89 	i = pam_get_user(pamh, &user, NULL);
90 	if ((i != PAM_SUCCESS) || (user == NULL)) {
91 		warn("could not identify user name");
92 		_pam_krb5_free_ctx(ctx);
93 		return i;
94 	}
95 
96 	/* Read our options. */
97 	i = v5_alloc_get_init_creds_opt(ctx, &gic_options);
98 	if (i != 0) {
99 		warn("error initializing options (shouldn't happen)");
100 		_pam_krb5_free_ctx(ctx);
101 		return PAM_SERVICE_ERR;
102 	}
103 	options = _pam_krb5_options_init(pamh, argc, argv, ctx,
104 					 _pam_krb5_option_role_general);
105 	if (options == NULL) {
106 		warn("error parsing options (shouldn't happen)");
107 		v5_free_get_init_creds_opt(ctx, gic_options);
108 		_pam_krb5_free_ctx(ctx);
109 		return PAM_SERVICE_ERR;
110 	}
111 	if (options->debug) {
112 		debug("called to authenticate '%s', configured realm '%s'",
113 		      user, options->realm);
114 	}
115 	_pam_krb5_set_init_opts(ctx, gic_options, options);
116 
117 	/* Prompt for the password, as we might need to. */
118 	prompted = 0;
119 	prompt_result = PAM_ABORT;
120 	second_pass = NULL;
121 	if (options->use_second_pass) {
122 		first_pass = NULL;
123 		i = _pam_krb5_get_item_text(pamh, PAM_AUTHTOK, &first_pass);
124 		if ((i != PAM_SUCCESS) || (first_pass == NULL)) {
125 			/* Nobody's asked for a password yet. */
126 			prompt_result = _pam_krb5_prompt_for(pamh,
127 							     Y_("Password: "),
128 							     &second_pass);
129 			prompted = 1;
130 		}
131 	}
132 
133 	/* Get information about the user and the user's principal name. */
134 	userinfo = _pam_krb5_user_info_init(ctx, user, options);
135 	if (userinfo == NULL) {
136 		if (options->ignore_unknown_principals) {
137 			retval = PAM_IGNORE;
138 		} else {
139 			warn("error getting information about '%s'", user);
140 			retval = PAM_USER_UNKNOWN;
141 		}
142 		if (prompted && (prompt_result == 0) && (second_pass != NULL)) {
143 			if (options->debug) {
144 				debug("saving newly-entered "
145 				      "password for use by "
146 				      "other modules");
147 			}
148 			pam_set_item(pamh, PAM_AUTHTOK, second_pass);
149 		}
150 		/* Clean up and return. */
151 		_pam_krb5_options_free(pamh, ctx, options);
152 		v5_free_get_init_creds_opt(ctx, gic_options);
153 		_pam_krb5_free_ctx(ctx);
154 		return retval;
155 	}
156 	if (options->debug) {
157 		debug("authenticating '%s'", userinfo->unparsed_name);
158 	}
159 
160 	/* Check the minimum UID argument. */
161 	if ((options->user_check) &&
162 	    (options->minimum_uid != (uid_t) -1) &&
163 	    (userinfo->uid < options->minimum_uid)) {
164 		if (options->debug) {
165 			debug("ignoring '%s' -- uid below minimum = %lu", user,
166 			      (unsigned long) options->minimum_uid);
167 		}
168 		_pam_krb5_user_info_free(ctx, userinfo);
169 		if (prompted && (prompt_result == 0) && (second_pass != NULL)) {
170 			if (options->debug) {
171 				debug("saving newly-entered "
172 				      "password for use by "
173 				      "other modules");
174 			}
175 			pam_set_item(pamh, PAM_AUTHTOK, second_pass);
176 		}
177 		_pam_krb5_options_free(pamh, ctx, options);
178 		v5_free_get_init_creds_opt(ctx, gic_options);
179 		_pam_krb5_free_ctx(ctx);
180 		return PAM_IGNORE;
181 	}
182 
183 	/* Get the stash for this user. */
184 	stash = _pam_krb5_stash_get(pamh, user, userinfo, options);
185 	if (stash == NULL) {
186 		warn("error retrieving stash for '%s' (shouldn't happen)",
187 		     user);
188 		_pam_krb5_user_info_free(ctx, userinfo);
189 		if (prompted && (prompt_result == 0) && (second_pass != NULL)) {
190 			if (options->debug) {
191 				debug("saving newly-entered "
192 				      "password for use by "
193 				      "other modules");
194 			}
195 			pam_set_item(pamh, PAM_AUTHTOK, second_pass);
196 		}
197 		_pam_krb5_options_free(pamh, ctx, options);
198 		v5_free_get_init_creds_opt(ctx, gic_options);
199 		_pam_krb5_free_ctx(ctx);
200 		return PAM_SERVICE_ERR;
201 	}
202 
203 	/* If we've been called before, then the stash is more or less stale,
204 	 * so reset things for applications which call pam_authenticate() more
205 	 * than once with the same library context. */
206 	stash->v5attempted = 0;
207 
208 	retval = PAM_AUTH_ERR;
209 
210 	/* Ideally we're only going to let libkrb5 ask questions once, and
211 	 * after that we intend to lie to it. */
212 	use_third_pass = options->use_third_pass;
213 
214 	/* Try with the stored password, if we've been told to use just that
215 	 * value. */
216 	first_pass = NULL;
217 	if ((retval != PAM_SUCCESS) && options->use_first_pass) {
218 		i = _pam_krb5_get_item_text(pamh, PAM_AUTHTOK, &first_pass);
219 		if ((i == PAM_SUCCESS) &&
220 		    (flags & PAM_DISALLOW_NULL_AUTHTOK) &&
221 		    (first_pass != NULL) &&
222 		    (strlen(first_pass) == 0)) {
223 			warn("disallowing NULL authtok for '%s'", user);
224 			retval = PAM_AUTH_ERR;
225 			i = PAM_AUTH_ERR;
226 		}
227 		if ((i == PAM_SUCCESS) &&
228 		    (first_pass != NULL) &&
229 		    (strlen(first_pass) > 0)) {
230 			if (options->debug) {
231 				if (use_third_pass) {
232 					debug("trying previously-entered "
233 					      "password for '%s', allowing "
234 					      "libkrb5 to prompt for more",
235 					      user);
236 				} else {
237 					debug("trying previously-entered "
238 					      "password for '%s'", user);
239 				}
240 			}
241 			retval = v5_get_creds(stash->v5ctx, pamh,
242 					      &stash->v5ccache,
243 					      &stash->v5armorccache,
244 					      user, userinfo,
245 					      options,
246 					      KRB5_TGS_NAME,
247 					      first_pass,
248 					      gic_options,
249 					      use_third_pass ?
250 					      _pam_krb5_normal_prompter :
251 					      _pam_krb5_previous_prompter,
252 					      &stash->v5expired,
253 					      &stash->v5result);
254 			use_third_pass = 0;
255 			stash->v5external = 0;
256 			stash->v5attempted = 1;
257 			if (options->debug) {
258 				debug("got result %d (%s)", stash->v5result,
259 				      v5_error_message(stash->v5result));
260 			}
261 		}
262 		if ((retval == PAM_SUCCESS) &&
263 		    (options->ignore_afs == 0) &&
264 		    (options->tokens == 1) &&
265 		    tokens_useful()) {
266 			tokens_obtain(ctx, stash, options, userinfo, 1);
267 		}
268 	}
269 
270 	/* If that didn't work, and we're allowed to ask for a new password, do
271 	 * so in preparation for another attempt. */
272 	if ((retval != PAM_SUCCESS) &&
273 	    (retval != PAM_USER_UNKNOWN) &&
274 	    options->use_second_pass) {
275 		/* The "second_pass" variable already contains a value if we
276 		 * asked for one. */
277 		if (!prompted) {
278 			prompt_result = _pam_krb5_prompt_for(pamh,
279 							     Y_("Password: "),
280 							     &second_pass);
281 			prompted = 1;
282 		}
283 		i = prompt_result;
284 		if ((i == PAM_SUCCESS) &&
285 		    (flags & PAM_DISALLOW_NULL_AUTHTOK) &&
286 		    (second_pass != NULL) &&
287 		    (strlen(second_pass) == 0)) {
288 			warn("disallowing NULL authtok for '%s'", user);
289 			retval = PAM_AUTH_ERR;
290 			i = PAM_AUTH_ERR;
291 		}
292 		if ((i == PAM_SUCCESS) &&
293 		    (second_pass != NULL) &&
294 		    (strlen(second_pass) > 0)) {
295 			/* Save the password for the next module. */
296 			if (options->debug) {
297 				debug("saving newly-entered "
298 				      "password for use by "
299 				      "other modules");
300 			}
301 			pam_set_item(pamh, PAM_AUTHTOK, second_pass);
302 			if (options->debug) {
303 				if (use_third_pass) {
304 					debug("trying newly-entered "
305 					      "password for '%s', allowing "
306 					      "libkrb5 to prompt for more",
307 					      user);
308 				} else {
309 					debug("trying newly-entered "
310 					      "password for '%s'", user);
311 				}
312 			}
313 			retval = v5_get_creds(stash->v5ctx, pamh,
314 					      &stash->v5ccache,
315 					      &stash->v5armorccache,
316 					      user, userinfo,
317 					      options,
318 					      KRB5_TGS_NAME,
319 					      second_pass,
320 					      gic_options,
321 					      use_third_pass ?
322 					      _pam_krb5_normal_prompter :
323 					      _pam_krb5_always_fail_prompter,
324 					      &stash->v5expired,
325 					      &stash->v5result);
326 			use_third_pass = 0;
327 			stash->v5external = 0;
328 			stash->v5attempted = 1;
329 			if (options->debug) {
330 				debug("got result %d (%s)", stash->v5result,
331 				      v5_error_message(stash->v5result));
332 			}
333 		}
334 		if ((retval == PAM_SUCCESS) &&
335 		    (options->ignore_afs == 0) &&
336 		    (options->tokens == 1) &&
337 		    tokens_useful()) {
338 			tokens_obtain(ctx, stash, options, userinfo, 1);
339 		}
340 	}
341 
342 	/* If we didn't use the first password (because it wasn't set), and we
343 	 * didn't ask for a password (due to the "no_initial_prompt" flag,
344 	 * probably), and we can let libkrb5 ask questions (no
345 	 * "no_subsequent_prompt"), then let libkrb5 have another go. */
346 	if ((retval != PAM_SUCCESS) &&
347 	    (retval != PAM_USER_UNKNOWN) &&
348 	    use_third_pass) {
349 		if (options->debug) {
350 			debug("not using an entered password for '%s', "
351 			      "allowing libkrb5 to prompt for more", user);
352 		}
353 		retval = v5_get_creds(stash->v5ctx, pamh,
354 				      &stash->v5ccache,
355 				      &stash->v5armorccache,
356 				      user, userinfo,
357 				      options,
358 				      KRB5_TGS_NAME,
359 				      NULL,
360 				      gic_options,
361 				      options->permit_password_callback ?
362 				      _pam_krb5_always_prompter :
363 				      _pam_krb5_normal_prompter,
364 				      &stash->v5expired,
365 				      &stash->v5result);
366 		stash->v5external = 0;
367 		stash->v5attempted = 1;
368 		if (options->debug) {
369 			debug("got result %d (%s)", stash->v5result,
370 			      v5_error_message(stash->v5result));
371 		}
372 		if ((retval == PAM_SUCCESS) &&
373 		    (options->ignore_afs == 0) &&
374 		    (options->tokens == 1) &&
375 		    tokens_useful()) {
376 			tokens_obtain(ctx, stash, options, userinfo, 1);
377 		}
378 	}
379 
380 	/* If we got this far, check the target user's .k5login file. */
381 	if ((retval == PAM_SUCCESS) && options->user_check &&
382 	    (options->ignore_k5login == 0)) {
383 		if (_pam_krb5_kuserok(ctx, stash, options, userinfo, user,
384 				      userinfo->uid, userinfo->gid) != TRUE) {
385 			notice("account checks fail for '%s': user disallowed "
386 			       "by .k5login file for '%s'",
387 			       userinfo->unparsed_name, user);
388 			retval = PAM_PERM_DENIED;
389 		} else {
390 			if (options->debug) {
391 				debug("'%s' passes .k5login check for '%s'",
392 				      userinfo->unparsed_name, user);
393 			}
394 		}
395 	}
396 
397 	/* Log the authentication status, optionally saving the credentials in
398 	 * a piece of shared memory. */
399 	if (retval == PAM_SUCCESS) {
400 		if (options->use_shmem) {
401 			_pam_krb5_stash_shm_write(pamh, stash, options,
402 						  user, userinfo);
403 		}
404 		notice("authentication succeeds for '%s' (%s)", user,
405 		       userinfo->unparsed_name);
406 	} else {
407 		if ((retval == PAM_USER_UNKNOWN) &&
408 		    options->ignore_unknown_principals) {
409 			retval = PAM_IGNORE;
410 		} else {
411 			notice("authentication fails for '%s' (%s): %s (%s)",
412 			       user,
413 			       userinfo->unparsed_name,
414 			       pam_strerror(pamh, retval),
415 			       v5_error_message(stash->v5result));
416 		}
417 	}
418 
419 	/* Clean up. */
420 	if (options->debug) {
421 		debug("pam_authenticate returning %d (%s)", retval,
422 		      pam_strerror(pamh, retval));
423 	}
424 	v5_free_get_init_creds_opt(ctx, gic_options);
425 	_pam_krb5_options_free(pamh, ctx, options);
426 	_pam_krb5_user_info_free(ctx, userinfo);
427 	_pam_krb5_free_ctx(ctx);
428 
429 	return retval;
430 }
431 
432 int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,PAM_KRB5_MAYBE_CONST char ** argv)433 pam_sm_setcred(pam_handle_t *pamh, int flags,
434 	       int argc, PAM_KRB5_MAYBE_CONST char **argv)
435 {
436 	const char *why = "";
437 	if (flags & PAM_ESTABLISH_CRED) {
438 		return _pam_krb5_open_session(pamh, flags, argc, argv,
439 					      "pam_setcred(PAM_ESTABLISH_CRED)",
440 					      _pam_krb5_session_caller_setcred);
441 	}
442 	if (flags & (PAM_REINITIALIZE_CRED | PAM_REFRESH_CRED)) {
443 		if (flags & PAM_REINITIALIZE_CRED) {
444 			why = "pam_setcred(PAM_REINITIALIZE_CRED)";
445 			if (flags & PAM_REFRESH_CRED) {
446 				why = "pam_setcred(PAM_REINITIALIZE_CRED|PAM_REFRESH_CRED)";
447 			}
448 		} else {
449 			why = "pam_setcred(PAM_REFRESH_CRED)";
450 		}
451 		if (_pam_krb5_sly_looks_unsafe() == 0) {
452 			return _pam_krb5_sly_maybe_refresh(pamh, flags, why,
453 							   argc, argv);
454 		} else {
455 			return PAM_IGNORE;
456 		}
457 	}
458 	if (flags & PAM_DELETE_CRED) {
459 		return _pam_krb5_close_session(pamh, flags, argc, argv,
460 					       "pam_setcred(PAM_DELETE_CRED)",
461 					       _pam_krb5_session_caller_setcred);
462 	}
463 	warn("pam_setcred() called with no flags");
464 	return PAM_SERVICE_ERR;
465 }
466