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 #include <kadm5/admin.h>
30 #include <krb5.h>
31 #include <security/pam_appl.h>
32 #include <security/pam_modules.h>
33 #include <security/pam_impl.h>
34 #include <string.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <pwd.h>
38 #include <syslog.h>
39 #include <libintl.h>
40 
41 #define	KRB5_AUTOMIGRATE_DATA	"SUNW-KRB5-AUTOMIGRATE-DATA"
42 
43 static void krb5_migrate_cleanup(pam_handle_t *pamh, void *data,
44 				int pam_status);
45 
46 /*
47  * pam_sm_authenticate - Authenticate a host-based client service
48  * principal to kadmind in order to permit the creation of a new user
49  * principal in the client's default realm.
50  */
51 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
52 			int argc, const char **argv)
53 {
54 	char *user = NULL;
55 	char *userdata = 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 = (char *)strdup(strstr(argv[i], "=") + 1);
91 		} else {
92 			syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
93 				"PAM-KRB5-AUTOMIGRATE (auth): unrecognized "
94 				"option %s"),
95 				argv[i]);
96 		}
97 	}
98 
99 	if (flags & PAM_SILENT)
100 		quiet = 1;
101 
102 	err = pam_get_item(pamh, PAM_USER, (void**)&user);
103 	if (err != PAM_SUCCESS) {
104 		goto cleanup;
105 	}
106 
107 	/*
108 	 * Check if user name is *not* NULL
109 	 */
110 	if (user == NULL || (user[0] == '\0')) {
111 		if (debug)
112 			syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN,
113 				"PAM-KRB5-AUTOMIGRATE (auth): "
114 				"user empty or null"));
115 		goto cleanup;
116 	}
117 
118 	/*
119 	 * Grok the user password
120 	 */
121 	err = pam_get_item(pamh, PAM_AUTHTOK, (void **)&password);
122 	if (err != PAM_SUCCESS) {
123 		goto cleanup;
124 	}
125 
126 	if (password == NULL || (password[0] == '\0')) {
127 		if (debug)
128 			syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN,
129 				"PAM-KRB5-AUTOMIGRATE (auth): "
130 				"authentication token is empty or null"));
131 		goto cleanup;
132 	}
133 
134 
135 	/*
136 	 * Now, lets do the all krb5/kadm5 setup for the principal addition
137 	 */
138 	if (retval = krb5_init_context(&context)) {
139 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
140 			"PAM-KRB5-AUTOMIGRATE (auth): Error initializing "
141 			"krb5: %s"),
142 			error_message(retval));
143 		goto cleanup;
144 	}
145 
146 	(void) memset((char *)&params, 0, sizeof (params));
147 	(void) memset(&kadm5_userprinc, 0, sizeof (kadm5_userprinc));
148 
149 	if (def_realm == NULL && krb5_get_default_realm(context, &def_realm)) {
150 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
151 			"PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining "
152 			"default krb5 realm"));
153 		goto cleanup;
154 	}
155 
156 	params.mask |= KADM5_CONFIG_REALM;
157 	params.realm = def_realm;
158 
159 	if (kadm5_get_adm_host_srv_name(context, def_realm,
160 					&kadmin_princ)) {
161 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
162 			"PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining "
163 			"host based service name for realm %s\n"), def_realm);
164 		goto cleanup;
165 	}
166 
167 	if (retval = krb5_sname_to_principal(context, NULL,
168 				(service != NULL)?service:"host",
169 				KRB5_NT_SRV_HST,
170 				&svcprinc)) {
171 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
172 			"PAM-KRB5-AUTOMIGRATE (auth): Error while creating "
173 			"krb5 host service principal: %s"),
174 			error_message(retval));
175 		goto cleanup;
176 	}
177 
178 	if (retval = krb5_unparse_name(context, svcprinc,
179 				&svcprincstr)) {
180 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
181 			"PAM-KRB5-AUTOMIGRATE (auth): Error while "
182 			"unparsing principal name: %s"),
183 			error_message(retval));
184 		krb5_free_principal(context, svcprinc);
185 		goto cleanup;
186 	}
187 
188 	krb5_free_principal(context, svcprinc);
189 
190 	/*
191 	 * Initialize the kadm5 connection using the default keytab
192 	 */
193 	retval = kadm5_init_with_skey(svcprincstr, NULL,
194 					kadmin_princ,
195 					&params,
196 					KADM5_STRUCT_VERSION,
197 					KADM5_API_VERSION_2,
198 					&handle);
199 	if (retval) {
200 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
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 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
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 				syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN,
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 			syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN,
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 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
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 		kadm5_userprinc.pw_expiration = now;
280 		mask |= KADM5_PW_EXPIRATION;
281 	}
282 
283 	mask |= KADM5_PRINCIPAL;
284 	retval = kadm5_create_principal(handle, &kadm5_userprinc,
285 						mask, password);
286 	if (retval) {
287 		switch (retval) {
288 		case KADM5_AUTH_ADD:
289 			if (debug)
290 				syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN,
291 				    "PAM-KRB5-AUTOMIGRATE (auth): %s does "
292 				    "not have the ADD privilege "
293 				    "for kadm5_create_principal: %s"),
294 				    svcprincstr, error_message(retval));
295 			break;
296 
297 		default:
298 			syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
299 				"PAM-KRB5-AUTOMIGRATE (auth): Generic error"
300 				"while doing kadm5_create_principal: %s"),
301 				error_message(retval));
302 			break;
303 		}
304 		goto cleanup;
305 	}
306 
307 	/*
308 	 * Success, new user principal has been added !
309 	 */
310 	if (!quiet) {
311 		char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
312 
313 		(void) snprintf(messages[0], sizeof (messages[0]),
314 			dgettext(TEXT_DOMAIN, "\nUser `%s' has been "
315 			"automatically migrated to the Kerberos realm %s\n"),
316 			user, def_realm);
317 		(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1,
318 				messages, NULL);
319 	}
320 	if (debug)
321 		syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN,
322 			"PAM-KRB5-AUTOMIGRATE (auth): User %s "
323 			"has been added to the Kerberos KDC database"),
324 			userprincstr);
325 
326 	/*
327 	 * Since this is a new krb5 principal, do a pam_set_data()
328 	 * for possible use by the acct_mgmt routine of pam_krb5(5)
329 	 */
330 	if (pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA,
331 			(const void **)&userdata) == PAM_SUCCESS) {
332 		/*
333 		 * We created a princ in a previous run on the same handle and
334 		 * it must have been for a different PAM_USER / princ name,
335 		 * otherwise we couldn't succeed here, unless that princ
336 		 * got deleted.
337 		 */
338 		if (userdata != NULL)
339 			free(userdata);
340 	}
341 	userdata = (char *)strdup(user);
342 	if (pam_set_data(pamh, KRB5_AUTOMIGRATE_DATA, userdata,
343 			krb5_migrate_cleanup) != PAM_SUCCESS) {
344 		if (userdata != NULL)
345 			free(userdata);
346 	}
347 
348 cleanup:
349 	if (service)
350 		free(service);
351 	if (kadmin_princ)
352 		free(kadmin_princ);
353 	if (svcprincstr)
354 		free(svcprincstr);
355 	if (userprincstr)
356 		free(userprincstr);
357 	if (def_realm)
358 		free(def_realm);
359 	(void) kadm5_free_principal_ent(handle, &kadm5_userprinc);
360 	(void) kadm5_destroy((void *)handle);
361 	if (context != NULL)
362 		krb5_free_context(context);
363 
364 	return (PAM_IGNORE);
365 }
366 
367 /*ARGSUSED*/
368 static void
369 krb5_migrate_cleanup(pam_handle_t *pamh, void *data, int pam_status) {
370 	if (data != NULL)
371 		free((char *)data);
372 }
373 
374 /*ARGSUSED*/
375 int
376 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
377 {
378 	return (PAM_IGNORE);
379 }
380