1 /*
2  * Copyright 2003,2004,2005,2006,2007,2008,2009,2011,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 #ifdef HAVE_SECURITY_PAM_APPL_H
36 #include <security/pam_appl.h>
37 #endif
38 
39 #ifdef HAVE_SECURITY_PAM_MODULES_H
40 #define PAM_SM_SESSION
41 #include <security/pam_modules.h>
42 #endif
43 
44 #include <limits.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 
50 #include KRB5_H
51 
52 #include "init.h"
53 #include "log.h"
54 #include "options.h"
55 #include "prompter.h"
56 #include "session.h"
57 #include "shmem.h"
58 #include "stash.h"
59 #include "tokens.h"
60 #include "userinfo.h"
61 #include "v5.h"
62 #include "xstr.h"
63 
64 int
65 _pam_krb5_open_session(pam_handle_t *pamh, int flags,
66 		       int argc, PAM_KRB5_MAYBE_CONST char **argv,
67 		       const char *caller,
68 		       enum _pam_krb5_session_caller caller_type)
69 {
70 	PAM_KRB5_MAYBE_CONST char *user;
71 	char envstr[PATH_MAX + 20], *segname;
72 	const char *ccname;
73 	krb5_context ctx;
74 	struct _pam_krb5_options *options;
75 	struct _pam_krb5_user_info *userinfo;
76 	struct _pam_krb5_stash *stash;
77 	int i, retval;
78 
79 	/* Initialize Kerberos. */
80 	if (_pam_krb5_init_ctx(&ctx, argc, argv) != 0) {
81 		warn("error initializing Kerberos");
82 		return PAM_SERVICE_ERR;
83 	}
84 
85 	/* Get the user's name. */
86 	i = pam_get_user(pamh, &user, NULL);
87 	if ((i != PAM_SUCCESS) || (user == NULL)) {
88 		warn("could not identify user name");
89 		_pam_krb5_free_ctx(ctx);
90 		return i;
91 	}
92 
93 	/* Read our options. */
94 	options = _pam_krb5_options_init(pamh, argc, argv, ctx,
95 					 _pam_krb5_option_role_general);
96 	if (options == NULL) {
97 		warn("error parsing options (shouldn't happen)");
98 		_pam_krb5_free_ctx(ctx);
99 		return PAM_SERVICE_ERR;
100 	}
101 
102 	/* If we're in a no-cred-session situation, return. */
103 	if ((!options->cred_session) &&
104 	    (caller_type == _pam_krb5_session_caller_setcred)) {
105 		_pam_krb5_options_free(pamh, ctx, options);
106 		_pam_krb5_free_ctx(ctx);
107 		return PAM_SUCCESS;
108 	}
109 
110 	/* Get information about the user and the user's principal name. */
111 	userinfo = _pam_krb5_user_info_init(ctx, user, options);
112 	if (userinfo == NULL) {
113 		if (options->debug) {
114 			debug("no user info for '%s'", user);
115 		}
116 		if (options->ignore_unknown_principals) {
117 			retval = PAM_IGNORE;
118 		} else {
119 			retval = PAM_USER_UNKNOWN;
120 		}
121 		if (options->debug) {
122 			debug("%s returning %d (%s)", caller,
123 			      retval,
124 			      pam_strerror(pamh, retval));
125 		}
126 		_pam_krb5_options_free(pamh, ctx, options);
127 		_pam_krb5_free_ctx(ctx);
128 		return retval;
129 	}
130 	if ((options->user_check) &&
131 	    (options->minimum_uid != (uid_t)-1) &&
132 	    (userinfo->uid < options->minimum_uid)) {
133 		if (options->debug) {
134 			debug("ignoring '%s' -- uid below minimum = %lu", user,
135 			      (unsigned long) options->minimum_uid);
136 		}
137 		_pam_krb5_user_info_free(ctx, userinfo);
138 		if (options->debug) {
139 			debug("%s returning %d (%s)", caller, PAM_IGNORE,
140 			      pam_strerror(pamh, PAM_IGNORE));
141 		}
142 		_pam_krb5_options_free(pamh, ctx, options);
143 		_pam_krb5_free_ctx(ctx);
144 		return PAM_IGNORE;
145 	}
146 
147 	/* Get the stash for this user. */
148 	stash = _pam_krb5_stash_get(pamh, user, userinfo, options);
149 	if (stash == NULL) {
150 		warn("no stash for '%s' (shouldn't happen)", user);
151 		_pam_krb5_user_info_free(ctx, userinfo);
152 		if (options->debug) {
153 			debug("%s returning %d (%s)", caller,
154 			      PAM_SERVICE_ERR,
155 			      pam_strerror(pamh, PAM_SERVICE_ERR));
156 		}
157 		_pam_krb5_options_free(pamh, ctx, options);
158 		_pam_krb5_free_ctx(ctx);
159 		return PAM_SERVICE_ERR;
160 	}
161 
162 	/* We don't need the shared memory segments any more, so we can get rid
163 	 * of them now.  (Depending on the application, we may not get a chance
164 	 * to do it later.) */
165 	if (options->use_shmem) {
166 		if ((stash->v5shm != -1) && (stash->v5shm_owner != -1)) {
167 			if (options->debug) {
168 				debug("removing shared memory segment %d"
169 				      " creator pid %ld",
170 				      stash->v5shm, (long) stash->v5shm_owner);
171 			}
172 			_pam_krb5_shm_remove(stash->v5shm_owner, stash->v5shm,
173 					     options->debug);
174 			stash->v5shm = -1;
175 			_pam_krb5_stash_shm_var_name(options, user, &segname);
176 			if (segname != NULL) {
177 				pam_putenv(pamh, segname);
178 				free(segname);
179 			}
180 		}
181 	}
182 
183 	/* If we don't have any credentials, then we're done. */
184 	if ((stash->v5attempted == 0) || (stash->v5result != 0)) {
185 		if (options->debug) {
186 			debug("no creds for user '%s', "
187 			      "skipping session setup", user);
188 		}
189 		_pam_krb5_user_info_free(ctx, userinfo);
190 		if (options->debug) {
191 			debug("%s returning %d (%s)", caller, PAM_SUCCESS,
192 			      pam_strerror(pamh, PAM_SUCCESS));
193 		}
194 		_pam_krb5_options_free(pamh, ctx, options);
195 		_pam_krb5_free_ctx(ctx);
196 		return PAM_SUCCESS;
197 	}
198 
199 	/* Obtain tokens, if necessary. */
200 	if ((i == PAM_SUCCESS) &&
201 	    (options->ignore_afs == 0) &&
202 	    tokens_useful()) {
203 		tokens_obtain(ctx, stash, options, userinfo, 1);
204 	}
205 
206 	/* Create the user's credential cache, but only if we didn't pick them
207 	 * up from our calling process. */
208 	if (!stash->v5external) {
209 		if (options->debug) {
210 #ifdef HAVE_LONG_LONG
211 			debug("creating ccache for '%s', uid=%llu, gid=%llu",
212 			      user,
213 			      options->user_check ?
214 			      (unsigned long long) userinfo->uid :
215 			      (unsigned long long) getuid(),
216 			      options->user_check ?
217 			      (unsigned long long) userinfo->gid :
218 			      (unsigned long long) getgid());
219 #else
220 			debug("creating ccache for '%s', uid=%lu, gid=%lu",
221 			      user,
222 			      options->user_check ?
223 			      (unsigned long) userinfo->uid :
224 			      (unsigned long) getuid(),
225 			      options->user_check ?
226 			      (unsigned long) userinfo->gid :
227 			      (unsigned long) getgid());
228 #endif
229 		}
230 		i = v5_save_for_user(ctx, stash, user, userinfo,
231 				     options, &ccname);
232 		if ((i == PAM_SUCCESS) && (strlen(ccname) > 0)) {
233 			sprintf(envstr, "KRB5CCNAME=%s", ccname);
234 			pam_putenv(pamh, envstr);
235 			stash->v5setenv = 1;
236 		} else {
237 			if (options->debug) {
238 				debug("failed to create ccache for '%s'", user);
239 			}
240 		}
241 	}
242 
243 	/* If we didn't create ccache files because we couldn't, just
244 	 * pretend everything's fine. */
245 	if ((i != PAM_SUCCESS) &&
246 	    (v5_ccache_has_tgt(ctx, stash->v5ccache,
247 			       options->realm, NULL) != 0)) {
248 		i = PAM_SUCCESS;
249 	}
250 
251 	/* Clean up. */
252 	if (options->debug) {
253 		debug("%s returning %d (%s)", caller, i,
254 		      pam_strerror(pamh, i));
255 	}
256 	_pam_krb5_options_free(pamh, ctx, options);
257 	_pam_krb5_user_info_free(ctx, userinfo);
258 
259 
260 	_pam_krb5_free_ctx(ctx);
261 	return i;
262 }
263 
264 int
265 _pam_krb5_close_session(pam_handle_t *pamh, int flags,
266 			int argc, PAM_KRB5_MAYBE_CONST char **argv,
267 		        const char *caller,
268 		        enum _pam_krb5_session_caller caller_type)
269 {
270 	PAM_KRB5_MAYBE_CONST char *user;
271 	krb5_context ctx;
272 	struct _pam_krb5_options *options;
273 	struct _pam_krb5_user_info *userinfo;
274 	struct _pam_krb5_stash *stash;
275 	int i, retval;
276 
277 	/* Initialize Kerberos. */
278 	if (_pam_krb5_init_ctx(&ctx, argc, argv) != 0) {
279 		warn("error initializing Kerberos");
280 		return PAM_SERVICE_ERR;
281 	}
282 
283 	/* Get the user's name. */
284 	i = pam_get_user(pamh, &user, NULL);
285 	if (i != PAM_SUCCESS) {
286 		warn("could not determine user name");
287 		_pam_krb5_free_ctx(ctx);
288 		return i;
289 	}
290 
291 	/* Read our options. */
292 	options = _pam_krb5_options_init(pamh, argc, argv, ctx,
293 					 _pam_krb5_option_role_general);
294 	if (options == NULL) {
295 		_pam_krb5_free_ctx(ctx);
296 		return PAM_SERVICE_ERR;
297 	}
298 
299 	/* If we're in a no-cred-session situation, return. */
300 	if ((!options->cred_session) &&
301 	    (caller_type == _pam_krb5_session_caller_setcred)) {
302 		_pam_krb5_options_free(pamh, ctx, options);
303 		_pam_krb5_free_ctx(ctx);
304 		return PAM_SUCCESS;
305 	}
306 
307 	/* Get information about the user and the user's principal name. */
308 	userinfo = _pam_krb5_user_info_init(ctx, user, options);
309 	if (userinfo == NULL) {
310 		if (options->ignore_unknown_principals) {
311 			retval = PAM_IGNORE;
312 		} else {
313 			warn("no user info for %s (shouldn't happen)", user);
314 			retval = PAM_USER_UNKNOWN;
315 		}
316 		if (options->debug) {
317 			debug("%s returning %d (%s)", caller,
318 			      retval,
319 			      pam_strerror(pamh, retval));
320 		}
321 		_pam_krb5_options_free(pamh, ctx, options);
322 		_pam_krb5_free_ctx(ctx);
323 		return retval;
324 	}
325 
326 	/* Check the minimum UID argument. */
327 	if ((options->user_check) &&
328 	    (options->minimum_uid != (uid_t)-1) &&
329 	    (userinfo->uid < options->minimum_uid)) {
330 		if (options->debug) {
331 			debug("ignoring '%s' -- uid below minimum", user);
332 		}
333 		_pam_krb5_user_info_free(ctx, userinfo);
334 		if (options->debug) {
335 			debug("%s returning %d (%s)", caller, PAM_IGNORE,
336 			      pam_strerror(pamh, PAM_IGNORE));
337 		}
338 		_pam_krb5_options_free(pamh, ctx, options);
339 		_pam_krb5_free_ctx(ctx);
340 		return PAM_IGNORE;
341 	}
342 
343 	/* Get the stash for this user. */
344 	stash = _pam_krb5_stash_get(pamh, user, userinfo, options);
345 	if (stash == NULL) {
346 		warn("no stash for user %s (shouldn't happen)", user);
347 		_pam_krb5_user_info_free(ctx, userinfo);
348 		if (options->debug) {
349 			debug("%s returning %d (%s)", caller,
350 			      PAM_SERVICE_ERR,
351 			      pam_strerror(pamh, PAM_SERVICE_ERR));
352 		}
353 		_pam_krb5_options_free(pamh, ctx, options);
354 		_pam_krb5_free_ctx(ctx);
355 		return PAM_SERVICE_ERR;
356 	}
357 
358 	/* If we didn't obtain any credentials, then we're done. */
359 	if ((stash->v5attempted == 0) || (stash->v5result != 0)) {
360 		if (options->debug) {
361 			debug("no creds for user '%s', "
362 			      "skipping session cleanup",
363 			      user);
364 		}
365 		_pam_krb5_user_info_free(ctx, userinfo);
366 		if (options->debug) {
367 			debug("%s returning %d (%s)", caller,
368 			      PAM_SUCCESS,
369 			      pam_strerror(pamh, PAM_SUCCESS));
370 		}
371 		_pam_krb5_options_free(pamh, ctx, options);
372 		_pam_krb5_free_ctx(ctx);
373 		return PAM_SUCCESS;
374 	}
375 
376 	if (options->ignore_afs == 0) {
377 		tokens_release(stash, options);
378 	}
379 
380 	if (!stash->v5external) {
381 		if (stash->v5ccnames != NULL) {
382 			v5_destroy(ctx, stash, options);
383 			if (stash->v5setenv) {
384 				pam_putenv(pamh, "KRB5CCNAME");
385 				stash->v5setenv = 0;
386 			}
387 		}
388 	} else {
389 		if (options->debug) {
390 			debug("leaving external ccache for '%s'", user);
391 		}
392 	}
393 
394 	_pam_krb5_user_info_free(ctx, userinfo);
395 	if (options->debug) {
396 		debug("%s returning %d (%s)", caller,
397 		      PAM_SUCCESS,
398 		      pam_strerror(pamh, PAM_SUCCESS));
399 	}
400 	_pam_krb5_options_free(pamh, ctx, options);
401 	_pam_krb5_free_ctx(ctx);
402 	return PAM_SUCCESS;
403 }
404 
405 int
406 pam_sm_open_session(pam_handle_t *pamh, int flags,
407 		    int argc, PAM_KRB5_MAYBE_CONST char **argv)
408 {
409 	return _pam_krb5_open_session(pamh, flags, argc, argv,
410 				      "pam_sm_open_session",
411 				      _pam_krb5_session_caller_session);
412 }
413 
414 int
415 pam_sm_close_session(pam_handle_t *pamh, int flags,
416 		     int argc, PAM_KRB5_MAYBE_CONST char **argv)
417 {
418 	return _pam_krb5_close_session(pamh, flags, argc, argv,
419 				       "pam_sm_close_session",
420 				       _pam_krb5_session_caller_session);
421 }
422