xref: /illumos-gate/usr/src/lib/pam_modules/krb5/krb5_acct_mgmt.c (revision 150d2c5288c645a1c1a7d2bee61199a3729406c7)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 
29 #include <kadm5/admin.h>
30 #include <krb5.h>
31 
32 #include <security/pam_appl.h>
33 #include <security/pam_modules.h>
34 #include <security/pam_impl.h>
35 #include <syslog.h>
36 #include <string.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <sys/types.h>
40 #include <pwd.h>
41 #include <libintl.h>
42 #include <netdb.h>
43 #include "utils.h"
44 #include <shadow.h>
45 
46 #include "krb5_repository.h"
47 
48 #define	KRB5_AUTOMIGRATE_DATA	"SUNW-KRB5-AUTOMIGRATE-DATA"
49 
50 /*
51  * pam_sm_acct_mgmt	  main account managment routine.
52  */
53 
54 static int
55 fetch_princ_entry(
56 	char *princ_str,
57 	char *password,
58 	kadm5_principal_ent_rec *prent,	/* out */
59 	krb5_timestamp *now, /* out */
60 	int debug)
61 
62 {
63 	kadm5_ret_t		code;
64 	krb5_principal 		princ = 0;
65 	char 			admin_realm[1024];
66 	char			kprinc[2*MAXHOSTNAMELEN];
67 	char			*cpw_service;
68 	void 			*server_handle;
69 	krb5_context		context;
70 	kadm5_config_params	params;
71 
72 	if (code = krb5_init_context(&context)) {
73 		return (PAM_SYSTEM_ERR);
74 	}
75 
76 	if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc,
77 		2*MAXHOSTNAMELEN)) != 0) {
78 		return (code);
79 	}
80 
81 	code = krb5_parse_name(context, kprinc, &princ);
82 	if (code != 0) {
83 		krb5_free_context(context);
84 		return (PAM_SYSTEM_ERR);
85 	}
86 
87 	if (strlen(password) == 0) {
88 		krb5_free_principal(context, princ);
89 		krb5_free_context(context);
90 		if (debug)
91 			__pam_log(LOG_AUTH | LOG_DEBUG,
92 			    "PAM-KRB5 (acct): fetch_princ_entry: pwlen=0");
93 		return (PAM_AUTH_ERR);
94 	}
95 
96 	(void) strlcpy(admin_realm,
97 		    krb5_princ_realm(context, princ)->data,
98 		    sizeof (admin_realm));
99 
100 	(void) memset((char *)&params, 0, sizeof (params));
101 	params.mask |= KADM5_CONFIG_REALM;
102 	params.realm = admin_realm;
103 
104 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
105 		__pam_log(LOG_AUTH | LOG_ERR,
106 			"PAM-KRB5 (acct):  unable to get host based "
107 			"service name for realm '%s'",
108 			admin_realm);
109 		krb5_free_principal(context, princ);
110 		krb5_free_context(context);
111 		return (PAM_SYSTEM_ERR);
112 	}
113 
114 	code = kadm5_init_with_password(kprinc, password, cpw_service,
115 					&params, KADM5_STRUCT_VERSION,
116 					KADM5_API_VERSION_2, &server_handle);
117 	if (code != 0) {
118 		if (debug)
119 			__pam_log(LOG_AUTH | LOG_DEBUG,
120 			    "PAM-KRB5 (acct): fetch_princ_entry: "
121 			    "init_with_pw failed: code = %d", code);
122 		krb5_free_principal(context, princ);
123 		krb5_free_context(context);
124 		return ((code == KADM5_BAD_PASSWORD) ?
125 			PAM_AUTH_ERR : PAM_SYSTEM_ERR);
126 	}
127 
128 	if (_kadm5_get_kpasswd_protocol(server_handle) != KRB5_CHGPWD_RPCSEC) {
129 		if (debug)
130 			__pam_log(LOG_AUTH | LOG_DEBUG,
131 			    "PAM-KRB5 (acct): fetch_princ_entry: "
132 			    "non-RPCSEC_GSS chpw server, can't get "
133 			    "princ entry");
134 		(void) kadm5_destroy(server_handle);
135 		krb5_free_principal(context, princ);
136 		krb5_free_context(context);
137 		return (PAM_SYSTEM_ERR);
138 	}
139 
140 	code = kadm5_get_principal(server_handle, princ, prent,
141 				KADM5_PRINCIPAL_NORMAL_MASK);
142 
143 	if (code != 0) {
144 		(void) kadm5_destroy(server_handle);
145 		krb5_free_principal(context, princ);
146 		krb5_free_context(context);
147 		return ((code == KADM5_UNK_PRINC) ?
148 			PAM_USER_UNKNOWN : PAM_SYSTEM_ERR);
149 	}
150 
151 	if (code = krb5_timeofday(context, now)) {
152 		(void) kadm5_destroy(server_handle);
153 		krb5_free_principal(context, princ);
154 		krb5_free_context(context);
155 		__pam_log(LOG_AUTH | LOG_ERR,
156 			    "PAM-KRB5 (acct): krb5_timeofday fail: code=%d",
157 		    code);
158 		return (PAM_SYSTEM_ERR);
159 	}
160 
161 	(void) kadm5_destroy(server_handle);
162 	krb5_free_principal(context, princ);
163 	krb5_free_context(context);
164 
165 	return (PAM_SUCCESS);
166 }
167 
168 /*
169  * exp_warn
170  *
171  * warn the user if her pw is set to expire
172  *
173  * We use the kadm protocol and chpw svc to fetch the user's KDC db
174  * entry.  The user's default perms on the KDC db should allow this.
175  * Note since the SEAM kadm API uses rpcsec_gss (which is diff from
176  * what MS and MIT 1.2 and before uses), this probably only works
177  * with a SEAM KDC.
178  */
179 
180 static int
181 exp_warn(
182 	pam_handle_t *pamh,
183 	char *user,
184 	char *password,
185 	int debug)
186 
187 {
188 	int err;
189 	kadm5_principal_ent_rec prent;
190 	krb5_timestamp  now, days;
191 	char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
192 
193 	if (debug)
194 		__pam_log(LOG_AUTH | LOG_DEBUG,
195 		    "PAM-KRB5 (acct): exp_warn start: user = '%s'",
196 		    user ? user : "<null>");
197 
198 	if (!pamh || !user || !password) {
199 		err = PAM_SERVICE_ERR;
200 		goto out;
201 	}
202 
203 	(void) memset(&prent, 0, sizeof (prent));
204 	if ((err = fetch_princ_entry(user, password, &prent,
205 				    &now, debug)) != PAM_SUCCESS) {
206 		if (debug)
207 			__pam_log(LOG_AUTH | LOG_DEBUG,
208 			    "PAM-KRB5 (acct): exp_warn: fetch_pr failed %d",
209 			    err);
210 		goto out;
211 	}
212 
213 	if (debug)
214 		__pam_log(LOG_AUTH | LOG_DEBUG,
215 		    "PAM-KRB5 (acct): exp_warn: fetch_princ success:"
216 		    " princ exp=%ld pw_exp = %ld, now =%ld, days=%ld",
217 		    prent.princ_expire_time,
218 		    prent.pw_expiration, now,
219 		    prent.pw_expiration > 0
220 		    ? ((prent.pw_expiration - now) / DAY)
221 		    : 0);
222 
223 	/* warn user if principal's pw is set to expire */
224 	if (prent.pw_expiration > 0) {
225 		days = (prent.pw_expiration - now) / DAY;
226 		if (days <= 0)
227 			(void) snprintf(messages[0],
228 				sizeof (messages[0]),
229 				dgettext(TEXT_DOMAIN,
230 		"Your Kerberos password will expire within 24 hours.\n"));
231 		else if (days == 1)
232 			(void) snprintf(messages[0],
233 				sizeof (messages[0]),
234 				dgettext(TEXT_DOMAIN,
235 			"Your Kerberos password will expire in 1 day.\n"));
236 		else
237 			(void) snprintf(messages[0],
238 				sizeof (messages[0]),
239 				dgettext(TEXT_DOMAIN,
240 			"Your Kerberos password will expire in %d days.\n"),
241 				(int)days);
242 
243 		(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1,
244 					messages, NULL);
245 	}
246 
247 	/* things went smooth */
248 	err = PAM_SUCCESS;
249 
250 out:
251 	if (debug)
252 		__pam_log(LOG_AUTH | LOG_DEBUG,
253 		    "PAM-KRB5 (acct): exp_warn end: err = %d", err);
254 
255 	return (err);
256 }
257 
258 /*
259  * pam_krb5 acct_mgmt
260  *
261  * we do
262  *    - check if pw expired (flag set in auth)
263  *    - warn user if pw is set to expire
264  *
265  * notes
266  *    - we require the auth module to have already run (sets module data)
267  *    - we don't worry about an expired princ cuz if that's the case,
268  *      auth would have failed
269  */
270 int
271 pam_sm_acct_mgmt(
272 	pam_handle_t *pamh,
273 	int	flags,
274 	int	argc,
275 	const char **argv)
276 
277 {
278 	char *user = NULL;
279 	char *userdata = NULL;
280 	int err;
281 	int i;
282 	krb5_module_data_t *kmd = NULL;
283 	char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
284 	int debug = 0;  /* pam.conf entry option */
285 	int nowarn = 0; /* pam.conf entry option, no expire warnings */
286 	pam_repository_t	*rep_data = NULL;
287 
288 	for (i = 0; i < argc; i++) {
289 		if (strcasecmp(argv[i], "debug") == 0)
290 			debug = 1;
291 		else if (strcasecmp(argv[i], "nowarn") == 0) {
292 			nowarn = 1;
293 			flags = flags | PAM_SILENT;
294 		} else {
295 			__pam_log(LOG_AUTH | LOG_ERR,
296 			    "PAM-KRB5 (acct): illegal option %s",
297 			    argv[i]);
298 		}
299 	}
300 
301 	if (debug)
302 		__pam_log(LOG_AUTH | LOG_DEBUG,
303 		    "PAM-KRB5 (acct): debug=%d, nowarn=%d",
304 		    debug, nowarn);
305 
306 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
307 
308 	if (rep_data != NULL) {
309 		/*
310 		 * If the repository is not ours,
311 		 * return PAM_IGNORE.
312 		 */
313 		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
314 			if (debug)
315 				__pam_log(LOG_AUTH | LOG_DEBUG,
316 					"PAM-KRB5 (acct): wrong"
317 					"repository found (%s), returning "
318 					"PAM_IGNORE", rep_data->type);
319 			return (PAM_IGNORE);
320 		}
321 	}
322 
323 
324 	/* get user name */
325 	(void) pam_get_item(pamh, PAM_USER, (void **) &user);
326 
327 	if (user == NULL || *user == '\0') {
328 		err = PAM_USER_UNKNOWN;
329 		goto out;
330 	}
331 
332 	/* get pam_krb5_migrate specific data */
333 	err = pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA,
334 					(const void **)&userdata);
335 	if (err != PAM_SUCCESS) {
336 		if (debug)
337 			__pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (acct): "
338 				"no module data for KRB5_AUTOMIGRATE_DATA");
339 	} else {
340 		/*
341 		 * We try and reauthenticate, since this user has a
342 		 * newly created krb5 principal via the pam_krb5_migrate
343 		 * auth module. That way, this new user will have fresh
344 		 * creds (assuming pam_sm_authenticate() succeeds).
345 		 */
346 		if (strcmp(user, userdata) == 0)
347 			(void) pam_sm_authenticate(pamh, flags, argc,
348 					(const char **)argv);
349 		else
350 			if (debug)
351 				__pam_log(LOG_AUTH | LOG_DEBUG,
352 				"PAM-KRB5 (acct): PAM_USER %s"
353 				"does not match user %s from pam_get_data()",
354 				user, (char *)userdata);
355 	}
356 
357 	/* get krb5 module data  */
358 	if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd))
359 	    != PAM_SUCCESS) {
360 		if (err == PAM_NO_MODULE_DATA) {
361 			/*
362 			 * pam_auth never called (possible config
363 			 * error; no pam_krb5 auth entry in pam.conf)
364 			 * or
365 			 * auth module returned before module data
366 			 * was instantiated (normal for auth 'acceptor')
367 			 */
368 			if (debug)
369 				__pam_log(LOG_AUTH | LOG_DEBUG,
370 				    "PAM-KRB5 (acct): no module data");
371 			err = PAM_IGNORE;
372 			goto out;
373 		} else {
374 			__pam_log(LOG_AUTH | LOG_ERR,
375 				    "PAM-KRB5 (acct): get module"
376 				    " data failed: err=%d",
377 			    err);
378 		}
379 		goto out;
380 	}
381 
382 	debug = debug || kmd->debug;
383 
384 	/*
385 	 * auth mod set status to ignore, most likely cuz root key is
386 	 * in keytab, so skip other checks and return ignore
387 	 */
388 	if (kmd->auth_status == PAM_IGNORE) {
389 		if (debug)
390 			__pam_log(LOG_AUTH | LOG_DEBUG,
391 			    "PAM-KRB5 (acct): kmd auth_status is IGNORE");
392 		err = PAM_IGNORE;
393 		goto out;
394 	}
395 
396 	/*
397 	 * auth mod set status to user_unknown, most likely cuz user is
398 	 * not a kerberos user.
399 	 */
400 	if (kmd->auth_status == PAM_USER_UNKNOWN) {
401 		if (debug)
402 			syslog(LOG_DEBUG,
403 			    "PAM-KRB5 (acct): kmd auth_status is USER UNKNOWN");
404 		err = PAM_USER_UNKNOWN;
405 		goto out;
406 	}
407 
408 	/*
409 	 * age_status will be set to PAM_NEW_AUTHTOK_REQD in pam_krb5's
410 	 * 'auth' if the user's key/pw has expired and needs to be changed
411 	 */
412 	if (kmd->age_status == PAM_NEW_AUTHTOK_REQD) {
413 		if (!nowarn) {
414 			(void) snprintf(messages[0], sizeof (messages[0]),
415 				dgettext(TEXT_DOMAIN,
416 				"Your Kerberos password has expired.\n"));
417 			(void) __pam_display_msg(pamh, PAM_TEXT_INFO,
418 					1, messages, NULL);
419 		}
420 		err = PAM_NEW_AUTHTOK_REQD;
421 		goto out;
422 	}
423 
424 	if (!(flags & PAM_SILENT) && !nowarn && kmd->password) {
425 		/* if we fail, let it slide, it's only a warning brah */
426 		(void) exp_warn(pamh, user, kmd->password, debug);
427 	}
428 
429 	/*
430 	 * Here we return any errors during the auth pass, if any.
431 	 */
432 	err = kmd->auth_status;
433 
434 out:
435 	if (debug)
436 		__pam_log(LOG_AUTH | LOG_DEBUG,
437 		    "PAM-KRB5 (acct): end: %s", pam_strerror(pamh, err));
438 
439 	return (err);
440 }
441