Fix CB support when client selects mechanism explicitly
[cyrus-sasl.git] / lib / client.c
index c42d6f5..58aeb0d 100644 (file)
@@ -390,17 +390,112 @@ static int have_prompts(sasl_conn_t *conn,
   return 1; /* we have all the prompts */
 }
 
-static inline int sasl_is_plus_mech(const char *mech)
+static int
+_mech_plus_p(const char *mech, size_t len)
+{
+    return (len > 5 && strncasecmp(&mech[len - 5], "-PLUS", 5) == 0);
+}
+
+/*
+ * Order PLUS mechanisms first. Returns NUL separated list of
+ * *count items.
+ */
+static int
+_sasl_client_order_mechs(const sasl_utils_t *utils,
+                        const char *mechs,
+                        int has_cb_data,
+                        char **ordered_mechs,
+                        size_t *count,
+                        int *server_can_cb)
 {
-    size_t len = strlen(mech);
-    const char *p;
+    char *list, *listp;
+    size_t i, mechslen, start;
+
+    *count = 0;
+    *server_can_cb = 0;
+
+    if (mechs == NULL || mechs[0] == '\0')
+        return SASL_NOMECH;
+
+    mechslen = strlen(mechs);
+
+    listp = list = utils->malloc(mechslen + 1);
+    if (list == NULL)
+       return SASL_NOMEM;
+
+    /* xxx confirm this with rfc 2222
+     * SASL mechanism allowable characters are "AZaz-_"
+     * seperators can be any other characters and of any length
+     * even variable lengths between
+     *
+     * Apps should be encouraged to simply use space or comma space
+     * though
+     */
+#define ismechchar(c)   (isalnum((c)) || (c) == '_' || (c) == '-')
+    do {
+        for (i = start = 0; i <= mechslen; i++) {
+           if (!ismechchar(mechs[i])) {
+                const char *mechp = &mechs[start];
+               size_t len = i - start;
+
+               if (len != 0 &&
+                    _mech_plus_p(mechp, len) == has_cb_data) {
+                   memcpy(listp, mechp, len);
+                   listp[len] = '\0';
+                   listp += len + 1;
+                   (*count)++;
+                   if (*server_can_cb == 0 && has_cb_data)
+                       *server_can_cb = 1;
+               }
+               start = ++i;
+           }
+       }
+       if (has_cb_data)
+           has_cb_data = 0;
+       else
+           break;
+    } while (1);
+
+    if (*count == 0) {
+        utils->free(list);
+        return SASL_NOMECH;
+    }
 
-    if (len < 5)
-        return 0;
+    *ordered_mechs = list;
 
-    p = &mech[len - 5];
+    return SASL_OK;
+}
+
+static inline int
+_sasl_cbinding_disp(sasl_client_params_t *cparams,
+                    int mech_nego,
+                    int server_can_cb,
+                    unsigned int *cbindingdisp)
+{
+    /*
+     * If negotiating mechanisms, then we fail immediately if the
+     * client requires channel binding and the server does not
+     * advertise support. Otherwise we send "y" (which later will
+     * become "p" if we select a supporting mechanism).
+     *
+     * If the client explicitly selected a mechanism, then we only
+     * send channel bindings if they're marked critical.
+     */
+
+    *cbindingdisp = SASL_CB_DISP_NONE;
+
+    if (SASL_CB_PRESENT(cparams)) {
+        if (mech_nego) {
+            if (!server_can_cb && SASL_CB_CRITICAL(cparams))
+               return SASL_NOMECH;
+            else
+                *cbindingdisp = SASL_CB_DISP_WANT;
+        } else if (SASL_CB_CRITICAL(cparams)) {
+            *cbindingdisp = SASL_CB_DISP_USED;
+        }
+    }
 
-    return (strcmp(p, "-PLUS") == 0);
+    return SASL_OK;
 }
 
 /* select a mechanism for a connection
@@ -418,14 +513,6 @@ static inline int sasl_is_plus_mech(const char *mech)
  *  SASL_INTERACT -- user interaction needed to fill in prompt_need list
  */
 
