r16268: Add TCP fallback for our implementation of the CHANGEPW kpasswd calls.
authorGünther Deschner <gd@samba.org>
Thu, 15 Jun 2006 21:25:57 +0000 (21:25 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 16:17:29 +0000 (11:17 -0500)
This patch is mainly based on the work of Todd Stecher
<tstecher@isilon.com> and has been reviewed by Jeremy.

I sucessfully tested and valgrinded it with MIT 1.4.3, 1.3.5, Heimdal
0.7.2 and 0.6.1rc3.

Guenther
(This used to be commit 535d03cbe8b021e9aa6d74b62d81b867c494c957)

source3/configure.in
source3/libads/krb5_setpw.c
source3/libsmb/clikrb5.c

index 799441ed950e5ecfce116fe9c94899382874c69c..720e85e71a91d02d91549f7e7d4055bb11e81864 100644 (file)
@@ -3457,6 +3457,7 @@ if test x"$with_ads_support" != x"no"; then
   AC_CHECK_FUNC_EXT(krb5_get_init_creds_opt_set_pac_request, $KRB5_LIBS)
   AC_CHECK_FUNC_EXT(krb5_get_renewed_creds, $KRB5_LIBS)
   AC_CHECK_FUNC_EXT(krb5_get_kdc_cred, $KRB5_LIBS)
+  AC_CHECK_FUNC_EXT(krb5_free_error_contents, $KRB5_LIBS)
 
   LIBS="$KRB5_LIBS $LIBS"
 
@@ -3506,6 +3507,18 @@ if test x"$with_ads_support" != x"no"; then
                [Whether the krb5_ap_req struct has a ticket pointer])
   fi
 
