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 *)¶ms, 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, ¶ms, 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