diff options
Diffstat (limited to 'src/lib/krb5/os/changepw.c')
| -rw-r--r-- | src/lib/krb5/os/changepw.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/src/lib/krb5/os/changepw.c b/src/lib/krb5/os/changepw.c new file mode 100644 index 0000000000000..e4db57084ddbe --- /dev/null +++ b/src/lib/krb5/os/changepw.c @@ -0,0 +1,398 @@ +/* -*- 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 "k5-int.h" +#include "fake-addrinfo.h" +#include "os-proto.h" +#include "../krb/auth_con.h" +#include "../krb/int-proto.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; + const char *newpw; + krb5_data ap_req; + krb5_ui_4 remote_seq_num, local_seq_num; +}; + +/* + * Wrapper function for the two backends + */ + +static krb5_error_code +locate_kpasswd(krb5_context context, const krb5_data *realm, + struct serverlist *serverlist, krb5_boolean no_udp) +{ + krb5_error_code code; + + code = k5_locate_server(context, realm, serverlist, locate_service_kpasswd, + no_udp); + + if (code == KRB5_REALM_CANT_RESOLVE || code == KRB5_REALM_UNKNOWN) { + code = k5_locate_server(context, realm, serverlist, + locate_service_kadmin, TRUE); + if (!code) { + /* Success with admin_server but now we need to change the port + * number to use DEFAULT_KPASSWD_PORT and the transport. */ + size_t i; + for (i = 0; i < serverlist->nservers; i++) { + struct server_entry *s = &serverlist->servers[i]; + + if (!no_udp && s->transport == TCP) + s->transport = TCP_OR_UDP; + if (s->hostname != NULL) + s->port = DEFAULT_KPASSWD_PORT; + else if (s->family == AF_INET) + ss2sin(&s->addr)->sin_port = htons(DEFAULT_KPASSWD_PORT); + else if (s->family == AF_INET6) + ss2sin6(&s->addr)->sin6_port = htons(DEFAULT_KPASSWD_PORT); + } + } + } + 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 *data, krb5_data *message) +{ + struct sendto_callback_context *ctx = data; + + krb5_free_data_contents(ctx->context, message); +} + + +static int +kpasswd_sendto_msg_callback(SOCKET fd, void *data, krb5_data *message) +{ + krb5_error_code code = 0; + struct sockaddr_storage local_addr; + krb5_address local_kaddr; + struct sendto_callback_context *ctx = data; + 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(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 && + memcmp(ss2sin6(&local_addr)->sin6_addr.s6_addr, + in6addr_any.s6_addr, sizeof(in6addr_any.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 = k5memdup(addrs[0]->contents, addrs[0]->length, + &code); + krb5_free_addresses(ctx->context, addrs); + if (local_kaddr.contents == NULL) + goto cleanup; + } + + + /* + * 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, + const char *newpw, + krb5_principal set_password_for, + int *result_code, + krb5_data *result_code_string, + krb5_data *result_string) +{ + krb5_data chpw_rep; + krb5_boolean no_udp = FALSE; + 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 serverlist sl = SERVERLIST_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 { + k5_transport_strategy strategy = no_udp ? NO_UDP : UDP_FIRST; + + code = locate_kpasswd(callback_ctx.context, &creds->server->realm, &sl, + no_udp); + if (code) + break; + + addrlen = sizeof(remote_addr); + + callback_info.data = &callback_ctx; + callback_info.pfn_callback = kpasswd_sendto_msg_callback; + callback_info.pfn_cleanup = kpasswd_sendto_msg_cleanup; + krb5_free_data_contents(callback_ctx.context, &chpw_rep); + + code = k5_sendto(callback_ctx.context, NULL, &creds->server->realm, + &sl, strategy, &callback_info, &chpw_rep, + ss2sa(&remote_addr), &addrlen, NULL, NULL, NULL); + if (code) { + /* + * Here we may want to switch to TCP on some errors. + * right? + */ + break; + } + + 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 && !no_udp) { + k5_free_serverlist(&sl); + no_udp = 1; + continue; + } + + break; + } + + if (result_code) + *result_code = local_result_code; + + if (result_code_string) { + 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 && !no_udp) { + k5_free_serverlist(&sl); + no_udp = 1; + } else { + break; + } + } while (TRUE); + +cleanup: + if (callback_ctx.auth_context != NULL) + krb5_auth_con_free(callback_ctx.context, callback_ctx.auth_context); + + k5_free_serverlist(&sl); + 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, + const 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, + const 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, + const 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, + change_password_for->realm.length, + change_password_for->realm.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; +} |
