/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* lib/krb5/os/changepw.c
*
* Copyright 1990,1999,2001,2008 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
* require a specific license from the United States Government.
* It is the responsibility of any person or organization contemplating
* export to obtain such a license before exporting.
*
* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
* distribute this software and its documentation for any purpose and
* without fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation, and that
* the name of M.I.T. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. Furthermore if you modify this software you must label
* your software as modified software and not distribute it in such a
* fashion that it might be confused with the original M.I.T. software.
* M.I.T. makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
*/
/*
* krb5_set_password - Implements set password per RFC 3244
* Added by Paul W. Nelson, Thursby Software Systems, Inc.
* Modified by Todd Stecher, Isilon Systems, to use krb1.4 socket infrastructure
*/
#include "fake-addrinfo.h"
#include "k5-int.h"
#include "os-proto.h"
#include "cm.h"
/* Solaris Kerberos: tweaked so auth_con.h could be found */
#include "auth_con.h"
#include <stdio.h>
#include <errno.h>
#ifndef GETSOCKNAME_ARG3_TYPE
#define GETSOCKNAME_ARG3_TYPE int
#endif
struct sendto_callback_context {
krb5_context context;
krb5_auth_context auth_context;
krb5_principal set_password_for;
char *newpw;
krb5_data ap_req;
krb5_ui_4 remote_seq_num, local_seq_num;
};
/*
* Wrapper function for the two backends
*/
/* Solaris Kerberos: removed static because other funcs are calling this */
krb5_error_code
locate_kpasswd(krb5_context context, const krb5_data *realm,
struct addrlist *addrlist, krb5_boolean useTcp)
{
krb5_error_code code;
int sockType = (useTcp ? SOCK_STREAM : SOCK_DGRAM);
code = krb5int_locate_server (context, realm, addrlist,
locate_service_kpasswd, sockType, AF_UNSPEC);
if (code == KRB5_REALM_CANT_RESOLVE || code == KRB5_REALM_UNKNOWN) {
code = krb5int_locate_server (context, realm, addrlist,
locate_service_kadmin, SOCK_STREAM,
AF_UNSPEC);
if (!code) {
/* Success with admin_server but now we need to change the
port number to use DEFAULT_KPASSWD_PORT and the socktype. */
size_t i;
for (i=0; i<addrlist->naddrs; i++) {
struct addrinfo *a = addrlist->addrs[i].ai;
krb5_ui_2 kpasswd_port = htons(DEFAULT_KPASSWD_PORT);
if (a->ai_family == AF_INET)
sa2sin (a->ai_addr)->sin_port = kpasswd_port;
if (a->ai_family == AF_INET6)
sa2sin6 (a->ai_addr)->sin6_port = kpasswd_port;
if (sockType != SOCK_STREAM)
a->ai_socktype = sockType;
}
}
}
return (code);
}
/**
* This routine is used for a callback in sendto_kdc.c code. Simply
* put, we need the client addr to build the krb_priv portion of the
* password request.
*/
static void
kpasswd_sendto_msg_cleanup (void* callback_context, krb5_data* message)
{
struct sendto_callback_context *ctx = callback_context;
krb5_free_data_contents(ctx->context, message);
}
static int
kpasswd_sendto_msg_callback(struct conn_state *conn,
void *callback_context,
krb5_data *message)
{
krb5_error_code code = 0;
struct sockaddr_storage local_addr;
krb5_address local_kaddr;
struct sendto_callback_context *ctx = callback_context;
GETSOCKNAME_ARG3_TYPE addrlen;
krb5_data output;
memset (message, 0, sizeof(krb5_data));
/*
* We need the local addr from the connection socket
*/
addrlen = sizeof(local_addr);
if (getsockname(conn->fd, ss2sa(&local_addr), &addrlen) < 0) {
code = SOCKET_ERRNO;
goto cleanup;
}
/* some brain-dead OS's don't return useful information from
* the getsockname call. Namely, windows and solaris. */
if (local_addr.ss_family == AF_INET &&
ss2sin(&local_addr)->sin_addr.s_addr != 0) {
local_kaddr.addrtype = ADDRTYPE_INET;
local_kaddr.length = sizeof(ss2sin(&local_addr)->sin_addr);
local_kaddr.contents = (krb5_octet *) &ss2sin(&local_addr)->sin_addr;
} else if (local_addr.ss_family == AF_INET6 &&
ss2sin6(&local_addr)->sin6_addr.s6_addr != 0) {
local_kaddr.addrtype = ADDRTYPE_INET6;
local_kaddr.length = sizeof(ss2sin6(&local_addr)->sin6_addr);
local_kaddr.contents = (krb5_octet *) &ss2sin6(&local_addr)->sin6_addr;
} else {
krb5_address **addrs;
code = krb5_os_localaddr(ctx->context, &addrs);
if (code)
goto cleanup;
local_kaddr.magic = addrs[0]->magic;
local_kaddr.addrtype = addrs[0]->addrtype;
local_kaddr.length = addrs[0]->length;
local_kaddr.contents = malloc(addrs[0]->length);
if (local_kaddr.contents == NULL && addrs[0]->length != 0) {
code = ENOMEM;
krb5_free_addresses(ctx->context, addrs);
goto cleanup;
}
if (addrs[0]->length)
memcpy(local_kaddr.contents, addrs[0]->contents, addrs[0]->length);
krb5_free_addresses(ctx->context, addrs);
}
/*
* TBD: Does this tamper w/ the auth context in such a way
* to break us? Yes - provide 1 per conn-state / host...
*/
if ((code = krb5_auth_con_setaddrs(ctx->context, ctx->auth_context,
&local_kaddr, NULL)))
goto cleanup;
ctx->auth_context->remote_seq_number = ctx->remote_seq_num;
ctx->auth_context->local_seq_number = ctx->local_seq_num;
if (ctx->set_password_for)
code = krb5int_mk_setpw_req(ctx->context,
ctx->auth_context,
&ctx->ap_req,
ctx->set_password_for,
ctx->newpw,
&output);
else
code = krb5int_mk_chpw_req(ctx->context,
ctx->auth_context,
&ctx->ap_req,
ctx->newpw,
&output);
if (code)
goto cleanup;
message->length = output.length;
message->data = output.data;
cleanup:
return code;
}
/*
** The logic for setting and changing a password is mostly the same
** change_set_password handles both cases
** if set_password_for is NULL, then a password change is performed,
** otherwise, the password is set for the principal indicated in set_password_for
*/
static krb5_error_code
change_set_password(krb5_context context,
krb5_creds *creds,
char *newpw,
krb5_principal set_password_for,
int *result_code,
krb5_data *result_code_string,
krb5_data *result_string)
{
krb5_data chpw_rep;
krb5_address remote_kaddr;
krb5_boolean useTcp = 0;
GETSOCKNAME_ARG3_TYPE addrlen;
krb5_error_code code = 0;
char *code_string;
int local_result_code;
struct sendto_callback_context callback_ctx;
struct sendto_callback_info callback_info;
struct sockaddr_storage remote_addr;
struct addrlist al = ADDRLIST_INIT;
memset(&chpw_rep, 0, sizeof(krb5_data));
memset( &callback_ctx, 0, sizeof(struct sendto_callback_context));
callback_ctx.context = context;
callback_ctx.newpw = newpw;
callback_ctx.set_password_for = set_password_for;
if ((code = krb5_auth_con_init(callback_ctx.context,
&callback_ctx.auth_context)))
goto cleanup;
if ((code = krb5_mk_req_extended(callback_ctx.context,
&callback_ctx.auth_context,
AP_OPTS_USE_SUBKEY,
NULL,
creds,
&callback_ctx.ap_req)))
goto cleanup;
callback_ctx.remote_seq_num = callback_ctx.auth_context->remote_seq_number;
callback_ctx.local_seq_num = callback_ctx.auth_context->local_seq_number;
do {
if ((code = locate_kpasswd(callback_ctx.context,
krb5_princ_realm(callback_ctx.context,
creds->server),
&al, useTcp)))
break;
addrlen = sizeof(remote_addr);
callback_info.context = (void*) &callback_ctx;
callback_info.pfn_callback = kpasswd_sendto_msg_callback;
callback_info.pfn_cleanup = kpasswd_sendto_msg_cleanup;
if ((code = krb5int_sendto(callback_ctx.context,
NULL,
&al,
&callback_info,
&chpw_rep,
NULL,
NULL,
ss2sa(&remote_addr),
&addrlen,
NULL,
NULL,
NULL
))) {
/*
* Here we may want to switch to TCP on some errors.
* right?
*/
break;
}
if (remote_addr.ss_family == AF_INET) {
remote_kaddr.addrtype = ADDRTYPE_INET;
remote_kaddr.length = sizeof(ss2sin(&remote_addr)->sin_addr);
remote_kaddr.contents =
(krb5_octet *) &ss2sin(&remote_addr)->sin_addr;
} else if (remote_addr.ss_family == AF_INET6) {
remote_kaddr.addrtype = ADDRTYPE_INET6;
remote_kaddr.length = sizeof(ss2sin6(&remote_addr)->sin6_addr);
remote_kaddr.contents =
(krb5_octet *) &ss2sin6(&remote_addr)->sin6_addr;
} else {
break;
}
if ((code = krb5_auth_con_setaddrs(callback_ctx.context,
callback_ctx.auth_context,
NULL,
&remote_kaddr)))
break;
if (set_password_for)
code = krb5int_rd_setpw_rep(callback_ctx.context,
callback_ctx.auth_context,
&chpw_rep,
&local_result_code,
result_string);
else
code = krb5int_rd_chpw_rep(callback_ctx.context,
callback_ctx.auth_context,
&chpw_rep,
&local_result_code,
result_string);
if (code) {
if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !useTcp ) {
krb5int_free_addrlist (&al);
useTcp = 1;
continue;
}
break;
}
if (result_code)
*result_code = local_result_code;
if (result_code_string) {
if (set_password_for)
code = krb5int_setpw_result_code_string(callback_ctx.context,
local_result_code,
(const char **)&code_string);
else
code = krb5_chpw_result_code_string(callback_ctx.context,
local_result_code,
&code_string);
if(code)
goto cleanup;
result_code_string->length = strlen(code_string);
result_code_string->data = malloc(result_code_string->length);
if (result_code_string->data == NULL) {
code = ENOMEM;
goto cleanup;
}
strncpy(result_code_string->data, code_string, result_code_string->length);
}
if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !useTcp ) {
krb5int_free_addrlist (&al);
useTcp = 1;
} else {
break;
}
} while (TRUE);
cleanup:
if (callback_ctx.auth_context != NULL)
krb5_auth_con_free(callback_ctx.context, callback_ctx.auth_context);
krb5int_free_addrlist (&al);
krb5_free_data_contents(callback_ctx.context, &callback_ctx.ap_req);
krb5_free_data_contents(callback_ctx.context, &chpw_rep);
return(code);
}
krb5_error_code KRB5_CALLCONV
krb5_change_password(krb5_context context,
krb5_creds *creds,
char *newpw,
int *result_code,
krb5_data *result_code_string,
krb5_data *result_string)
{
return change_set_password(context, creds, newpw, NULL,
result_code, result_code_string, result_string );
}
/*
* krb5_set_password - Implements set password per RFC 3244
*
*/
krb5_error_code KRB5_CALLCONV
krb5_set_password(krb5_context context,
krb5_creds *creds,
char *newpw,
krb5_principal change_password_for,
int *result_code,
krb5_data *result_code_string,
krb5_data *result_string
)
{
return change_set_password(context, creds, newpw, change_password_for,
result_code, result_code_string, result_string );
}
krb5_error_code KRB5_CALLCONV
krb5_set_password_using_ccache(krb5_context context,
krb5_ccache ccache,
char *newpw,
krb5_principal change_password_for,
int *result_code,
krb5_data *result_code_string,
krb5_data *result_string
)
{
krb5_creds creds;
krb5_creds *credsp;
krb5_error_code code;
/*
** get the proper creds for use with krb5_set_password -
*/
memset (&creds, 0, sizeof(creds));
/*
** first get the principal for the password service -
*/
code = krb5_cc_get_principal (context, ccache, &creds.client);
if (!code) {
code = krb5_build_principal(context, &creds.server,
krb5_princ_realm(context, change_password_for)->length,
krb5_princ_realm(context, change_password_for)->data,
"kadmin", "changepw", NULL);
if (!code) {
code = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
if (!code) {
code = krb5_set_password(context, credsp, newpw, change_password_for,
result_code, result_code_string,
result_string);
krb5_free_creds(context, credsp);
}
}
krb5_free_cred_contents(context, &creds);
}
return code;
}