krb5_migrate_authenticate.c revision 4960:a4746a82a247
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
42static 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 */
50int 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
355cleanup:
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*/
375static void
376krb5_migrate_cleanup(pam_handle_t *pamh, void *data, int pam_status) {
377	if (data != NULL)
378		free((char *)data);
379}
380
381/*ARGSUSED*/
382int
383pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
384{
385	return (PAM_IGNORE);
386}
387