Home | History | Annotate | Download | only in krb5_migrate
      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  */
     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(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 
    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
    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
    380 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
    381 {
    382 	return (PAM_IGNORE);
    383 }
    384