From 8d8fc0bdc087ec77418f0832969e1dfc6dfa1a7c Mon Sep 17 00:00:00 2001 From: Ken Raeburn Date: Wed, 23 Aug 2006 22:56:29 +0000 Subject: [PATCH] Merge Todd's TCP changepw support, with a few fixups * include/cm.h (state_strings, enum conn_states, struct incoming_krb5_message, struct conn_state): Moved here from lib/krb5/os/sendto_kdc.c. (stuct sendto_callback_info): New type. * lib/krb5/os/sendto_kdc.c (set_conn_state_msg_length): New function. (setup_connection): Deleted argument message_len_buf. Don't store message length; call set_conn_state_msg_length instead. (start_connection): New arguments callback_info and callback_buffer. Invoke callback function if any, and set message length on success. (maybe_send): New arguments callback_info and callback_buffer; pass them to start_connection. (krb5int_sendto): New arguments callback_info, remoteaddr, remoteaddrlen. If callback info is provided, allocate per-connection buffers, and pass them to maybe_send. On cleanup, invoke the cleanup callback function if any. (krb5_sendto_kdc): Update krb5int_sendto call. * include/k5-int.h (struct sendto_callback_info): Add forward declaration. (krb5int_sendto, struct _krb5int_access.sendto_udp): Update for new signature. * lib/krb5/os/send524 (krb5int_524_sendto_kdc): Update krb5int_sendto call. * lib/krb4/send_to_kdc.c (krb5int_send_to_kdc_addr): Update sendto_udp call. * lib/krb5/os/changepw.c (struct sendto_callback_context): New type. (krb5_locate_kpasswd): New argument useTcp, used to select socket type in krb5int_locate_server call. (kpasswd_sendto_msg_cleanup, kpasswd_sendto_msg_callback): New functions. (krb5_change_set_password): Call krb5int_sendto with callbacks, instead of managing the exchange here. On RESPONSE_TOO_BIG error, try again with TCP only. * lib/krb5/krb/chpw.c (krb5int_rd_chpw_rep): If length is wrong, check if a buggy server sent a KRB_ERROR. git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@18518 dc483132-0cff-0310-8789-dd5450dbe970 --- src/include/cm.h | 42 +++ src/include/k5-int.h | 17 +- src/lib/krb4/send_to_kdc.c | 4 +- src/lib/krb5/krb/chpw.c | 41 ++- src/lib/krb5/os/changepw.c | 508 ++++++++++++++++------------------- src/lib/krb5/os/send524.c | 2 +- src/lib/krb5/os/sendto_kdc.c | 166 ++++++++---- 7 files changed, 444 insertions(+), 336 deletions(-) diff --git a/src/include/cm.h b/src/include/cm.h index 428e61e58..716e6cb59 100644 --- a/src/include/cm.h +++ b/src/include/cm.h @@ -32,10 +32,52 @@ struct select_state { struct timeval end_time; /* magic: tv_sec==0 => never time out */ }; + /* Select state flags. */ #define SSF_READ 0x01 #define SSF_WRITE 0x02 #define SSF_EXCEPTION 0x04 + +static const char *const state_strings[] = { + "INITIALIZING", "CONNECTING", "WRITING", "READING", "FAILED" +}; + + +/* connection states */ +enum conn_states { INITIALIZING, CONNECTING, WRITING, READING, FAILED }; +struct incoming_krb5_message { + size_t bufsizebytes_read; + size_t bufsize; + char *buf; + char *pos; + unsigned char bufsizebytes[4]; + size_t n_left; +}; +struct conn_state { + SOCKET fd; + krb5_error_code err; + enum conn_states state; + unsigned int is_udp : 1; + int (*service)(struct conn_state *, struct select_state *, int); + struct addrinfo *addr; + struct { + struct { + sg_buf sgbuf[2]; + sg_buf *sgp; + int sg_count; + unsigned char msg_len_buf[4]; + } out; + struct incoming_krb5_message in; + } x; +}; + +struct sendto_callback_info { + int (*pfn_callback) (struct conn_state *, void *, krb5_data *); + void (*pfn_cleanup) (void *, krb5_data *); + void *context; +}; + + krb5_error_code krb5int_cm_call_select (const struct select_state *, struct select_state *, int *); diff --git a/src/include/k5-int.h b/src/include/k5-int.h index a8b9e5d99..a55fea7a4 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -487,15 +487,19 @@ extern char *strdup (const char *); #include struct addrlist; +struct sendto_callback_info; /* libos.spec */ krb5_error_code krb5_lock_file (krb5_context, int, int); krb5_error_code krb5_unlock_file (krb5_context, int); krb5_error_code krb5_sendto_kdc (krb5_context, const krb5_data *, const krb5_data *, krb5_data *, int *, int); -krb5_error_code krb5int_sendto (krb5_context, const krb5_data *, - const struct addrlist *, krb5_data *, - struct sockaddr *, socklen_t *, int *); + +krb5_error_code krb5int_sendto (krb5_context context, const krb5_data *message, + const struct addrlist *addrs, struct sendto_callback_info* callback_info, + krb5_data *reply, struct sockaddr *localaddr, socklen_t *localaddrlen, + struct sockaddr *remoteaddr, socklen_t *remoteaddrlen, int *addr_used); + krb5_error_code krb5_get_krbhst (krb5_context, const krb5_data *, char *** ); krb5_error_code krb5_free_krbhst (krb5_context, char * const * ); krb5_error_code krb5_create_secure_file (krb5_context, const char * pathname); @@ -1610,7 +1614,7 @@ krb5int_generate_and_save_subkey (krb5_context, krb5_auth_context, /* set and change password helpers */ krb5_error_code krb5int_mk_chpw_req - (krb5_context context, krb5_auth_context auth_context, + (krb5_context context, krb5_auth_context auth_context, krb5_data *ap_req, char *passwd, krb5_data *packet); krb5_error_code krb5int_rd_chpw_rep (krb5_context context, krb5_auth_context auth_context, @@ -1673,8 +1677,9 @@ typedef struct _krb5int_access { krb5_data *output); /* service location and communication */ krb5_error_code (*sendto_udp) (krb5_context, const krb5_data *msg, - const struct addrlist *, krb5_data *reply, - struct sockaddr *, socklen_t *, int *); + const struct addrlist *, struct sendto_callback_info*, krb5_data *reply, + struct sockaddr *, socklen_t *,struct sockaddr *, + socklen_t *, int *); krb5_error_code (*add_host_to_list)(struct addrlist *lp, const char *hostname, int port, int secport, diff --git a/src/lib/krb4/send_to_kdc.c b/src/lib/krb4/send_to_kdc.c index 3be677b6c..a33ad2b03 100644 --- a/src/lib/krb4/send_to_kdc.c +++ b/src/lib/krb4/send_to_kdc.c @@ -180,8 +180,8 @@ krb4int_send_to_kdc_addr( message.length = pkt->length; message.data = (char *)pkt->dat; /* XXX yuck */ - retval = internals.sendto_udp(NULL, &message, &al, &reply, addr, - addrlen, NULL); + retval = internals.sendto_udp(NULL, &message, &al, NULL, &reply, addr, + addrlen, NULL, 0, NULL); DEB(("sendto_udp returns %d\n", retval)); free_al: internals.free_addrlist(&al); diff --git a/src/lib/krb5/krb/chpw.c b/src/lib/krb5/krb/chpw.c index 640124601..427ea39aa 100644 --- a/src/lib/krb5/krb/chpw.c +++ b/src/lib/krb5/krb/chpw.c @@ -9,7 +9,12 @@ krb5_error_code -krb5int_mk_chpw_req(krb5_context context, krb5_auth_context auth_context, krb5_data *ap_req, char *passwd, krb5_data *packet) +krb5int_mk_chpw_req( + krb5_context context, + krb5_auth_context auth_context, + krb5_data *ap_req, + char *passwd, + krb5_data *packet) { krb5_error_code ret = 0; krb5_data clearpw; @@ -33,15 +38,15 @@ krb5int_mk_chpw_req(krb5_context context, krb5_auth_context auth_context, krb5_d packet->length = 6 + ap_req->length + cipherpw.length; packet->data = (char *) malloc(packet->length); if (packet->data == NULL) - { + { ret = ENOMEM; goto cleanup; - } + } ptr = packet->data; /* length */ - *ptr++ = (packet->length>>8) & 0xff; + *ptr++ = (packet->length>> 8) & 0xff; *ptr++ = packet->length & 0xff; /* version == 0x0001 big-endian */ @@ -96,8 +101,30 @@ krb5int_rd_chpw_rep(krb5_context context, krb5_auth_context auth_context, krb5_d plen = (*ptr++ & 0xff); plen = (plen<<8) | (*ptr++ & 0xff); - if (plen != packet->length) - return(KRB5KRB_AP_ERR_MODIFIED); + if (plen != packet->length) + { + /* + * MS KDCs *may* send back a KRB_ERROR. Although + * not 100% correct via RFC3244, it's something + * we can workaround here. + */ + if (krb5_is_krb_error(packet)) { + + if ((ret = krb5_rd_error(context, packet, &krberror))) + return(ret); + + if (krberror->e_data.data == NULL) { + ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code) krberror->error; + krb5_free_error(context, krberror); + return (ret); + } + } + else + { + return(KRB5KRB_AP_ERR_MODIFIED); + } + } + /* verify version number */ @@ -367,7 +394,7 @@ krb5int_rd_setpw_rep( krb5_context context, krb5_auth_context auth_context, krb5 /* ** set password version is 0xff80, change password version is 1 */ - if (version_number != 0xff80 && version_number != 1) + if (version_number != 1 && version_number != 0xff80) return(KRB5KDC_ERR_BAD_PVNO); /* ** now fill in ap_rep with the reply - diff --git a/src/lib/krb5/os/changepw.c b/src/lib/krb5/os/changepw.c index 5f900b621..60f2d1d64 100644 --- a/src/lib/krb5/os/changepw.c +++ b/src/lib/krb5/os/changepw.c @@ -27,11 +27,13 @@ /* * 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" #include #include @@ -40,26 +42,37 @@ #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; +}; + + /* * Wrapper function for the two backends */ static krb5_error_code krb5_locate_kpasswd(krb5_context context, const krb5_data *realm, - struct addrlist *addrlist) + 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, 0, 0); + locate_service_kpasswd, sockType, 0); + if (code == KRB5_REALM_CANT_RESOLVE || code == KRB5_REALM_UNKNOWN) { code = krb5int_locate_server (context, realm, addrlist, - locate_service_kadmin, 1, 0); + locate_service_kadmin, SOCK_STREAM, 0); if (!code) { /* Success with admin_server but now we need to change the port number to use DEFAULT_KPASSWD_PORT. */ int i; - for ( i=0;inaddrs;i++ ) { + for (i=0; inaddrs; i++) { struct addrinfo *a = addrlist->addrs[i].ai; if (a->ai_family == AF_INET) sa2sin (a->ai_addr)->sin_port = htons(DEFAULT_KPASSWD_PORT); @@ -70,253 +83,225 @@ krb5_locate_kpasswd(krb5_context context, const krb5_data *realm, } -/* -** The logic for setting and changing a password is mostly the same -** krb5_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 -*/ -krb5_error_code KRB5_CALLCONV -krb5_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) +/** + * 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) { - krb5_auth_context auth_context; - krb5_data ap_req, chpw_req, chpw_rep; - krb5_address local_kaddr, remote_kaddr; - char *code_string; - krb5_error_code code = 0; - int i; - GETSOCKNAME_ARG3_TYPE addrlen; - struct sockaddr_storage local_addr, remote_addr, tmp_addr; - int cc, local_result_code; - /* platforms seem to be consistant and use the same types */ - GETSOCKNAME_ARG3_TYPE tmp_len; - SOCKET s1 = INVALID_SOCKET, s2 = INVALID_SOCKET; - int tried_one = 0; - struct addrlist al = ADDRLIST_INIT; - - - /* Initialize values so that cleanup call can safely check for NULL */ - auth_context = NULL; - memset(&chpw_req, 0, sizeof(krb5_data)); - memset(&chpw_rep, 0, sizeof(krb5_data)); - memset(&ap_req, 0, sizeof(krb5_data)); - - /* initialize auth_context so that we know we have to free it */ - if ((code = krb5_auth_con_init(context, &auth_context))) - goto cleanup; - - if ((code = krb5_mk_req_extended(context, &auth_context, - AP_OPTS_USE_SUBKEY, - NULL, creds, &ap_req))) - goto cleanup; - - if ((code = krb5_locate_kpasswd(context, - krb5_princ_realm(context, creds->server), - &al))) - goto cleanup; - - /* this is really obscure. s1 is used for all communications. it - is left unconnected in case the server is multihomed and routes - are asymmetric. s2 is connected to resolve routes and get - addresses. this is the *only* way to get proper addresses for - multihomed hosts if routing is asymmetric. - - A related problem in the server, but not the client, is that - many os's have no way to disconnect a connected udp socket, so - the s2 socket needs to be closed and recreated for each - request. The s1 socket must not be closed, or else queued - requests will be lost. - - A "naive" client implementation (one socket, no connect, - hostname resolution to get the local ip addr) will work and - interoperate if the client is single-homed. */ - - if ((s1 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) { - code = SOCKET_ERRNO; - goto cleanup; - } + 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; - if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) { + 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; } - /* - * This really should try fallback addresses in cases of timeouts. - * For now, where the MIT KDC implementation only supports one - * kpasswd server machine anyways, we'll only try the first IPv4 - * address we can connect() to. This isn't right for multi-homed - * servers; oh well. - */ - for (i=0; iai_family != AF_INET) - continue; + if (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 { + krb5_address **addrs; - tried_one = 1; - if (connect(s2, al.addrs[i].ai->ai_addr, al.addrs[i].ai->ai_addrlen) == SOCKET_ERROR) { - if (SOCKET_ERRNO == ECONNREFUSED || SOCKET_ERRNO == EHOSTUNREACH) - continue; /* try the next addr */ + code = krb5_os_localaddr(ctx->context, &addrs); + if (code) + goto cleanup; - code = SOCKET_ERRNO; + 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 = errno; + krb5_free_addresses(ctx->context, addrs); goto cleanup; } + memcpy(local_kaddr.contents, addrs[0]->contents, addrs[0]->length); - addrlen = sizeof(local_addr); + krb5_free_addresses(ctx->context, addrs); + } - if (getsockname(s2, ss2sa(&local_addr), &addrlen) < 0) { - if (SOCKET_ERRNO == ECONNREFUSED || SOCKET_ERRNO == EHOSTUNREACH) - continue; /* try the next addr */ - code = SOCKET_ERRNO; - goto cleanup; - } + /* + * TBD: Does this tamper w/ the auth context in such a way + * to break us? Yes - provide 1 per conn-state / host... + */ - /* some brain-dead OS's don't return useful information from - * the getsockname call. Namely, windows and solaris. */ - if (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 { - krb5_address **addrs; + if ((code = krb5_auth_con_setaddrs(ctx->context, ctx->auth_context, + &local_kaddr, NULL))) + goto cleanup; - krb5_os_localaddr(context, &addrs); + 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; - 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); - memcpy(local_kaddr.contents, addrs[0]->contents, addrs[0]->length); + message->length = output.length; + message->data = output.data; - krb5_free_addresses(context, addrs); - } +cleanup: + return code; +} + + +/* +** The logic for setting and changing a password is mostly the same +** krb5_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 +*/ +krb5_error_code KRB5_CALLCONV +krb5_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( &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; + + do { + if ((code = krb5_locate_kpasswd(callback_ctx.context, + krb5_princ_realm(callback_ctx.context, + creds->server), + &al, useTcp))) + break; addrlen = sizeof(remote_addr); - if (getpeername(s2, ss2sa(&remote_addr), &addrlen) < 0) { - if (SOCKET_ERRNO == ECONNREFUSED || SOCKET_ERRNO == EHOSTUNREACH) - continue; /* try the next addr */ - code = SOCKET_ERRNO; - goto cleanup; + 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 + ))) { + + /* + * Here we may want to switch to TCP on some errors. + * right? + */ + break; } remote_kaddr.addrtype = ADDRTYPE_INET; remote_kaddr.length = sizeof(ss2sin(&remote_addr)->sin_addr); remote_kaddr.contents = (krb5_octet *) &ss2sin(&remote_addr)->sin_addr; - /* mk_priv requires that the local address be set. - getsockname is used for this. rd_priv requires that the - remote address be set. recvfrom is used for this. If - rd_priv is given a local address, and the message has the - recipient addr in it, this will be checked. However, there - is simply no way to know ahead of time what address the - message will be delivered *to*. Therefore, it is important - that either no recipient address is in the messages when - mk_priv is called, or that no local address is passed to - rd_priv. Both is a better idea, and I have done that. In - summary, when mk_priv is called, *only* a local address is - specified. when rd_priv is called, *only* a remote address - is specified. Are we having fun yet? */ - - if ((code = krb5_auth_con_setaddrs(context, auth_context, - &local_kaddr, NULL))) { - goto cleanup; - } - - if( set_password_for ) - code = krb5int_mk_setpw_req(context, auth_context, &ap_req, set_password_for, newpw, &chpw_req); + 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_mk_chpw_req(context, auth_context, &ap_req, newpw, &chpw_req); - if (code) - { - goto cleanup; - } - - if ((cc = sendto(s1, chpw_req.data, - (GETSOCKNAME_ARG3_TYPE) chpw_req.length, 0, - al.addrs[i].ai->ai_addr, al.addrs[i].ai->ai_addrlen)) - != chpw_req.length) - { - if ((cc < 0) && ((SOCKET_ERRNO == ECONNREFUSED) || - (SOCKET_ERRNO == EHOSTUNREACH))) - continue; /* try the next addr */ - - code = (cc < 0) ? SOCKET_ERRNO : ECONNABORTED; - goto cleanup; - } - - chpw_rep.length = 1500; - chpw_rep.data = (char *) malloc(chpw_rep.length); - - /* XXX need a timeout/retry loop here */ - FD_ZERO (&fdset); - FD_SET (s1, &fdset); - timeout.tv_sec = 120; - timeout.tv_usec = 0; - switch (select (s1 + 1, &fdset, 0, 0, &timeout)) { - case -1: - code = SOCKET_ERRNO; - goto cleanup; - case 0: - code = ETIMEDOUT; - goto cleanup; - default: - /* fall through */ - ; - } + 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; + } - /* "recv" would be good enough here... except that Windows/NT - commits the atrocity of returning -1 to indicate failure, - but leaving errno set to 0. - - "recvfrom(...,NULL,NULL)" would seem to be a good enough - alternative, and it works on NT, but it doesn't work on - SunOS 4.1.4 or Irix 5.3. Thus we must actually accept the - value and discard it. */ - tmp_len = sizeof(tmp_addr); - if ((cc = recvfrom(s1, chpw_rep.data, - (GETSOCKNAME_ARG3_TYPE) chpw_rep.length, - 0, ss2sa(&tmp_addr), &tmp_len)) < 0) - { - code = SOCKET_ERRNO; - goto cleanup; + break; } - closesocket(s1); - s1 = INVALID_SOCKET; - closesocket(s2); - s2 = INVALID_SOCKET; - - chpw_rep.length = cc; - - if ((code = krb5_auth_con_setaddrs(context, auth_context, - NULL, &remote_kaddr))) - goto cleanup; - - if( set_password_for ) - code = krb5int_rd_setpw_rep(context, auth_context, &chpw_rep, &local_result_code, result_string); - else - code = krb5int_rd_chpw_rep(context, auth_context, &chpw_rep, &local_result_code, result_string); - if (code) - goto cleanup; - if (result_code) *result_code = local_result_code; - + if (result_code_string) { - if( set_password_for ) - code = krb5int_setpw_result_code_string(context, local_result_code, (const char **)&code_string); - else - code = krb5_chpw_result_code_string(context, local_result_code, &code_string); - if(code) - goto cleanup; + 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); @@ -327,34 +312,20 @@ krb5_change_set_password( strncpy(result_code_string->data, code_string, result_code_string->length); } - code = 0; - goto cleanup; - } - - if (tried_one) - /* Got some non-fatal errors, but didn't get any successes. */ - code = SOCKET_ERRNO; - else - /* Had some addresses, but didn't try any because they weren't - AF_INET addresses and we don't support AF_INET6 addresses - here yet. */ - code = EHOSTUNREACH; + if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !useTcp ) { + krb5int_free_addrlist (&al); + useTcp = 1; + } else { + break; + } + } while (TRUE); cleanup: - if (auth_context != NULL) - krb5_auth_con_free(context, auth_context); + if (callback_ctx.auth_context != NULL) + krb5_auth_con_free(callback_ctx.context, callback_ctx.auth_context); krb5int_free_addrlist (&al); - - if (s1 != INVALID_SOCKET) - closesocket(s1); - - if (s2 != INVALID_SOCKET) - closesocket(s2); - - krb5_free_data_contents(context, &chpw_req); - krb5_free_data_contents(context, &chpw_rep); - krb5_free_data_contents(context, &ap_req); + krb5_free_data_contents(callback_ctx.context, &callback_ctx.ap_req); return(code); } @@ -393,36 +364,33 @@ krb5_set_password_using_ccache( int *result_code, krb5_data *result_code_string, krb5_data *result_string ) { - krb5_creds creds; - krb5_creds *credsp; - krb5_error_code code; + 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); + /* + ** 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); + } } - return code; + krb5_free_cred_contents(context, &creds); + } + return code; } diff --git a/src/lib/krb5/os/send524.c b/src/lib/krb5/os/send524.c index 09c9c9022..f6e7a0b56 100644 --- a/src/lib/krb5/os/send524.c +++ b/src/lib/krb5/os/send524.c @@ -98,7 +98,7 @@ krb5int_524_sendto_kdc (context, message, realm, reply, addr, addrlen) if (al.naddrs == 0) return KRB5_REALM_UNKNOWN; - retval = krb5int_sendto (context, message, &al, reply, addr, addrlen, NULL); + retval = krb5int_sendto (context, message, &al, NULL, reply, addr, addrlen, NULL, 0, NULL); krb5int_free_addrlist (&al); return retval; #else diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c index 75cb03678..b616578a0 100644 --- a/src/lib/krb5/os/sendto_kdc.c +++ b/src/lib/krb5/os/sendto_kdc.c @@ -381,8 +381,8 @@ krb5_sendto_kdc (krb5_context context, const krb5_data *message, } if (addrs.naddrs > 0) { - retval = krb5int_sendto (context, message, &addrs, reply, 0, 0, - &addr_used); + retval = krb5int_sendto (context, message, &addrs, 0, reply, 0, 0, + 0, 0, &addr_used); if (retval == 0) { /* * Set use_master to 1 if we ended up talking to a master when @@ -448,35 +448,6 @@ krb5_sendto_kdc (krb5_context context, const krb5_data *message, #include "cm.h" -static const char *const state_strings[] = { - "INITIALIZING", "CONNECTING", "WRITING", "READING", "FAILED" -}; -enum conn_states { INITIALIZING, CONNECTING, WRITING, READING, FAILED }; -struct incoming_krb5_message { - size_t bufsizebytes_read; - size_t bufsize; - char *buf; - char *pos; - unsigned char bufsizebytes[4]; - size_t n_left; -}; -struct conn_state { - SOCKET fd; - krb5_error_code err; - enum conn_states state; - unsigned int is_udp : 1; - int (*service)(struct conn_state *, struct select_state *, int); - struct addrinfo *addr; - struct { - struct { - sg_buf sgbuf[2]; - sg_buf *sgp; - int sg_count; - } out; - struct incoming_krb5_message in; - } x; -}; - static int getcurtime (struct timeval *tvp) { #ifdef _WIN32 @@ -552,11 +523,37 @@ static int service_tcp_fd (struct conn_state *conn, static int service_udp_fd (struct conn_state *conn, struct select_state *selstate, int ssflags); +static void +set_conn_state_msg_length (struct conn_state *state, const krb5_data *message) +{ + if (!message || message->length == 0) + return; + + if (!state->is_udp) { + + state->x.out.msg_len_buf[0] = (message->length >> 24) & 0xff; + state->x.out.msg_len_buf[1] = (message->length >> 16) & 0xff; + state->x.out.msg_len_buf[2] = (message->length >> 8) & 0xff; + state->x.out.msg_len_buf[3] = message->length & 0xff; + + SG_SET(&state->x.out.sgbuf[0], state->x.out.msg_len_buf, 4); + SG_SET(&state->x.out.sgbuf[1], message->data, message->length); + state->x.out.sg_count = 2; + + } else { + + SG_SET(&state->x.out.sgbuf[0], message->data, message->length); + SG_SET(&state->x.out.sgbuf[1], 0, 0); + state->x.out.sg_count = 1; + + } +} + + static int setup_connection (struct conn_state *state, struct addrinfo *ai, - const krb5_data *message, unsigned char *message_len_buf, - char **udpbufp) + const krb5_data *message, char **udpbufp) { state->state = INITIALIZING; state->err = 0; @@ -565,17 +562,25 @@ setup_connection (struct conn_state *state, struct addrinfo *ai, state->fd = INVALID_SOCKET; SG_SET(&state->x.out.sgbuf[1], 0, 0); if (ai->ai_socktype == SOCK_STREAM) { + /* SG_SET(&state->x.out.sgbuf[0], message_len_buf, 4); SG_SET(&state->x.out.sgbuf[1], message->data, message->length); state->x.out.sg_count = 2; + */ + state->is_udp = 0; state->service = service_tcp_fd; + set_conn_state_msg_length (state, message); } else { + /* SG_SET(&state->x.out.sgbuf[0], message->data, message->length); SG_SET(&state->x.out.sgbuf[1], 0, 0); state->x.out.sg_count = 1; + */ + state->is_udp = 1; state->service = service_udp_fd; + set_conn_state_msg_length (state, message); if (*udpbufp == 0) { *udpbufp = malloc(krb5_max_dgram_size); @@ -594,7 +599,10 @@ setup_connection (struct conn_state *state, struct addrinfo *ai, } static int -start_connection (struct conn_state *state, struct select_state *selstate) +start_connection (struct conn_state *state, + struct select_state *selstate, + struct sendto_callback_info* callback_info, + krb5_data* callback_buffer) { int fd, e; struct addrinfo *ai = state->addr; @@ -628,6 +636,7 @@ start_connection (struct conn_state *state, struct select_state *selstate) */ if (SOCKET_ERRNO == EINPROGRESS || SOCKET_ERRNO == EWOULDBLOCK) { state->state = CONNECTING; + state->fd = fd; } else { dprint("connect failed: %m\n", SOCKET_ERRNO); (void) closesocket(fd); @@ -643,10 +652,36 @@ start_connection (struct conn_state *state, struct select_state *selstate) * stack is broken, but if they gave us a connection, use it. */ state->state = WRITING; + state->fd = fd; } dprint("new state = %s\n", state_strings[state->state]); - state->fd = fd; + + /* + * Here's where KPASSWD callback gets the socket information it needs for + * a kpasswd request + */ + if (callback_info) { + + e = callback_info->pfn_callback(state, + callback_info->context, + callback_buffer); + if (e != 0) { + dprint("callback failed: %m\n", e); + (void) closesocket(fd); + state->err = e; + state->fd = INVALID_SOCKET; + state->state = FAILED; + return -3; + } + + dprint("callback %p (message=%d@%p)\n", + state, + callback_buffer->length, + callback_buffer->data); + + set_conn_state_msg_length( state, callback_buffer ); + } if (ai->ai_socktype == SOCK_DGRAM) { /* Send it now. */ @@ -660,7 +695,7 @@ start_connection (struct conn_state *state, struct select_state *selstate) (void) closesocket(state->fd); state->fd = INVALID_SOCKET; state->state = FAILED; - return -3; + return -4; } else { state->state = READING; } @@ -699,7 +734,10 @@ start_connection (struct conn_state *state, struct select_state *selstate) Otherwise, the caller should immediately move on to process the next connection. */ static int -maybe_send (struct conn_state *conn, struct select_state *selstate) +maybe_send (struct conn_state *conn, + struct select_state *selstate, + struct sendto_callback_info* callback_info, + krb5_data* callback_buffer) { sg_buf *sg; @@ -707,7 +745,7 @@ maybe_send (struct conn_state *conn, struct select_state *selstate) state_strings[conn->state], conn->is_udp ? "udp" : "tcp"); if (conn->state == INITIALIZING) - return start_connection(conn, selstate); + return start_connection(conn, selstate, callback_info, callback_buffer); /* Did we already shut down this channel? */ if (conn->state == FAILED) { @@ -1056,22 +1094,27 @@ service_fds (struct select_state *selstate, krb5_error_code krb5int_sendto (krb5_context context, const krb5_data *message, - const struct addrlist *addrs, krb5_data *reply, - struct sockaddr *localaddr, socklen_t *localaddrlen, - int *addr_used) + const struct addrlist *addrs, + struct sendto_callback_info* callback_info, krb5_data *reply, + struct sockaddr *localaddr, socklen_t *localaddrlen, + struct sockaddr *remoteaddr, socklen_t *remoteaddrlen, + int *addr_used) { int i, pass; int delay_this_pass = 2; krb5_error_code retval; struct conn_state *conns; + krb5_data *callback_data = 0; size_t n_conns, host; struct select_state *sel_state; struct timeval now; int winning_conn = -1, e = 0; - unsigned char message_len_buf[4]; char *udpbuf = 0; - dprint("krb5int_sendto(message=%d@%p, addrlist=", message->length, message->data); + if (message) + dprint("krb5int_sendto(message=%d@%p, addrlist=", message->length, message->data); + else + dprint("krb5int_sendto(callback=%p, addrlist=", callback_info); print_addrlist(addrs); dprint(")\n"); @@ -1083,7 +1126,18 @@ krb5int_sendto (krb5_context context, const krb5_data *message, if (conns == NULL) { return ENOMEM; } + memset(conns, 0, n_conns * sizeof(conns[i])); + + if (callback_info) { + callback_data = malloc(n_conns * sizeof(krb5_data)); + if (callback_data == NULL) { + return ENOMEM; + } + + memset(conns, 0, n_conns * sizeof(callback_data[i])); + } + for (i = 0; i < n_conns; i++) { conns[i].fd = INVALID_SOCKET; } @@ -1102,15 +1156,13 @@ krb5int_sendto (krb5_context context, const krb5_data *message, FD_ZERO(&sel_state->wfds); FD_ZERO(&sel_state->xfds); - message_len_buf[0] = (message->length >> 24) & 0xff; - message_len_buf[1] = (message->length >> 16) & 0xff; - message_len_buf[2] = (message->length >> 8) & 0xff; - message_len_buf[3] = message->length & 0xff; /* Set up connections. */ for (host = 0; host < n_conns; host++) { - retval = setup_connection (&conns[host], addrs->addrs[host].ai, - message, message_len_buf, &udpbuf); + retval = setup_connection(&conns[host], + addrs->addrs[host].ai, + message, + &udpbuf); if (retval) continue; } @@ -1122,7 +1174,10 @@ krb5int_sendto (krb5_context context, const krb5_data *message, dprint("host %d\n", host); /* Send to the host, wait for a response, then move on. */ - if (maybe_send(&conns[host], sel_state)) + if (maybe_send(&conns[host], + sel_state, + callback_info, + (callback_info ? &callback_data[host] : NULL))) continue; retval = getcurtime(&now); @@ -1180,6 +1235,10 @@ krb5int_sendto (krb5_context context, const krb5_data *message, *addr_used = winning_conn; if (localaddr != 0 && localaddrlen != 0 && *localaddrlen > 0) (void) getsockname(conns[winning_conn].fd, localaddr, localaddrlen); + + if (remoteaddr != 0 && remoteaddrlen != 0 && *remoteaddrlen > 0) + (void) getpeername(conns[winning_conn].fd, remoteaddr, remoteaddrlen); + egress: for (i = 0; i < n_conns; i++) { if (conns[i].fd != INVALID_SOCKET) @@ -1188,7 +1247,14 @@ egress: && conns[i].x.in.buf != 0 && conns[i].x.in.buf != udpbuf) free(conns[i].x.in.buf); + if (callback_info) { + callback_info->pfn_cleanup( callback_info->context, &callback_data[i]); + } } + + if (callback_data) + free(callback_data); + free(conns); if (reply->data != udpbuf) free(udpbuf); -- 2.26.2