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