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 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 *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 *)¶ms, 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 ¶ms, 195 KADM5_STRUCT_VERSION, 196 KADM5_API_VERSION_2, 197 &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", 202 error_message(retval)); 203 goto cleanup; 204 } 205 206 207 /* 208 * The RPCSEC_GSS connection has been established; Lets check to see 209 * if the corresponding user principal exists in the KDC database. 210 * If not, lets create a new one. 211 */ 212 213 strlength = strlen(user) + strlen(def_realm) + 2; 214 userprincstr = (char *)malloc(strlength); 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 **)&userdata) == 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 (userdata != NULL) 345 free(userdata); 346 } 347 userdata = (char *)strdup(user); 348 if (pam_set_data(pamh, KRB5_AUTOMIGRATE_DATA, userdata, 349 krb5_migrate_cleanup) != PAM_SUCCESS) { 350 if (userdata != NULL) 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