imap: Added support for SASL based authentication mechanism detection
authorSteve Holme <steve_holme@hotmail.com>
Sun, 6 Jan 2013 19:13:58 +0000 (19:13 +0000)
committerSteve Holme <steve_holme@hotmail.com>
Sun, 6 Jan 2013 19:13:58 +0000 (19:13 +0000)
Added support for detecting the supported SASL authentication mechanisms
via the CAPABILITY command.

lib/imap.c
lib/imap.h
tests/data/test1321
tests/data/test801

index ab11464..388af04 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -70,6 +70,7 @@
 #include "multiif.h"
 #include "url.h"
 #include "rawstr.h"
+#include "curl_sasl.h"
 
 #define _MPRINTF_REPLACE /* use our functions only */
 #include <curl/mprintf.h>
@@ -320,7 +321,8 @@ static char* imap_atom(const char* str)
 }
 
 /* Function that checks for an ending imap status code at the start of the
-   given string. */
+   given string but also detects the supported mechanisms from the CAPABILITY
+   response. */
 static int imap_endofresp(struct pingpong *pp, int *resp)
 {
   char *line = pp->linestart_resp;
@@ -328,6 +330,7 @@ static int imap_endofresp(struct pingpong *pp, int *resp)
   struct imap_conn *imapc = &pp->conn->proto.imapc;
   const char *id = imapc->idstr;
   size_t id_len = strlen(id);
+  size_t wordlen;
 
   /* Do we have a generic command response? */
   if(len >= id_len + 3) {
@@ -337,6 +340,64 @@ static int imap_endofresp(struct pingpong *pp, int *resp)
     }
   }
 
+  /* Are we processing CAPABILITY command responses? */
+  if(imapc->state == IMAP_CAPABILITY) {
+    /* Do we have a valid response? */
+    if(len >= 2 && !memcmp("* ", line, 2)) {
+      line += 2;
+      len -= 2;
+
+      /* Loop through the data line */
+      for(;;) {
+        while(len &&
+              (*line == ' ' || *line == '\t' ||
+               *line == '\r' || *line == '\n')) {
+
+          if(*line == '\n')
+            return FALSE;
+
+          line++;
+          len--;
+        }
+
+        if(!len)
+          break;
+
+        /* Extract the word */
+        for(wordlen = 0; wordlen < len && line[wordlen] != ' ' &&
+              line[wordlen] != '\t' && line[wordlen] != '\r' &&
+              line[wordlen] != '\n';)
+          wordlen++;
+
+        /* Do we have an AUTH capability? */
+        if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) {
+          line += 5;
+          len -= 5;
+          wordlen -= 5;
+
+          /* Test the word for a matching authentication mechanism */
+          if(wordlen == 5 && !memcmp(line, "LOGIN", 5))
+            imapc->authmechs |= SASL_MECH_LOGIN;
+          if(wordlen == 5 && !memcmp(line, "PLAIN", 5))
+            imapc->authmechs |= SASL_MECH_PLAIN;
+          else if(wordlen == 8 && !memcmp(line, "CRAM-MD5", 8))
+            imapc->authmechs |= SASL_MECH_CRAM_MD5;
+          else if(wordlen == 10 && !memcmp(line, "DIGEST-MD5", 10))
+            imapc->authmechs |= SASL_MECH_DIGEST_MD5;
+          else if(wordlen == 6 && !memcmp(line, "GSSAPI", 6))
+            imapc->authmechs |= SASL_MECH_GSSAPI;
+          else if(wordlen == 8 && !memcmp(line, "EXTERNAL", 8))
+            imapc->authmechs |= SASL_MECH_EXTERNAL;
+          else if(wordlen == 4 && !memcmp(line, "NTLM", 4))
+            imapc->authmechs |= SASL_MECH_NTLM;
+        }
+
+        line += wordlen;
+        len -= wordlen;
+      }
+    }
+  }
+
   /* Are we processing FETCH command responses? */
   if(imapc->state == IMAP_FETCH) {
     /* Do we have a valid response? */
@@ -346,7 +407,7 @@ static int imap_endofresp(struct pingpong *pp, int *resp)
     }
   }
 
-  return FALSE; /* nothing for us */
+  return FALSE; /* Nothing for us */
 }
 
 /* This is the ONLY way to change IMAP state! */
