Merge Todd's TCP changepw support, with a few fixups
authorKen Raeburn <raeburn@mit.edu>
Wed, 23 Aug 2006 22:56:29 +0000 (22:56 +0000)
committerKen Raeburn <raeburn@mit.edu>
Wed, 23 Aug 2006 22:56:29 +0000 (22:56 +0000)
* 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
src/include/k5-int.h
src/lib/krb4/send_to_kdc.c
src/lib/krb5/krb/chpw.c
src/lib/krb5/os/changepw.c
src/lib/krb5/os/send524.c
src/lib/krb5/os/sendto_kdc.c

index 428e61e5875ccabdfe3c49b5b0279a45932fbca0..716e6cb593745e6326df71e3f2d5812a32efc201 100644 (file)
@@ -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 *);
index a8b9e5d99a9fb94b77b0f840d4b3b664be33d825..a55fea7a49eadc420bbd2d9c30642d79f717dd81 100644 (file)
@@ -487,15 +487,19 @@ extern char *strdup (const char *);
 #include <stdio.h>
 
 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,
index 3be677b6c3fb0bd8bdbd2ca625e6f47d3ac2ca8a..a33ad2b037d1f6c454f9733eec5245e2276349e3 100644 (file)
@@ -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);
index 640124601e18df54053857e30c9c90ab99c734a0..427ea39aaa8a5485714c25338a343e3d29200e32 100644 (file)
@@ -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 -
index 5f900b6216ae87fc11d23ee65853aa923d1125ec..60f2d1d64a78da7b44775083b4342b32f7980a66 100644 (file)
 /*
  * 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 <stdio.h>
 #include <errno.h>
 #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;i<addrlist->naddrs;i++ ) {
+           for (i=0; i<addrlist->naddrs; 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; i<al.naddrs; i++) {
-       fd_set fdset;
-       struct timeval timeout;
+    /* some brain-dead OS's don't return useful information from
+     * the getsockname call.  Namely, windows and solaris.  */
 
-       /* XXX Now the locate_ functions can return IPv6 addresses.  */
-       if (al.addrs[i].ai->ai_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;
 }
index 09c9c90228c5c0dbbd8127147b974398f02bbe48..f6e7a0b568669695d3cda1ead8fc0676b0374df6 100644 (file)
@@ -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
index 75cb03678523f310a99bc07fca40de02f0775634..b616578a0bc2a98827aa8b0f1904c66769d3cf7b 100644 (file)
@@ -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);