-/* xxx confirm this with rfc 2222
- * SASL mechanism allowable characters are "AZaz-_"
- * seperators can be any other characters and of any length
- * even variable lengths between
- *
- * Apps should be encouraged to simply use space or comma space
- * though
- */
 int sasl_client_start(sasl_conn_t *conn,
                      const char *mechlist,
                      sasl_interact_t **prompt_need,
@@ -434,12 +521,12 @@ int sasl_client_start(sasl_conn_t *conn,
                      const char **mech)
 {
     sasl_client_conn_t *c_conn= (sasl_client_conn_t *) conn;
-    char name[SASL_MECHNAMEMAX + 1];
+    char *ordered_mechs = NULL, *name;
     cmechanism_t *m=NULL,*bestm=NULL;
-    size_t pos=0,place;
-    size_t list_len;
+    size_t i, list_len;
     sasl_ssf_t bestssf = 0, minssf = 0;
-    int result;
+    int result, server_can_cb = 0;
+    unsigned int cbindingdisp;
 
     if(_sasl_client_active==0) return SASL_NOTINIT;
 
@@ -464,38 +551,33 @@ int sasl_client_start(sasl_conn_t *conn,
        minssf = conn->props.min_ssf - conn->external.ssf;
     }
 
-    /* parse mechlist */
-    list_len = strlen(mechlist);
-
-    while (pos<list_len)
-    {
-       place=0;
-       while ((pos<list_len) && (isalnum((unsigned char)mechlist[pos])
-                                 || mechlist[pos] == '_'
-                                 || mechlist[pos] == '-')) {
-           name[place]=mechlist[pos];
-           pos++;
-           place++;
-           if (SASL_MECHNAMEMAX < place) {
-               place--;
-               while(pos<list_len && (isalnum((unsigned char)mechlist[pos])
-                                      || mechlist[pos] == '_'
-                                      || mechlist[pos] == '-'))
-                   pos++;
-           }
-       }
-       pos++;
-       name[place]=0;
+    /* Order mechanisms so -PLUS are preferred */
+    result = _sasl_client_order_mechs(c_conn->cparams->utils,
+                                     mechlist,
+                                     SASL_CB_PRESENT(c_conn->cparams),
+                                     &ordered_mechs,
+                                     &list_len,
+                                     &server_can_cb);
+    if (result != 0)
+       goto done;
 
-       if (! place) continue;
+    /*
+     * Determine channel binding disposition based on whether we
+     * are doing mechanism negotiation and whether server supports
+     * channel bindings.
+     */
+    result = _sasl_cbinding_disp(c_conn->cparams, (list_len > 1),
+                                 server_can_cb, &cbindingdisp);
+    if (result != 0)
+       goto done;
 
+    for (i = 0, name = ordered_mechs; i < list_len; i++) {
        /* foreach in client list */
        for (m = cmechlist->mech_list; m != NULL; m = m->next) {
-           int myflags;
-           
-           /* Is this the mechanism the server is suggesting? */
-           if (strcasecmp(m->m.plug->mech_name, name))
-               continue; /* no */
+           int myflags, plus;
+
+           if (!_sasl_is_equal_mech(name, m->m.plug->mech_name, &plus))
+               continue;
 
            /* Do we have the prompts for it? */
            if (!have_prompts(conn, m->m.plug))
@@ -519,6 +601,11 @@ int sasl_client_start(sasl_conn_t *conn,
            }
 
            /* Can we meet it's features? */
+          if (SASL_CB_PRESENT(c_conn->cparams) &&
+               !(m->m.plug->features & SASL_FEAT_CHANNEL_BINDING)) {
+               break;
+           }
+
            if ((m->m.plug->features & SASL_FEAT_NEEDSERVERFQDN)
                && !conn->serverFQDN) {
                break;
@@ -530,16 +617,6 @@ int sasl_client_start(sasl_conn_t *conn,
                break;
            }
 
-           /* If client requires channel binding, prefer -PLUS mech */
-           if (c_conn->cparams->chanbindingslen != 0) {
-               if (sasl_is_plus_mech(name))
-                   c_conn->cparams->chanbindingsflag = SASL_CB_FLAG_USED;
-               else
-                   c_conn->cparams->chanbindingsflag = SASL_CB_FLAG_WANT;
-           } else {
-               c_conn->cparams->chanbindingsflag = SASL_CB_FLAG_NONE;
-           }
-
 #ifdef PREFER_MECH
            if (strcasecmp(m->m.plug->mech_name, PREFER_MECH) &&
                bestm && m->m.plug->max_ssf <= bestssf) {
@@ -578,6 +655,10 @@ int sasl_client_start(sasl_conn_t *conn,
                break;
            }
 
+           if (SASL_CB_PRESENT(c_conn->cparams) && plus) {
+               cbindingdisp = SASL_CB_DISP_USED;
+           }
+
            if (mech) {
                *mech = m->m.plug->mech_name;
            }
@@ -585,6 +666,7 @@ int sasl_client_start(sasl_conn_t *conn,
            bestm = m;
            break;
        }
+       name += strlen(name) + 1;
     }
 
     if (bestm == NULL) {
@@ -607,6 +689,7 @@ int sasl_client_start(sasl_conn_t *conn,
 
     c_conn->cparams->external_ssf = conn->external.ssf;
     c_conn->cparams->props = conn->props;
+    c_conn->cparams->cbindingdisp = cbindingdisp;
     c_conn->mech = bestm;
 
     /* init that plugin */
@@ -631,6 +714,8 @@ int sasl_client_start(sasl_conn_t *conn,
        result = SASL_CONTINUE;
 
  done:
+    if (ordered_mechs != NULL)
+       c_conn->cparams->utils->free(ordered_mechs);
     RETURN(conn, result);
 }
 
@@ -967,6 +1052,16 @@ _sasl_print_mechanism (
            printf ("%cNEED_SERVER_FQDN", delimiter);
            delimiter = '|';
        }
+
+       if (m->plug->features & SASL_FEAT_GSS_FRAMING) {
+           printf ("%cGSS_FRAMING", delimiter);
+           delimiter = '|';
+       }
+
+       if (m->plug->features & SASL_FEAT_CHANNEL_BINDING) {
+           printf ("%cCHANNEL_BINDING", delimiter);
+           delimiter = '|';
+       }
     }
 
 /* Delay loading is not supported for the client side plugins: