summaryrefslogtreecommitdiff
path: root/src/lib/krb5/os/changepw.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/krb5/os/changepw.c')
-rw-r--r--src/lib/krb5/os/changepw.c398
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;
+}