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
40static 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 */
48int 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(5)
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
352cleanup:
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*/
372static void
373krb5_migrate_cleanup(pam_handle_t *pamh, void *data, int pam_status) {
374	if (data != NULL)
375		free((char *)data);
376}
377
378/*ARGSUSED*/
379int
380pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
381{
382	return (PAM_IGNORE);
383}
384