@@ -361,6 +422,7 @@ static void state(struct connectdata *conn,
     "SERVERGREET",
     "STARTTLS",
     "UPGRADETLS",
+    "CAPABILITY",
     "LOGIN",
     "SELECT",
     "FETCH",
@@ -376,6 +438,35 @@ static void state(struct connectdata *conn,
   imapc->state = newstate;
 }
 
+static CURLcode imap_state_capability(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct imap_conn *imapc = &conn->proto.imapc;
+  const char *str;
+
+  imapc->authmechs = 0;         /* No known authentication mechanisms yet */
+
+  /* Check we have a username and password to authenticate with and end the
+     connect phase if we don't */
+  if(!conn->bits.user_passwd) {
+    state(conn, IMAP_STOP);
+
+    return result;
+  }
+
+  str = getcmdid(conn);
+
+  /* Send the CAPABILITY command */
+  result = imap_sendf(conn, str, "%s CAPABILITY", str);
+
+  if(result)
+    return result;
+
+  state(conn, IMAP_CAPABILITY);
+
+  return CURLE_OK;
+}
+
 static CURLcode imap_state_login(struct connectdata *conn)
 {
   CURLcode result;
@@ -439,7 +530,7 @@ static CURLcode imap_state_servergreet_resp(struct connectdata *conn,
     state(conn, IMAP_STARTTLS);
   }
   else
-    result = imap_state_login(conn);
+    result = imap_state_capability(conn);
 
   return result;
 }
@@ -460,7 +551,7 @@ static CURLcode imap_state_starttls_resp(struct connectdata *conn,
       result = CURLE_USE_SSL_FAILED;
     }
     else
-      result = imap_state_login(conn);
+      result = imap_state_capability(conn);
   }
   else {
     if(data->state.used_interface == Curl_if_multi) {
@@ -471,7 +562,7 @@ static CURLcode imap_state_starttls_resp(struct connectdata *conn,
       result = Curl_ssl_connect(conn, FIRSTSOCKET);
       if(CURLE_OK == result) {
         imap_to_imaps(conn);
-        result = imap_state_login(conn);
+        result = imap_state_capability(conn);
       }
     }
   }
@@ -488,12 +579,23 @@ static CURLcode imap_state_upgrade_tls(struct connectdata *conn)
 
   if(imapc->ssldone) {
     imap_to_imaps(conn);
-    result = imap_state_login(conn);
+    result = imap_state_capability(conn);
   }
 
   return result;
 }
 
+/* For CAPABILITY responses */
+static CURLcode imap_state_capability_resp(struct connectdata *conn,
+                                           int imapcode,
+                                           imapstate instate)
+{
+  (void)imapcode; /* no use for this yet */
+  (void)instate; /* no use for this yet */
+
+  return imap_state_login(conn);
+}
+
 /* For LOGIN responses */
 static CURLcode imap_state_login_resp(struct connectdata *conn,
                                       int imapcode,
@@ -694,6 +796,10 @@ static CURLcode imap_statemach_act(struct connectdata *conn)
       result = imap_state_starttls_resp(conn, imapcode, imapc->state);
       break;
 
+    case IMAP_CAPABILITY:
+      result = imap_state_capability_resp(conn, imapcode, imapc->state);
+      break;
+
     case IMAP_LOGIN:
       result = imap_state_login_resp(conn, imapcode, imapc->state);
       break;
index 4cd6bc7..60cb046 100644 (file)
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2009 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2009 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -34,6 +34,7 @@ typedef enum {
   IMAP_STARTTLS,
   IMAP_UPGRADETLS,   /* asynchronously upgrade the connection to SSL/TLS
                        (multi mode only) */
+  IMAP_CAPABILITY,
   IMAP_LOGIN,
   IMAP_SELECT,
   IMAP_FETCH,
@@ -45,12 +46,13 @@ typedef enum {
    struct */
 struct imap_conn {
   struct pingpong pp;
-  char *mailbox;     /* Message ID to fetch */
-  imapstate state;   /* Always use imap.c:state() to change state! */
-  int cmdid;         /* Next command ID */
-  const char *idstr; /* String based response ID to wait for */
-  bool ssldone;      /* Is connect() over SSL done? Only relevant in
-                        multi mode */
+  char *mailbox;          /* Message ID to fetch */
+  unsigned int authmechs; /* Accepted authentication mechanisms */
+  imapstate state;        /* Always use imap.c:state() to change state! */
+  int cmdid;              /* Next command ID */
+  const char *idstr;      /* String based response ID to wait for */
+  bool ssldone;           /* Is connect() over SSL done? Only relevant in
+                             multi mode */
 };
 
 extern const struct Curl_handler Curl_handler_imap;
index 7d45d96..4a04575 100644 (file)
@@ -56,10 +56,11 @@ imap://%HOSTIP:%IMAPPORT/1321 -u user:secret -p -x %HOSTIP:%PROXYPORT
 ^User-Agent: curl/.*
 </strip>
 <protocol>
-B LOGIN user secret\r
-C SELECT 1321\r
-D FETCH 1 BODY[TEXT]\r
-A LOGOUT\r
+B CAPABILITY\r
+C LOGIN user secret\r
+D SELECT 1321\r
+A FETCH 1 BODY[TEXT]\r
+B LOGOUT\r
 </protocol>
 <proxy>
 CONNECT %HOSTIP:%IMAPPORT HTTP/1.1\r
index 271d8d1..d5f1cfe 100644 (file)
@@ -38,10 +38,11 @@ imap://%HOSTIP:%IMAPPORT/801 -u user:secret
 # Verify data after the test has been "shot"
 <verify>
 <protocol>
-B LOGIN user secret\r
-C SELECT 801\r
-D FETCH 1 BODY[TEXT]\r
-A LOGOUT\r
+B CAPABILITY\r
+C LOGIN user secret\r
+D SELECT 801\r
+A FETCH 1 BODY[TEXT]\r
+B LOGOUT\r
 </protocol>
 </verify>
 </testcase>