+  AC_CACHE_CHECK([for e_data pointer in krb5_error],
+                samba_cv_HAVE_E_DATA_POINTER_IN_KRB5_ERROR,[
+    AC_TRY_COMPILE([#include <krb5.h>],
+      [krb5_error err; err.e_data = NULL;],
+      samba_cv_HAVE_E_DATA_POINTER_IN_KRB5_ERROR=yes,
+      samba_cv_HAVE_E_DATA_POINTER_IN_KRB5_ERROR=no)])
+
+  if test x"$samba_cv_HAVE_E_DATA_POINTER_IN_KRB5_ERROR" = x"yes"; then
+    AC_DEFINE(HAVE_E_DATA_POINTER_IN_KRB5_ERROR,1,
+               [Whether the krb5_error struct has a e_data pointer])
+  fi
+
   AC_CACHE_CHECK([for krb5_crypto type],
                 samba_cv_HAVE_KRB5_CRYPTO,[
     AC_TRY_COMPILE([#include <krb5.h>],
index ec2ff5afb1a444f10f7f513273fa014654cf8e4f..07e6320c266e5d0c555850d1550f6dc3559c165c 100644 (file)
 #define KRB5_KPASSWD_BAD_PRINCIPAL             9
 #define KRB5_KPASSWD_ETYPE_NOSUPP              10
 
+/*
+ * we've got to be able to distinguish KRB_ERRORs from other
+ * requests - valid response for CHPW v2 replies.
+ */
+
+#define krb5_is_krb_error(packet) \
+       ( packet && packet->length && (((char *)packet->data)[0] == 0x7e || ((char *)packet->data)[0] == 0x5e))
+
 /* This implements kerberos password change protocol as specified in 
  * kerb-chg-password-02.txt and kerberos-set-passwd-02.txt
  * as well as microsoft version of the protocol 
@@ -129,14 +137,16 @@ static krb5_error_code build_kpasswd_request(uint16 pversion,
                                           krb5_data *ap_req,
                                           const char *princ,
                                           const char *passwd,
+                                          BOOL use_tcp,
                                           krb5_data *packet)
 {
        krb5_error_code ret;
        krb5_data cipherpw;
        krb5_data encoded_setpw;
        krb5_replay_data replay;
-       char *p;
+       char *p, *msg_start;
        DATA_BLOB setpw;
+       unsigned int msg_length;
 
        ret = krb5_auth_con_setflags(context,
                                     auth_context,KRB5_AUTH_CONTEXT_DO_SEQUENCE);
@@ -172,12 +182,16 @@ static krb5_error_code build_kpasswd_request(uint16 pversion,
                return ret;
        }
 
-       packet->data = (char *)SMB_MALLOC(ap_req->length + cipherpw.length + 6);
+       packet->data = (char *)SMB_MALLOC(ap_req->length + cipherpw.length + (use_tcp ? 10 : 6 ));
        if (!packet->data)
                return -1;
 
+
+
        /* see the RFC for details */
-       p = ((char *)packet->data) + 2;
+
+       msg_start = p = ((char *)packet->data) + (use_tcp ? 4 : 0);
+       p += 2;
        RSSVAL(p, 0, pversion);
        p += 2;
        RSSVAL(p, 0, ap_req->length);
@@ -187,7 +201,12 @@ static krb5_error_code build_kpasswd_request(uint16 pversion,
        memcpy(p, cipherpw.data, cipherpw.length);
        p += cipherpw.length;
        packet->length = PTR_DIFF(p,packet->data);
-       RSSVAL(packet->data, 0, packet->length);
+       msg_length = PTR_DIFF(p,msg_start);
+
+       if (use_tcp) {
+               RSIVAL(packet->data, 0, msg_length);
+       }
+       RSSVAL(msg_start, 0, msg_length);
        
        free(cipherpw.data);    /* from 'krb5_mk_priv(...)' */
 
@@ -248,7 +267,8 @@ static krb5_error_code setpw_result_code_string(krb5_context context,
                        return KRB5KRB_ERR_GENERIC;
        }
 }
-static krb5_error_code parse_setpw_reply(krb5_context context, 
+static krb5_error_code parse_setpw_reply(krb5_context context,
+                                        BOOL use_tcp,
                                         krb5_auth_context auth_context,
                                         krb5_data *packet)
 {
@@ -259,23 +279,41 @@ static krb5_error_code parse_setpw_reply(krb5_context context,
        krb5_data clearresult;
        krb5_ap_rep_enc_part *ap_rep_enc;
        krb5_replay_data replay;
-       
-       if (packet->length < 4) {
+       unsigned int msg_length = packet->length;
+
+
+       if (packet->length < (use_tcp ? 8 : 4)) {
                return KRB5KRB_AP_ERR_MODIFIED;
        }
        
        p = packet->data;
+       /*
+       ** see if it is an error
+       */
+       if (krb5_is_krb_error(packet)) {
+
+               ret = handle_krberror_packet(context, packet);
+               if (ret) {
+                       return ret;
+               }
+       }
+
        
-       if (((char *)packet->data)[0] == 0x7e || ((char *)packet->data)[0] == 0x5e) {
-               /* it's an error packet. We should parse it ... */
-               DEBUG(1,("Got error packet 0x%x from kpasswd server\n",
-                        ((char *)packet->data)[0]));
-               return KRB5KRB_AP_ERR_MODIFIED;
+       /* tcp... */
+       if (use_tcp) {
+               msg_length -= 4;
+               if (RIVAL(p, 0) != msg_length) {
+                       DEBUG(1,("Bad TCP packet length (%d/%d) from kpasswd server\n",
+                       RIVAL(p, 0), msg_length));
+                       return KRB5KRB_AP_ERR_MODIFIED;
+               }
+
+               p += 4;
        }
        
-       if (RSVAL(p, 0) != packet->length) {
+       if (RSVAL(p, 0) != msg_length) {
                DEBUG(1,("Bad packet length (%d/%d) from kpasswd server\n",
-                        RSVAL(p, 0), packet->length));
+                        RSVAL(p, 0), msg_length));
                return KRB5KRB_AP_ERR_MODIFIED;
        }
 
@@ -342,9 +380,9 @@ static krb5_error_code parse_setpw_reply(krb5_context context,
                return KRB5KRB_AP_ERR_MODIFIED;
        }
 
-       if(res_code == KRB5_KPASSWD_SUCCESS)
-                       return 0;
-       else {
+       if (res_code == KRB5_KPASSWD_SUCCESS) {
+               return 0;
+       else {
                const char *errstr;
                setpw_result_code_string(context, res_code, &errstr);
                DEBUG(1, ("Error changing password: %s (%d)\n", errstr, res_code));
@@ -365,7 +403,10 @@ static ADS_STATUS do_krb5_kpasswd_request(krb5_context context,
        int ret, sock;
        socklen_t addr_len;
        struct sockaddr remote_addr, local_addr;
+       struct in_addr *addr = interpret_addr2(kdc_host);
        krb5_address local_kaddr, remote_kaddr;
+       BOOL    use_tcp = False;
+
 
        ret = krb5_mk_req_extended(context, &auth_context, AP_OPTS_USE_SUBKEY,
                                   NULL, credsp, &ap_req);
@@ -373,102 +414,123 @@ static ADS_STATUS do_krb5_kpasswd_request(krb5_context context,
                DEBUG(1,("krb5_mk_req_extended failed (%s)\n", error_message(ret)));
                return ADS_ERROR_KRB5(ret);
        }
-       
-       sock = open_udp_socket(kdc_host, DEFAULT_KPASSWD_PORT);
-       if (sock == -1) {
-               int rc = errno;
-               free(ap_req.data);
-               krb5_auth_con_free(context, auth_context);
-               DEBUG(1,("failed to open kpasswd socket to %s (%s)\n", 
-                        kdc_host, strerror(errno)));
-               return ADS_ERROR_SYSTEM(rc);
-       }
-       
-       addr_len = sizeof(remote_addr);
-       getpeername(sock, &remote_addr, &addr_len);
-       addr_len = sizeof(local_addr);
-       getsockname(sock, &local_addr, &addr_len);
-       
-       setup_kaddr(&remote_kaddr, &remote_addr);
-       setup_kaddr(&local_kaddr, &local_addr);
-
-       ret = krb5_auth_con_setaddrs(context, auth_context, &local_kaddr, NULL);
-       if (ret) {
-               close(sock);
-               free(ap_req.data);
-               krb5_auth_con_free(context, auth_context);
-               DEBUG(1,("krb5_auth_con_setaddrs failed (%s)\n", error_message(ret)));
-               return ADS_ERROR_KRB5(ret);
-       }
-
-       ret = build_kpasswd_request(pversion, context, auth_context, &ap_req,
-                                 princ, newpw, &chpw_req);
-       if (ret) {
-               close(sock);
-               free(ap_req.data);
-               krb5_auth_con_free(context, auth_context);
-               DEBUG(1,("build_setpw_request failed (%s)\n", error_message(ret)));
-               return ADS_ERROR_KRB5(ret);
-       }
 
-       if (write(sock, chpw_req.data, chpw_req.length) != chpw_req.length) {
-               close(sock);
-               free(chpw_req.data);
-               free(ap_req.data);
-               krb5_auth_con_free(context, auth_context);
-               DEBUG(1,("send of chpw failed (%s)\n", strerror(errno)));
-               return ADS_ERROR_SYSTEM(errno);
-       }
+       do {
 
-       free(chpw_req.data);
+               if (!use_tcp) {
 
-       chpw_rep.length = 1500;
-       chpw_rep.data = (char *) SMB_MALLOC(chpw_rep.length);
-       if (!chpw_rep.data) {
-               close(sock);
-               free(ap_req.data);
-               krb5_auth_con_free(context, auth_context);
-               DEBUG(1,("send of chpw failed (%s)\n", strerror(errno)));
-               errno = ENOMEM;
-               return ADS_ERROR_SYSTEM(errno);
-       }
+                       sock = open_udp_socket(kdc_host, DEFAULT_KPASSWD_PORT);
 
-       ret = read(sock, chpw_rep.data, chpw_rep.length);
-       if (ret < 0) {
-               close(sock);
-               free(chpw_rep.data);
-               free(ap_req.data);
-               krb5_auth_con_free(context, auth_context);
-               DEBUG(1,("recv of chpw reply failed (%s)\n", strerror(errno)));
-               return ADS_ERROR_SYSTEM(errno);
-       }
+               } else {
 
-       close(sock);
-       chpw_rep.length = ret;
+                       sock = open_socket_out(SOCK_STREAM, addr, DEFAULT_KPASSWD_PORT, 
+                                              LONG_CONNECT_TIMEOUT);
+               }
 
-       ret = krb5_auth_con_setaddrs(context, auth_context, NULL,&remote_kaddr);
-       if (ret) {
-               free(chpw_rep.data);
-               free(ap_req.data);
-               krb5_auth_con_free(context, auth_context);
-               DEBUG(1,("krb5_auth_con_setaddrs on reply failed (%s)\n", 
-                        error_message(ret)));
-               return ADS_ERROR_KRB5(ret);
-       }
+               if (sock == -1) {
+                       int rc = errno;
+                       SAFE_FREE(ap_req.data);
+                       krb5_auth_con_free(context, auth_context);
+                       DEBUG(1,("failed to open kpasswd socket to %s (%s)\n", 
+                                kdc_host, strerror(errno)));
+                       return ADS_ERROR_SYSTEM(rc);
+               }
+               
+               addr_len = sizeof(remote_addr);
+               getpeername(sock, &remote_addr, &addr_len);
+               addr_len = sizeof(local_addr);
+               getsockname(sock, &local_addr, &addr_len);
+               
+               setup_kaddr(&remote_kaddr, &remote_addr);
+               setup_kaddr(&local_kaddr, &local_addr);
+       
+               ret = krb5_auth_con_setaddrs(context, auth_context, &local_kaddr, NULL);
+               if (ret) {
+                       close(sock);
+                       SAFE_FREE(ap_req.data);
+                       krb5_auth_con_free(context, auth_context);
+                       DEBUG(1,("krb5_auth_con_setaddrs failed (%s)\n", error_message(ret)));
+                       return ADS_ERROR_KRB5(ret);
+               }
+       
+               ret = build_kpasswd_request(pversion, context, auth_context, &ap_req,
+                                         princ, newpw, use_tcp, &chpw_req);
+               if (ret) {
+                       close(sock);
+                       SAFE_FREE(ap_req.data);
+                       krb5_auth_con_free(context, auth_context);
+                       DEBUG(1,("build_setpw_request failed (%s)\n", error_message(ret)));
+                       return ADS_ERROR_KRB5(ret);
+               }
 
-       ret = parse_setpw_reply(context, auth_context, &chpw_rep);
-       free(chpw_rep.data);
+               ret = write(sock, chpw_req.data, chpw_req.length); 
 
-       if (ret) {
-               free(ap_req.data);
+               if (ret != chpw_req.length) {
+                       close(sock);
+                       SAFE_FREE(chpw_req.data);
+                       SAFE_FREE(ap_req.data);
+                       krb5_auth_con_free(context, auth_context);
+                       DEBUG(1,("send of chpw failed (%s)\n", strerror(errno)));
+                       return ADS_ERROR_SYSTEM(errno);
+               }
+       
+               SAFE_FREE(chpw_req.data);
+       
+               chpw_rep.length = 1500;
+               chpw_rep.data = (char *) SMB_MALLOC(chpw_rep.length);
+               if (!chpw_rep.data) {
+                       close(sock);
+                       SAFE_FREE(ap_req.data);
+                       krb5_auth_con_free(context, auth_context);
+                       DEBUG(1,("send of chpw failed (%s)\n", strerror(errno)));
+                       errno = ENOMEM;
+                       return ADS_ERROR_SYSTEM(errno);
+               }
+       
+               ret = read(sock, chpw_rep.data, chpw_rep.length);
+               if (ret < 0) {
+                       close(sock);
+                       SAFE_FREE(chpw_rep.data);
+                       SAFE_FREE(ap_req.data);
+                       krb5_auth_con_free(context, auth_context);
+                       DEBUG(1,("recv of chpw reply failed (%s)\n", strerror(errno)));
+                       return ADS_ERROR_SYSTEM(errno);
+               }
+       
+               close(sock);
+               chpw_rep.length = ret;
+       
+               ret = krb5_auth_con_setaddrs(context, auth_context, NULL,&remote_kaddr);
+               if (ret) {
+                       SAFE_FREE(chpw_rep.data);
+                       SAFE_FREE(ap_req.data);
+                       krb5_auth_con_free(context, auth_context);
+                       DEBUG(1,("krb5_auth_con_setaddrs on reply failed (%s)\n", 
+                                error_message(ret)));
+                       return ADS_ERROR_KRB5(ret);
+               }
+       
+               ret = parse_setpw_reply(context, use_tcp, auth_context, &chpw_rep);
+               SAFE_FREE(chpw_rep.data);
+       
+               if (ret) {
+                       
+                       if (ret == KRB5KRB_ERR_RESPONSE_TOO_BIG && !use_tcp) {
+                               DEBUG(5, ("Trying setpw with TCP!!!\n"));
+                               use_tcp = True;
+                               continue;
+                       }
+
+                       SAFE_FREE(ap_req.data);
+                       krb5_auth_con_free(context, auth_context);
+                       DEBUG(1,("parse_setpw_reply failed (%s)\n", 
+                                error_message(ret)));
+                       return ADS_ERROR_KRB5(ret);
+               }
+       
+               SAFE_FREE(ap_req.data);
                krb5_auth_con_free(context, auth_context);
-               DEBUG(1,("parse_setpw_reply failed (%s)\n", 
-                        error_message(ret)));
-               return ADS_ERROR_KRB5(ret);
-       }
-
-       free(ap_req.data);
-       krb5_auth_con_free(context, auth_context);
+       } while ( ret );
 
        return ADS_SUCCESS;
 }
index e0d6b09d977db1b4d1db43808ba932e8f3c4a649..f1815b3e8f2817faa391c5f7d0c18bedf3005632 100644 (file)
@@ -1310,7 +1310,64 @@ done:
 
        return ret;
 }
-                               
+
+void smb_krb5_free_error(krb5_context context, krb5_error *krberror)
+{
+#ifdef HAVE_KRB5_FREE_ERROR_CONTENTS /* Heimdal */
+       krb5_free_error_contents(context, krberror);
+#else /* MIT */
+       krb5_free_error(context, krberror);
+#endif
+}
+
+krb5_error_code handle_krberror_packet(krb5_context context,
+                                      krb5_data *packet)
+{
+       krb5_error_code ret;
+       BOOL got_error_code = False;
+
+       DEBUG(10,("handle_krberror_packet: got error packet\n"));
+       
+#ifdef HAVE_E_DATA_POINTER_IN_KRB5_ERROR /* Heimdal */
+       {
+               krb5_error krberror;
+
+               if ((ret = krb5_rd_error(context, packet, &krberror))) {
+                       DEBUG(10,("handle_krberror_packet: krb5_rd_error failed with: %s\n", 
+                               error_message(ret)));
+                       return ret;
+               }
+
+               if (krberror.e_data == NULL || krberror.e_data->data == NULL) {
+                       ret = (krb5_error_code) krberror.error_code;
+                       got_error_code = True;
+               }
+
+               smb_krb5_free_error(context, &krberror);
+       }
+#else /* MIT */
+       {
+               krb5_error *krberror;
+
+               if ((ret = krb5_rd_error(context, packet, &krberror))) {
+                       DEBUG(10,("handle_krberror_packet: krb5_rd_error failed with: %s\n", 
+                               error_message(ret)));
+                       return ret;
+               }
+
+               if (krberror->e_data.data == NULL) {
+                       ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code) krberror->error;
+                       got_error_code = True;
+               }
+               smb_krb5_free_error(context, krberror);
+       }
+#endif
+       if (got_error_code) {
+               DEBUG(5,("handle_krberror_packet: got KERBERR from kpasswd: %s (%d)\n", 
+                       error_message(ret), ret));
+       }
+       return ret;
+}
 
 #else /* HAVE_KRB5 */
  /* this saves a few linking headaches */