/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved.
*/
#include <k5-int.h>
#include <kadm5/admin.h>
#include <krb5.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_impl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <syslog.h>
#include <libintl.h>
#define KRB5_AUTOMIGRATE_DATA "SUNW-KRB5-AUTOMIGRATE-DATA"
static void krb5_migrate_cleanup(pam_handle_t *pamh, void *data,
int pam_status);
/*
* pam_sm_authenticate - Authenticate a host-based client service
* principal to kadmind in order to permit the creation of a new user
* principal in the client's default realm.
*/
int pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
char *user = NULL;
char *userdata = NULL;
char *olduserdata = NULL;
char *password = NULL;
int err, i;
time_t now;
/* pam.conf options */
int debug = 0;
int quiet = 0;
int expire_pw = 0;
char *service = NULL;
/* krb5-specific defines */
kadm5_ret_t retval = 0;
krb5_context context = NULL;
kadm5_config_params params;
krb5_principal svcprinc;
char *svcprincstr = NULL;
krb5_principal userprinc;
char *userprincstr = NULL;
int strlength = 0;
kadm5_principal_ent_rec kadm5_userprinc;
char **kadmin_princs = NULL;
char *def_realm = NULL;
void *handle = NULL;
long mask = 0;
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0) {
debug = 1;
} else if (strcmp(argv[i], "quiet") == 0) {
quiet = 1;
} else if (strcmp(argv[i], "expire_pw") == 0) {
expire_pw = 1;
} else if ((strstr(argv[i], "client_service=") != NULL) &&
(strcmp((strstr(argv[i], "=") + 1), "") != 0)) {
service = strdup(strstr(argv[i], "=") + 1);
} else {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): unrecognized "
"option %s", argv[i]);
}
}
if (flags & PAM_SILENT)
quiet = 1;
err = pam_get_item(pamh, PAM_USER, (void**)&user);
if (err != PAM_SUCCESS) {
goto cleanup;
}
/*
* Check if user name is *not* NULL
*/
if (user == NULL || (user[0] == '\0')) {
if (debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5-AUTOMIGRATE (auth): user empty or null");
goto cleanup;
}
/*
* Can't tolerate memory failure later on. Get a copy
* before any work is done.
*/
if ((userdata = strdup(user)) == NULL) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Out of memory");
goto cleanup;
}
/*
* Grok the user password
*/
err = pam_get_item(pamh, PAM_AUTHTOK, (void **)&password);
if (err != PAM_SUCCESS) {
goto cleanup;
}
if (password == NULL || (password[0] == '\0')) {
if (debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5-AUTOMIGRATE (auth): "
"authentication token is empty or null");
goto cleanup;
}
/*
* Now, lets do the all krb5/kadm5 setup for the principal addition
*/
if (retval = krb5_init_secure_context(&context)) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Error initializing "
"krb5: %s", error_message(retval));
goto cleanup;
}
(void) memset((char *)&params, 0, sizeof (params));
(void) memset(&kadm5_userprinc, 0, sizeof (kadm5_userprinc));
if (def_realm == NULL && krb5_get_default_realm(context, &def_realm)) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining "
"default krb5 realm");
goto cleanup;
}
params.mask |= KADM5_CONFIG_REALM;
params.realm = def_realm;
if (kadm5_get_adm_host_srv_names(context, def_realm,
&kadmin_princs)) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining "
"host based service name for realm %s\n", def_realm);
goto cleanup;
}
if (retval = krb5_sname_to_principal(context, NULL,
(service != NULL) ? service : "host", KRB5_NT_SRV_HST, &svcprinc)) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Error while creating "
"krb5 host service principal: %s",
error_message(retval));
goto cleanup;
}
if (retval = krb5_unparse_name(context, svcprinc,
&svcprincstr)) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Error while "
"unparsing principal name: %s", error_message(retval));
krb5_free_principal(context, svcprinc);
goto cleanup;
}
krb5_free_principal(context, svcprinc);
/*
* Initialize the kadm5 connection using the default keytab
*/
retval = kadm5_init_with_skey(context, svcprincstr, NULL,
kadmin_princs, &params, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2,
NULL, &handle);
if (retval) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Error while "
"doing kadm5_init_with_skey: %s", error_message(retval));
goto cleanup;
}
/*
* The RPCSEC_GSS connection has been established; Lets check to see
* if the corresponding user principal exists in the KDC database.
* If not, lets create a new one.
*/
strlength = strlen(user) + strlen(def_realm) + 2;
if ((userprincstr = malloc(strlength)) == NULL)
goto cleanup;
(void) strlcpy(userprincstr, user, strlength);
(void) strlcat(userprincstr, "@", strlength);
(void) strlcat(userprincstr, def_realm, strlength);
if (retval = krb5_parse_name(context, userprincstr,
&userprinc)) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Error while "
"parsing user principal name: %s",
error_message(retval));
goto cleanup;
}
retval = kadm5_get_principal(handle, userprinc, &kadm5_userprinc,
KADM5_PRINCIPAL_NORMAL_MASK);
krb5_free_principal(context, userprinc);
if (retval) {
switch (retval) {
case KADM5_AUTH_GET:
if (debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5-AUTOMIGRATE (auth): %s does "
"not have the GET privilege "
"for kadm5_get_principal: %s",
svcprincstr, error_message(retval));
break;
case KADM5_UNK_PRINC:
default:
break;
}
/*
* We will try & add this principal anyways, continue on ...
*/
(void) memset(&kadm5_userprinc, 0, sizeof (kadm5_userprinc));
} else {
/*
* Principal already exists in the KDC database, quit now
*/
if (debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5-AUTOMIGRATE (auth): Principal %s "
"already exists in Kerberos KDC database",
userprincstr);
goto cleanup;
}
if (retval = krb5_parse_name(context, userprincstr,
&(kadm5_userprinc.principal))) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Error while "
"parsing user principal name: %s",
error_message(retval));
goto cleanup;
}
if (expire_pw) {
(void) time(&now);
/*
* The local system time could actually be later than the
* system time of the KDC we are authenticating to. We expire
* w/the local system time minus clockskew so that we are
* assured that it is expired on this login, not the next.
*/
now -= context->clockskew;
kadm5_userprinc.pw_expiration = now;
mask |= KADM5_PW_EXPIRATION;
}
mask |= KADM5_PRINCIPAL;
retval = kadm5_create_principal(handle, &kadm5_userprinc,
mask, password);
if (retval) {
switch (retval) {
case KADM5_AUTH_ADD:
if (debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5-AUTOMIGRATE (auth): %s does "
"not have the ADD privilege "
"for kadm5_create_principal: %s",
svcprincstr, error_message(retval));
break;
default:
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5-AUTOMIGRATE (auth): Generic error"
"while doing kadm5_create_principal: %s",
error_message(retval));
break;
}
goto cleanup;
}
/*
* Success, new user principal has been added !
*/
if (!quiet) {
char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
(void) snprintf(messages[0], sizeof (messages[0]),
dgettext(TEXT_DOMAIN, "\nUser `%s' has been "
"automatically migrated to the Kerberos realm %s\n"),
user, def_realm);
(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1,
messages, NULL);
}
if (debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5-AUTOMIGRATE (auth): User %s "
"has been added to the Kerberos KDC database",
userprincstr);
/*
* Since this is a new krb5 principal, do a pam_set_data()
* for possible use by the acct_mgmt routine of pam_krb5(5)
*/
if (pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA,
(const void **)&olduserdata) == PAM_SUCCESS) {
/*
* We created a princ in a previous run on the same handle and
* it must have been for a different PAM_USER / princ name,
* otherwise we couldn't succeed here, unless that princ
* got deleted.
*/
if (olduserdata != NULL)
free(olduserdata);
}
if (pam_set_data(pamh, KRB5_AUTOMIGRATE_DATA, userdata,
krb5_migrate_cleanup) != PAM_SUCCESS) {
free(userdata);
}
cleanup:
if (service)
free(service);
if (kadmin_princs)
free_srv_names(kadmin_princs);
if (svcprincstr)
free(svcprincstr);
if (userprincstr)
free(userprincstr);
if (def_realm)
free(def_realm);
(void) kadm5_free_principal_ent(handle, &kadm5_userprinc);
(void) kadm5_destroy((void *)handle);
if (context != NULL)
krb5_free_context(context);
return (PAM_IGNORE);
}
/*ARGSUSED*/
static void
krb5_migrate_cleanup(pam_handle_t *pamh, void *data, int pam_status) {
if (data != NULL)
free((char *)data);
}
/*ARGSUSED*/
int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return (PAM_IGNORE);
}