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