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 2008 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 42 static 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 */ 50 int 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 *olduserdata = 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 = strdup(strstr(argv[i], "=") + 1); 91 } else { 92 __pam_log(LOG_AUTH | LOG_ERR, 93 "PAM-KRB5-AUTOMIGRATE (auth): unrecognized " 94 "option %s", 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): user empty or null"); 113 goto cleanup; 114 } 115 116 /* 117 * Can't tolerate memory failure later on. Get a copy 118 * before any work is done. 119 */ 120 if ((userdata = strdup(user)) == NULL) { 121 __pam_log(LOG_AUTH | LOG_ERR, 122 "PAM-KRB5-AUTOMIGRATE (auth): Out of memory"); 123 goto cleanup; 124 } 125 126 /* 127 * Grok the user password 128 */ 129 err = pam_get_item(pamh, PAM_AUTHTOK, (void **)&password); 130 if (err != PAM_SUCCESS) { 131 goto cleanup; 132 } 133 134 if (password == NULL || (password[0] == '\0')) { 135 if (debug) 136 __pam_log(LOG_AUTH | LOG_DEBUG, 137 "PAM-KRB5-AUTOMIGRATE (auth): " 138 "authentication token is empty or null"); 139 goto cleanup; 140 } 141 142 143 /* 144 * Now, lets do the all krb5/kadm5 setup for the principal addition 145 */ 146 if (retval = krb5_init_context(&context)) { 147 __pam_log(LOG_AUTH | LOG_ERR, 148 "PAM-KRB5-AUTOMIGRATE (auth): Error initializing " 149 "krb5: %s", error_message(retval)); 150 goto cleanup; 151 } 152 153 (void) memset((char *)¶ms, 0, sizeof (params)); 154 (void) memset(&kadm5_userprinc, 0, sizeof (kadm5_userprinc)); 155 156 if (def_realm == NULL && krb5_get_default_realm(context, &def_realm)) { 157 __pam_log(LOG_AUTH | LOG_ERR, 158 "PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining " 159 "default krb5 realm"); 160 goto cleanup; 161 } 162 163 params.mask |= KADM5_CONFIG_REALM; 164 params.realm = def_realm; 165 166 if (kadm5_get_adm_host_srv_name(context, def_realm, 167 &kadmin_princ)) { 168 __pam_log(LOG_AUTH | LOG_ERR, 169 "PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining " 170 "host based service name for realm %s\n", def_realm); 171 goto cleanup; 172 } 173 174 if (retval = krb5_sname_to_principal(context, NULL, 175 (service != NULL) ? service : "host", KRB5_NT_SRV_HST, &svcprinc)) { 176 __pam_log(LOG_AUTH | LOG_ERR, 177 "PAM-KRB5-AUTOMIGRATE (auth): Error while creating " 178 "krb5 host service principal: %s", 179 error_message(retval)); 180 goto cleanup; 181 } 182 183 if (retval = krb5_unparse_name(context, svcprinc, 184 &svcprincstr)) { 185 __pam_log(LOG_AUTH | LOG_ERR, 186 "PAM-KRB5-AUTOMIGRATE (auth): Error while " 187 "unparsing principal name: %s", error_message(retval)); 188 krb5_free_principal(context, svcprinc); 189 goto cleanup; 190 } 191 192 krb5_free_principal(context, svcprinc); 193 194 /* 195 * Initialize the kadm5 connection using the default keytab 196 */ 197 retval = kadm5_init_with_skey(svcprincstr, NULL, 198 kadmin_princ, ¶ms, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, 199 NULL, &handle); 200 if (retval) { 201 __pam_log(LOG_AUTH | LOG_ERR, 202 "PAM-KRB5-AUTOMIGRATE (auth): Error while " 203 "doing kadm5_init_with_skey: %s", 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 if ((userprincstr = malloc(strlength)) == NULL) 216 goto cleanup; 217 (void) strlcpy(userprincstr, user, strlength); 218 (void) strlcat(userprincstr, "@", strlength); 219 (void) strlcat(userprincstr, def_realm, strlength); 220 221 222 if (retval = krb5_parse_name(context, userprincstr, 223 &userprinc)) { 224 __pam_log(LOG_AUTH | LOG_ERR, 225 "PAM-KRB5-AUTOMIGRATE (auth): Error while " 226 "parsing user principal name: %s", 227 error_message(retval)); 228 goto cleanup; 229 } 230 231 retval = kadm5_get_principal(handle, userprinc, &kadm5_userprinc, 232 KADM5_PRINCIPAL_NORMAL_MASK); 233 234 krb5_free_principal(context, userprinc); 235 236 if (retval) { 237 switch (retval) { 238 case KADM5_AUTH_GET: 239 if (debug) 240 __pam_log(LOG_AUTH | LOG_DEBUG, 241 "PAM-KRB5-AUTOMIGRATE (auth): %s does " 242 "not have the GET privilege " 243 "for kadm5_get_principal: %s", 244 svcprincstr, error_message(retval)); 245 break; 246 247 case KADM5_UNK_PRINC: 248 default: 249 break; 250 } 251 /* 252 * We will try & add this principal anyways, continue on ... 253 */ 254 (void) memset(&kadm5_userprinc, 0, sizeof (kadm5_userprinc)); 255 } else { 256 /* 257 * Principal already exists in the KDC database, quit now 258 */ 259 if (debug) 260 __pam_log(LOG_AUTH | LOG_DEBUG, 261 "PAM-KRB5-AUTOMIGRATE (auth): Principal %s " 262 "already exists in Kerberos KDC database", 263 userprincstr); 264 goto cleanup; 265 } 266 267 268 269 if (retval = krb5_parse_name(context, userprincstr, 270 &(kadm5_userprinc.principal))) { 271 __pam_log(LOG_AUTH | LOG_ERR, 272 "PAM-KRB5-AUTOMIGRATE (auth): Error while " 273 "parsing user principal name: %s", 274 error_message(retval)); 275 goto cleanup; 276 } 277 278 if (expire_pw) { 279 (void) time(&now); 280 /* 281 * The local system time could actually be later than the 282 * system time of the KDC we are authenticating to. We expire 283 * w/the local system time minus clockskew so that we are 284 * assured that it is expired on this login, not the next. 285 */ 286 now -= context->clockskew; 287 kadm5_userprinc.pw_expiration = now; 288 mask |= KADM5_PW_EXPIRATION; 289 } 290 291 mask |= KADM5_PRINCIPAL; 292 retval = kadm5_create_principal(handle, &kadm5_userprinc, 293 mask, password); 294 if (retval) { 295 switch (retval) { 296 case KADM5_AUTH_ADD: 297 if (debug) 298 __pam_log(LOG_AUTH | LOG_DEBUG, 299 "PAM-KRB5-AUTOMIGRATE (auth): %s does " 300 "not have the ADD privilege " 301 "for kadm5_create_principal: %s", 302 svcprincstr, error_message(retval)); 303 break; 304 305 default: 306 __pam_log(LOG_AUTH | LOG_ERR, 307 "PAM-KRB5-AUTOMIGRATE (auth): Generic error" 308 "while doing kadm5_create_principal: %s", 309 error_message(retval)); 310 break; 311 } 312 goto cleanup; 313 } 314 315 /* 316 * Success, new user principal has been added ! 317 */ 318 if (!quiet) { 319 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 320 321 (void) snprintf(messages[0], sizeof (messages[0]), 322 dgettext(TEXT_DOMAIN, "\nUser `%s' has been " 323 "automatically migrated to the Kerberos realm %s\n"), 324 user, def_realm); 325 (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, 326 messages, NULL); 327 } 328 if (debug) 329 __pam_log(LOG_AUTH | LOG_DEBUG, 330 "PAM-KRB5-AUTOMIGRATE (auth): User %s " 331 "has been added to the Kerberos KDC database", 332 userprincstr); 333 334 /* 335 * Since this is a new krb5 principal, do a pam_set_data() 336 * for possible use by the acct_mgmt routine of pam_krb5(5) 337 */ 338 if (pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA, 339 (const void **)&olduserdata) == PAM_SUCCESS) { 340 /* 341 * We created a princ in a previous run on the same handle and 342 * it must have been for a different PAM_USER / princ name, 343 * otherwise we couldn't succeed here, unless that princ 344 * got deleted. 345 */ 346 if (olduserdata != NULL) 347 free(olduserdata); 348 } 349 if (pam_set_data(pamh, KRB5_AUTOMIGRATE_DATA, userdata, 350 krb5_migrate_cleanup) != PAM_SUCCESS) { 351 free(userdata); 352 } 353 354 cleanup: 355 if (service) 356 free(service); 357 if (kadmin_princ) 358 free(kadmin_princ); 359 if (svcprincstr) 360 free(svcprincstr); 361 if (userprincstr) 362 free(userprincstr); 363 if (def_realm) 364 free(def_realm); 365 (void) kadm5_free_principal_ent(handle, &kadm5_userprinc); 366 (void) kadm5_destroy((void *)handle); 367 if (context != NULL) 368 krb5_free_context(context); 369 370 return (PAM_IGNORE); 371 } 372 373 /*ARGSUSED*/ 374 static void 375 krb5_migrate_cleanup(pam_handle_t *pamh, void *data, int pam_status) { 376 if (data != NULL) 377 free((char *)data); 378 } 379 380 /*ARGSUSED*/ 381 int 382 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 383 { 384 return (PAM_IGNORE); 385 } 386