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