xref: /illumos-gate/usr/src/lib/pam_modules/krb5_migrate/krb5_migrate_authenticate.c (revision bea83d026ee1bd1b2a2419e1d0232f107a5d7d9b)
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 2008 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 #include <kadm5/admin.h>
29 #include <krb5.h>
30 #include <security/pam_appl.h>
31 #include <security/pam_modules.h>
32 #include <security/pam_impl.h>
33 #include <string.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <pwd.h>
37 #include <syslog.h>
38 #include <libintl.h>
39 
40 #define	KRB5_AUTOMIGRATE_DATA	"SUNW-KRB5-AUTOMIGRATE-DATA"
41 
42 static void krb5_migrate_cleanup(pam_handle_t *pamh, void *data,
43 				int pam_status);
44 
45 /*
46  * pam_sm_authenticate - Authenticate a host-based client service
47  * principal to kadmind in order to permit the creation of a new user
48  * principal in the client's default realm.
49  */
50 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
51 			int argc, const char **argv)
52 {
53 	char *user = NULL;
54 	char *userdata = NULL;
55 	char *olduserdata = NULL;
56 	char *password = NULL;
57 	int err, i;
58 	time_t now;
59 
60 	/* pam.conf options */
61 	int debug = 0;
62 	int quiet = 0;
63 	int expire_pw = 0;
64 	char *service = NULL;
65 
66 	/* krb5-specific defines */
67 	kadm5_ret_t retval = 0;
68 	krb5_context context = NULL;
69 	kadm5_config_params params;
70 	krb5_principal svcprinc;
71 	char *svcprincstr = NULL;
72 	krb5_principal userprinc;
73 	char *userprincstr = NULL;
74 	int strlength = 0;
75 	kadm5_principal_ent_rec kadm5_userprinc;
76 	char *kadmin_princ = NULL;
77 	char *def_realm = NULL;
78 	void *handle = NULL;
79 	long mask = 0;
80 
81 	for (i = 0; i < argc; i++) {
82 		if (strcmp(argv[i], "debug") == 0) {
83 			debug = 1;
84 		} else if (strcmp(argv[i], "quiet") == 0) {
85 			quiet = 1;
86 		} else if (strcmp(argv[i], "expire_pw") == 0) {
87 			expire_pw = 1;
88 		} else if ((strstr(argv[i], "client_service=") != NULL) &&
89 		    (strcmp((strstr(argv[i], "=") + 1), "") != 0)) {
90 			service = strdup(strstr(argv[i], "=") + 1);
91 		} else {
92 			__pam_log(LOG_AUTH | LOG_ERR,
93 			    "PAM-KRB5-AUTOMIGRATE (auth): unrecognized "
94 			    "option %s", argv[i]);
95 		}
96 	}
97 
98 	if (flags & PAM_SILENT)
99 		quiet = 1;
100 
101 	err = pam_get_item(pamh, PAM_USER, (void**)&user);
102 	if (err != PAM_SUCCESS) {
103 		goto cleanup;
104 	}
105 
106 	/*
107 	 * Check if user name is *not* NULL
108 	 */
109 	if (user == NULL || (user[0] == '\0')) {
110 		if (debug)
111 			__pam_log(LOG_AUTH | LOG_DEBUG,
112 			    "PAM-KRB5-AUTOMIGRATE (auth): user empty or null");
113 		goto cleanup;
114 	}
115 
116 	/*
117 	 * Can't tolerate memory failure later on. Get a copy
118 	 * before any work is done.
119 	 */
120 	if ((userdata = strdup(user)) == NULL) {
121 		__pam_log(LOG_AUTH | LOG_ERR,
122 		    "PAM-KRB5-AUTOMIGRATE (auth): Out of memory");
123 		goto cleanup;
124 	}
125 
126 	/*
127 	 * Grok the user password
128 	 */
129 	err = pam_get_item(pamh, PAM_AUTHTOK, (void **)&password);
130 	if (err != PAM_SUCCESS) {
131 		goto cleanup;
132 	}
133 
134 	if (password == NULL || (password[0] == '\0')) {
135 		if (debug)
136 			__pam_log(LOG_AUTH | LOG_DEBUG,
137 			    "PAM-KRB5-AUTOMIGRATE (auth): "
138 			    "authentication token is empty or null");
139 		goto cleanup;
140 	}
141 
142 
143 	/*
144 	 * Now, lets do the all krb5/kadm5 setup for the principal addition
145 	 */
146 	if (retval = krb5_init_context(&context)) {
147 		__pam_log(LOG_AUTH | LOG_ERR,
148 		    "PAM-KRB5-AUTOMIGRATE (auth): Error initializing "
149 		    "krb5: %s", error_message(retval));
150 		goto cleanup;
151 	}
152 
153 	(void) memset((char *)&params, 0, sizeof (params));
154 	(void) memset(&kadm5_userprinc, 0, sizeof (kadm5_userprinc));
155 
156 	if (def_realm == NULL && krb5_get_default_realm(context, &def_realm)) {
157 		__pam_log(LOG_AUTH | LOG_ERR,
158 		    "PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining "
159 		    "default krb5 realm");
160 		goto cleanup;
161 	}
162 
163 	params.mask |= KADM5_CONFIG_REALM;
164 	params.realm = def_realm;
165 
166 	if (kadm5_get_adm_host_srv_name(context, def_realm,
167 	    &kadmin_princ)) {
168 		__pam_log(LOG_AUTH | LOG_ERR,
169 		    "PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining "
170 		    "host based service name for realm %s\n", def_realm);
171 		goto cleanup;
172 	}
173 
174 	if (retval = krb5_sname_to_principal(context, NULL,
175 	    (service != NULL) ? service : "host", KRB5_NT_SRV_HST, &svcprinc)) {
176 		__pam_log(LOG_AUTH | LOG_ERR,
177 		    "PAM-KRB5-AUTOMIGRATE (auth): Error while creating "
178 		    "krb5 host service principal: %s",
179 		    error_message(retval));
180 		goto cleanup;
181 	}
182 
183 	if (retval = krb5_unparse_name(context, svcprinc,
184 	    &svcprincstr)) {
185 		__pam_log(LOG_AUTH | LOG_ERR,
186 		    "PAM-KRB5-AUTOMIGRATE (auth): Error while "
187 		    "unparsing principal name: %s", error_message(retval));
188 		krb5_free_principal(context, svcprinc);
189 		goto cleanup;
190 	}
191 
192 	krb5_free_principal(context, svcprinc);
193 
194 	/*
195 	 * Initialize the kadm5 connection using the default keytab
196 	 */
197 	retval = kadm5_init_with_skey(svcprincstr, NULL,
198 	    kadmin_princ, &params, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2,
199 	    NULL, &handle);
200 	if (retval) {
201 		__pam_log(LOG_AUTH | LOG_ERR,
202 		    "PAM-KRB5-AUTOMIGRATE (auth): Error while "
203 		    "doing kadm5_init_with_skey: %s", error_message(retval));
204 		goto cleanup;
205 	}
206 
207 
208 	/*
209 	 * The RPCSEC_GSS connection has been established; Lets check to see
210 	 * if the corresponding user principal exists in the KDC database.
211 	 * If not, lets create a new one.
212 	 */
213 
214 	strlength = strlen(user) + strlen(def_realm) + 2;
215 	if ((userprincstr = malloc(strlength)) == NULL)
216 		goto cleanup;
217 	(void) strlcpy(userprincstr, user, strlength);
218 	(void) strlcat(userprincstr, "@", strlength);
219 	(void) strlcat(userprincstr, def_realm, strlength);
220 
221 
222 	if (retval = krb5_parse_name(context, userprincstr,
223 	    &userprinc)) {
224 		__pam_log(LOG_AUTH | LOG_ERR,
225 		    "PAM-KRB5-AUTOMIGRATE (auth): Error while "
226 		    "parsing user principal name: %s",
227 		    error_message(retval));
228 		goto cleanup;
229 	}
230 
231 	retval = kadm5_get_principal(handle, userprinc, &kadm5_userprinc,
232 	    KADM5_PRINCIPAL_NORMAL_MASK);
233 
234 	krb5_free_principal(context, userprinc);
235 
236 	if (retval) {
237 		switch (retval) {
238 		case KADM5_AUTH_GET:
239 			if (debug)
240 				__pam_log(LOG_AUTH | LOG_DEBUG,
241 				    "PAM-KRB5-AUTOMIGRATE (auth): %s does "
242 				    "not have the GET privilege "
243 				    "for kadm5_get_principal: %s",
244 				    svcprincstr, error_message(retval));
245 			break;
246 
247 		case KADM5_UNK_PRINC:
248 		default:
249 			break;
250 		}
251 		/*
252 		 * We will try & add this principal anyways, continue on ...
253 		 */
254 		(void) memset(&kadm5_userprinc, 0, sizeof (kadm5_userprinc));
255 	} else {
256 		/*
257 		 * Principal already exists in the KDC database, quit now
258 		 */
259 		if (debug)
260 			__pam_log(LOG_AUTH | LOG_DEBUG,
261 			    "PAM-KRB5-AUTOMIGRATE (auth): Principal %s "
262 			    "already exists in Kerberos KDC database",
263 			    userprincstr);
264 		goto cleanup;
265 	}
266 
267 
268 
269 	if (retval = krb5_parse_name(context, userprincstr,
270 	    &(kadm5_userprinc.principal))) {
271 		__pam_log(LOG_AUTH | LOG_ERR,
272 		    "PAM-KRB5-AUTOMIGRATE (auth): Error while "
273 		    "parsing user principal name: %s",
274 		    error_message(retval));
275 		goto cleanup;
276 	}
277 
278 	if (expire_pw) {
279 		(void) time(&now);
280 		/*
281 		 * The local system time could actually be later than the
282 		 * system time of the KDC we are authenticating to.  We expire
283 		 * w/the local system time minus clockskew so that we are
284 		 * assured that it is expired on this login, not the next.
285 		 */
286 		now -= context->clockskew;
287 		kadm5_userprinc.pw_expiration = now;
288 		mask |= KADM5_PW_EXPIRATION;
289 	}
290 
291 	mask |= KADM5_PRINCIPAL;
292 	retval = kadm5_create_principal(handle, &kadm5_userprinc,
293 	    mask, password);
294 	if (retval) {
295 		switch (retval) {
296 		case KADM5_AUTH_ADD:
297 			if (debug)
298 				__pam_log(LOG_AUTH | LOG_DEBUG,
299 				    "PAM-KRB5-AUTOMIGRATE (auth): %s does "
300 				    "not have the ADD privilege "
301 				    "for kadm5_create_principal: %s",
302 				    svcprincstr, error_message(retval));
303 			break;
304 
305 		default:
306 			__pam_log(LOG_AUTH | LOG_ERR,
307 			    "PAM-KRB5-AUTOMIGRATE (auth): Generic error"
308 			    "while doing kadm5_create_principal: %s",
309 			    error_message(retval));
310 			break;
311 		}
312 		goto cleanup;
313 	}
314 
315 	/*
316 	 * Success, new user principal has been added !
317 	 */
318 	if (!quiet) {
319 		char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
320 
321 		(void) snprintf(messages[0], sizeof (messages[0]),
322 		    dgettext(TEXT_DOMAIN, "\nUser `%s' has been "
323 		    "automatically migrated to the Kerberos realm %s\n"),
324 		    user, def_realm);
325 		(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1,
326 		    messages, NULL);
327 	}
328 	if (debug)
329 		__pam_log(LOG_AUTH | LOG_DEBUG,
330 		    "PAM-KRB5-AUTOMIGRATE (auth): User %s "
331 		    "has been added to the Kerberos KDC database",
332 		    userprincstr);
333 
334 	/*
335 	 * Since this is a new krb5 principal, do a pam_set_data()
336 	 * for possible use by the acct_mgmt routine of pam_krb5(5)
337 	 */
338 	if (pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA,
339 	    (const void **)&olduserdata) == PAM_SUCCESS) {
340 		/*
341 		 * We created a princ in a previous run on the same handle and
342 		 * it must have been for a different PAM_USER / princ name,
343 		 * otherwise we couldn't succeed here, unless that princ
344 		 * got deleted.
345 		 */
346 		if (olduserdata != NULL)
347 			free(olduserdata);
348 	}
349 	if (pam_set_data(pamh, KRB5_AUTOMIGRATE_DATA, userdata,
350 	    krb5_migrate_cleanup) != PAM_SUCCESS) {
351 		free(userdata);
352 	}
353 
354 cleanup:
355 	if (service)
356 		free(service);
357 	if (kadmin_princ)
358 		free(kadmin_princ);
359 	if (svcprincstr)
360 		free(svcprincstr);
361 	if (userprincstr)
362 		free(userprincstr);
363 	if (def_realm)
364 		free(def_realm);
365 	(void) kadm5_free_principal_ent(handle, &kadm5_userprinc);
366 	(void) kadm5_destroy((void *)handle);
367 	if (context != NULL)
368 		krb5_free_context(context);
369 
370 	return (PAM_IGNORE);
371 }
372 
373 /*ARGSUSED*/
374 static void
375 krb5_migrate_cleanup(pam_handle_t *pamh, void *data, int pam_status) {
376 	if (data != NULL)
377 		free((char *)data);
378 }
379 
380 /*ARGSUSED*/
381 int
382 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
383 {
384 	return (PAM_IGNORE);
385 }
386