Merge branch 'master' of github.com:bagder/curl
authormonnerat <pm@datasphere.ch>
Mon, 19 Apr 2010 09:17:46 +0000 (11:17 +0200)
committermonnerat <pm@datasphere.ch>
Mon, 19 Apr 2010 09:17:46 +0000 (11:17 +0200)
15 files changed:
docs/RESOURCES
lib/Makefile.inc
lib/curl_hmac.h [new file with mode: 0644]
lib/curl_md5.h
lib/hmac.c [new file with mode: 0644]
lib/md5.c
lib/smtp.c
lib/smtp.h
packages/OS400/os400sys.c
tests/data/Makefile.am
tests/data/test805 [new file with mode: 0644]
tests/data/test806 [new file with mode: 0644]
tests/data/test807 [new file with mode: 0644]
tests/ftpserver.pl
tests/runtests.pl

index 4d6465d..760e759 100644 (file)
@@ -32,12 +32,16 @@ This document lists documents and standards used by curl.
 
   RFC 2068 - HTTP 1.1 (obsoleted by RFC 2616)
 
+  RFC 2104 - Keyed-Hashing for Message Authentication
+
   RFC 2109 - HTTP State Management Mechanism (cookie stuff)
            - Also, read Netscape's specification at
              http://curl.haxx.se/rfc/cookie_spec.html
 
   RFC 2183 - The Content-Disposition Header Field
 
+  RFC 2195 - CRAM-MD5 authentication
+
   RFC 2229 - A Dictionary Server Protocol
 
   RFC 2255 - Newer LDAP URL syntax document.
@@ -73,3 +77,7 @@ This document lists documents and standards used by curl.
   RFC 2965 - HTTP State Management Mechanism. Cookies. Obsoletes RFC2109
 
   RFC 3207 - SMTP over TLS
+
+  RFC 4616 - PLAIN authentication
+
+  RFC 4954 - SMTP Authentication
index 9803c8c..f90e4dc 100644 (file)
@@ -12,7 +12,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c   \
   strdup.c socks.c ssh.c nss.c qssl.c rawstr.c curl_addrinfo.c          \
   socks_gssapi.c socks_sspi.c curl_sspi.c slist.c nonblock.c           \
   curl_memrchr.c imap.c pop3.c smtp.c pingpong.c rtsp.c curl_threads.c \
-  warnless.c
+  warnless.c hmac.c
 
 HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h      \
   progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h     \
@@ -25,4 +25,4 @@ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h     \
   tftp.h sockaddr.h splay.h strdup.h setup_once.h socks.h ssh.h nssg.h \
   curl_base64.h rawstr.h curl_addrinfo.h curl_sspi.h slist.h nonblock.h        \
   curl_memrchr.h imap.h pop3.h smtp.h pingpong.h rtsp.h curl_threads.h \
-  warnless.h
+  warnless.h curl_hmac.h
diff --git a/lib/curl_hmac.h b/lib/curl_hmac.h
new file mode 100644 (file)
index 0000000..4c5a5a6
--- /dev/null
@@ -0,0 +1,67 @@
+#ifndef HEADER_CURL_HMAC_H
+#define HEADER_CURL_HMAC_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2010, 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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef CURL_DISABLE_CRYPTO_AUTH
+
+typedef void    (* HMAC_hinit_func)(void * context);
+typedef void    (* HMAC_hupdate_func)(void * context,
+                                      const unsigned char * data,
+                                      unsigned int len);
+typedef void    (* HMAC_hfinal_func)(unsigned char * result, void * context);
+
+
+/* Per-hash function HMAC parameters. */
+
+typedef struct {
+  HMAC_hinit_func       hmac_hinit;     /* Initialize context procedure. */
+  HMAC_hupdate_func     hmac_hupdate;   /* Update context with data. */
+  HMAC_hfinal_func      hmac_hfinal;    /* Get final result procedure. */
+  unsigned int          hmac_ctxtsize;  /* Context structure size. */
+  unsigned int          hmac_maxkeylen; /* Maximum key length (bytes). */
+  unsigned int          hmac_resultlen; /* Result length (bytes). */
+} HMAC_params;
+
+
+/* HMAC computation context. */
+
+typedef struct {
+  const HMAC_params *   hmac_hash;      /* Hash function definition. */
+  void *                hmac_hashctxt1; /* Hash function context 1. */
+  void *                hmac_hashctxt2; /* Hash function context 2. */
+} HMAC_context;
+
+
+/* Prototypes. */
+
+HMAC_context * Curl_HMAC_init(const HMAC_params * hashparams,
+                              const unsigned char * key,
+                              unsigned int keylen);
+int Curl_HMAC_update(HMAC_context * context,
+                     const unsigned char * data,
+                     unsigned int len);
+int Curl_HMAC_final(HMAC_context * context, unsigned char * result);
+
+#endif
+
+#endif
index cab9915..cd520bc 100644 (file)
  *
  ***************************************************************************/
 
+#ifndef CURL_DISABLE_CRYPTO_AUTH
+#include "curl_hmac.h"
+
+const HMAC_params Curl_HMAC_MD5[1];
+
 void Curl_md5it(unsigned char *output,
                 const unsigned char *input);
+#endif
 
 #endif /* HEADER_CURL_MD5_H */
diff --git a/lib/hmac.c b/lib/hmac.c
new file mode 100644 (file)
index 0000000..1d3ea83
--- /dev/null
@@ -0,0 +1,123 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2010, 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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * RFC2104 Keyed-Hashing for Message Authentication
+ *
+ ***************************************************************************/
+
+#ifndef CURL_DISABLE_CRYPTO_AUTH
+
+#include "setup.h"
+#include "curl_hmac.h"
+
+/*
+ * Generic HMAC algorithm.
+ *
+ *   This module computes HMAC digests based on any hash function. Parameters
+ * and computing procedures are set-up dynamically at HMAC computation
+ * context initialisation.
+ */
+
+static const unsigned char hmac_ipad = 0x36;
+static const unsigned char hmac_opad = 0x5C;
+
+
+
+HMAC_context *
+Curl_HMAC_init(const HMAC_params * hashparams,
+               const unsigned char * key,
+               unsigned int keylen)
+{
+  unsigned int i;
+  HMAC_context * ctxt;
+  unsigned char * hkey;
+  unsigned char b;
+
+  /* Create HMAC context. */
+  i = sizeof *ctxt + 2 * hashparams->hmac_ctxtsize + hashparams->hmac_resultlen;
+  ctxt = (HMAC_context *) malloc(i);
+
+  if(!ctxt)
+    return ctxt;
+
+  ctxt->hmac_hash = hashparams;
+  ctxt->hmac_hashctxt1 = (void *) (ctxt + 1);
+  ctxt->hmac_hashctxt2 = (void *) ((char *) ctxt->hmac_hashctxt1 +
+      hashparams->hmac_ctxtsize);
+
+  /* If the key is too long, replace it by its hash digest. */
+  if(keylen > hashparams->hmac_maxkeylen) {
+    (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1);
+    (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, key, keylen);
+    hkey = (unsigned char *) ctxt->hmac_hashctxt2 + hashparams->hmac_ctxtsize;
+    (*hashparams->hmac_hfinal)(hkey, ctxt->hmac_hashctxt1);
+    key = hkey;
+    keylen = hashparams->hmac_resultlen;
+  }
+
+  /* Prime the two hash contexts with the modified key. */
+  (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1);
+  (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt2);
+
+  for (i = 0; i < keylen; i++) {
+    b = *key ^ hmac_ipad;
+    (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &b, 1);
+    b = *key++ ^ hmac_opad;
+    (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &b, 1);
+  }
+
+  for (; i < hashparams->hmac_maxkeylen; i++) {
+    (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &hmac_ipad, 1);
+    (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &hmac_opad, 1);
+  }
+
+  /* Done, return pointer to HMAC context. */
+  return ctxt;
+}
+
+int Curl_HMAC_update(HMAC_context * ctxt,
+                     const unsigned char * data,
+                     unsigned int len)
+{
+  /* Update first hash calculation. */
+  (*ctxt->hmac_hash->hmac_hupdate)(ctxt->hmac_hashctxt1, data, len);
+  return 0;
+}
+
+
+int Curl_HMAC_final(HMAC_context * ctxt, unsigned char * result)
+{
+  const HMAC_params * hashparams = ctxt->hmac_hash;
+
+  /* Do not get result if called with a null parameter: only release storage. */
+
+  if(!result)
+    result = (unsigned char *) ctxt->hmac_hashctxt2 +
+     ctxt->hmac_hash->hmac_ctxtsize;
+
+  (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt1);
+  (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2,
+   result, hashparams->hmac_resultlen);
+  (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt2);
+  free((char *) ctxt);
+  return 0;
+}
+
+#endif
index 32d0634..25a0f0b 100644 (file)
--- a/lib/md5.c
+++ b/lib/md5.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2010, 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
 #include <string.h>
 
 #include "curl_md5.h"
+#include "curl_hmac.h"
 
 #ifdef USE_GNUTLS
 
 #include <gcrypt.h>
 
-void Curl_md5it(unsigned char *outbuffer, /* 16 bytes */
-                const unsigned char *input)
+typedef struct gcry_md_hd_t MD5_CTX;
+
+static void MD5_Init(MD5_CTX * ctx)
+{
+  gcry_md_open(ctx, GCRY_MD_MD5, 0);
+}
+
+static void MD5_Update(MD5_CTX * ctx,
+                       const unsigned char * input,
+                       unsigned int inputLen)
+{
+  gcry_md_write(*ctx, input, inputLen);
+}
+
+static void MD5_Final(unsigned char digest[16], MD5_CTX * ctx)
 {
-  gcry_md_hd_t ctx;
-  gcry_md_open(&ctx, GCRY_MD_MD5, 0);
-  gcry_md_write(ctx, input, (unsigned int)strlen((char *)input));
-  memcpy (outbuffer, gcry_md_read (ctx, 0), 16);
-  gcry_md_close(ctx);
+  memcpy(digest, gcry_md_read(*ctx, 0), 16);
+  gcry_md_close(*ctx);
 }
 
 #else
@@ -358,6 +369,18 @@ static void Decode (UINT4 *output,
 
 #endif /* USE_SSLEAY */
 
+#endif /* USE_GNUTLS */
+
+const HMAC_params Curl_HMAC_MD5[1] = {
+  (HMAC_hinit_func) MD5_Init,           /* Hash initialization function. */
+  (HMAC_hupdate_func) MD5_Update,       /* Hash update function. */
+  (HMAC_hfinal_func) MD5_Final,         /* Hash computation end function. */
+  sizeof(MD5_CTX),                      /* Size of hash context structure. */
+  64,                                   /* Maximum key length. */
+  16                                    /* Result size. */
+};
+
+
 void Curl_md5it(unsigned char *outbuffer, /* 16 bytes */
                 const unsigned char *input)
 {
@@ -367,6 +390,4 @@ void Curl_md5it(unsigned char *outbuffer, /* 16 bytes */
   MD5_Final(outbuffer, &ctx);
 }
 
-#endif /* USE_GNUTLS */
-
 #endif /* CURL_DISABLE_CRYPTO_AUTH */
index b4fa58f..185bd4b 100644 (file)
@@ -20,6 +20,9 @@
  *
  * RFC2821 SMTP protocol
  * RFC3207 SMTP over TLS
+ * RFC4954 SMTP Authentication
+ * RFC2195 CRAM-MD5 authentication
+ * RFC4616 PLAIN authentication
  *
  ***************************************************************************/
 
@@ -85,6 +88,9 @@
 #include "url.h"
 #include "rawstr.h"
 #include "strtoofft.h"
+#include "curl_base64.h"
+#include "curl_md5.h"
+#include "curl_hmac.h"
 
 #define _MPRINTF_REPLACE /* use our functions only */
 #include <curl/mprintf.h>
@@ -202,20 +208,65 @@ static const struct Curl_handler Curl_handler_smtps_proxy = {
 #endif
 
 
-/* fucntion that checks for an ending smtp status code at the start of the
-   given string */
+/* Function that checks for an ending smtp status code at the start of the
+   given string.
+   As a side effect, it also flags allowed authentication mechanisms according
+   to EHLO AUTH response. */
 static int smtp_endofresp(struct pingpong *pp, int *resp)
 {
   char *line = pp->linestart_resp;
   size_t len = pp->nread_resp;
+  struct connectdata *conn = pp->conn;
+  struct smtp_conn *smtpc = &conn->proto.smtpc;
+  int result;
+  size_t wordlen;
+
+  if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2]))
+    return FALSE;       /* Nothing for us. */
+
+  if((result = line[3] == ' '))
+    *resp = atoi(line);
+
+  line += 4;
+  len -= 4;
+
+  if(smtpc->state == SMTP_EHLO && len >= 5 && !memcmp(line, "AUTH ", 5)) {
+    line += 5;
+    len -= 5;
+
+    for (;;) {
+      while (len &&
+       (*line == ' ' || *line == '\t' || *line == '\r' || *line == '\n')) {
+        line++;
+        len--;
+      }
 
-  if( (len >= 4) && (' ' == line[3]) &&
-      ISDIGIT(line[0]) && ISDIGIT(line[1]) && ISDIGIT(line[2])) {
-    *resp=atoi(line);
-    return TRUE;
+      if(!len)
+        break;
+
+      for (wordlen = 0; wordlen < len && line[wordlen] != ' ' &&
+       line[wordlen] != '\t' && line[wordlen] != '\r' && line[wordlen] != '\n';)
+        wordlen++;
+
+      if(wordlen == 5 && !memcmp(line, "LOGIN", 5))
+        smtpc->authmechs |= SMTP_AUTH_LOGIN;
+      else if(wordlen == 5 && !memcmp(line, "PLAIN", 5))
+        smtpc->authmechs |= SMTP_AUTH_PLAIN;
+      else if(wordlen == 8 && !memcmp(line, "CRAM-MD5", 8))
+        smtpc->authmechs |= SMTP_AUTH_CRAM_MD5;
+      else if(wordlen == 10 && !memcmp(line, "DIGEST-MD5", 10))
+        smtpc->authmechs |= SMTP_AUTH_DIGEST_MD5;
+      else if(wordlen == 6 && !memcmp(line, "GSSAPI", 6))
+        smtpc->authmechs |= SMTP_AUTH_GSSAPI;
+      else if(wordlen == 8 && !memcmp(line, "EXTERNAL", 8))
+        smtpc->authmechs |= SMTP_AUTH_EXTERNAL;
+
+      line += wordlen;
+      len -= wordlen;
+    }
   }
 
-  return FALSE; /* nothing for us */
+  return result;
 }
 
 /* This is the ONLY way to change SMTP state! */
@@ -230,6 +281,11 @@ static void state(struct connectdata *conn,
     "EHLO",
     "HELO",
     "STARTTLS",
+    "AUTHPLAIN",
+    "AUTHLOGIN",
+    "AUTHPASSWD",
+    "AUTHCRAM",
+    "AUTH",
     "MAIL",
     "RCPT",
     "DATA",
@@ -252,8 +308,10 @@ static CURLcode smtp_state_ehlo(struct connectdata *conn)
   CURLcode result;
   struct smtp_conn *smtpc = &conn->proto.smtpc;
 
+  smtpc->authmechs = 0;         /* No known authentication mechanisms yet. */
+
   /* send EHLO */
-  result = Curl_pp_sendf(&conn->proto.smtpc.pp, "EHLO %s", smtpc->domain);
+  result = Curl_pp_sendf(&smtpc->pp, "EHLO %s", smtpc->domain);
 
   if(result)
     return result;
@@ -268,7 +326,7 @@ static CURLcode smtp_state_helo(struct connectdata *conn)
   struct smtp_conn *smtpc = &conn->proto.smtpc;
 
   /* send HELO */
-  result = Curl_pp_sendf(&conn->proto.smtpc.pp, "HELO %s", smtpc->domain);
+  result = Curl_pp_sendf(&smtpc->pp, "HELO %s", smtpc->domain);
 
   if(result)
     return result;
@@ -277,6 +335,106 @@ static CURLcode smtp_state_helo(struct connectdata *conn)
   return CURLE_OK;
 }
 
+static int smtp_auth_plain_data(struct connectdata * conn, char * * outptr)
+{
+  char plainauth[2 * MAX_CURL_USER_LENGTH + MAX_CURL_PASSWORD_LENGTH];
+  unsigned int ulen;
+  unsigned int plen;
+
+  ulen = strlen(conn->user);
+  plen = strlen(conn->passwd);
+
+  if(2 * ulen + plen + 2 > sizeof plainauth)
+    return -1;
+
+  memcpy(plainauth, conn->user, ulen);
+  plainauth[ulen] = '\0';
+  memcpy(plainauth + ulen + 1, conn->user, ulen);
+  plainauth[2 * ulen + 1] = '\0';
+  memcpy(plainauth + 2 * ulen + 2, conn->passwd, plen);
+  return Curl_base64_encode(conn->data, plainauth, 2 * ulen + plen + 2, outptr);
+}
+
+static int smtp_auth_login_user(struct connectdata * conn, char * * outptr)
+{
+  int ulen;
+
+  ulen = strlen(conn->user);
+
+  if(!ulen) {
+    *outptr = strdup("=");
+    return *outptr? 1: -1;
+  }
+
+  return Curl_base64_encode(conn->data, conn->user, ulen, outptr);
+}
+
+static CURLcode smtp_authenticate(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct smtp_conn *smtpc = &conn->proto.smtpc;
+  char * initresp;
+  const char * mech;
+  int l;
+  int state1;
+  int state2;
+
+  if(!conn->bits.user_passwd)
+    state(conn, SMTP_STOP);             /* End of connect phase. */
+  else {
+    initresp = (char *) NULL;
+    l = 1;
+
+    /* Check supported authentication mechanisms by decreasing order of
+       preference. */
+#ifndef CURL_DISABLE_CRYPTO_AUTH
+    if(smtpc->authmechs & SMTP_AUTH_CRAM_MD5) {
+      mech = "CRAM-MD5";
+      state1 = SMTP_AUTHCRAM;
+    }
+    else
+#endif
+    if(smtpc->authmechs & SMTP_AUTH_PLAIN) {
+      mech = "PLAIN";
+      state1 = SMTP_AUTHPLAIN;
+      state2 = SMTP_AUTH;
+      l = smtp_auth_plain_data(conn, &initresp);
+    }
+    else if(smtpc->authmechs & SMTP_AUTH_LOGIN) {
+      mech = "LOGIN";
+      state1 = SMTP_AUTHLOGIN;
+      state2 = SMTP_AUTHPASSWD;
+      l = smtp_auth_login_user(conn, &initresp);
+    }
+    else
+      result = CURLE_LOGIN_DENIED;      /* Other mechanisms not supported. */
+
+    if(!result) {
+      if(l <= 0)
+        result = CURLE_OUT_OF_MEMORY;
+      else if(initresp &&
+       l + strlen(mech) <= 512 - 8) {   /* AUTH <mech> ...<crlf> */
+        result = Curl_pp_sendf(&smtpc->pp, "AUTH %s %s", mech, initresp);
+        free(initresp);
+
+        if(!result)
+          state(conn, state2);
+      }
+      else {
+        if(initresp)
+          free(initresp);
+
+        result = Curl_pp_sendf(&smtpc->pp, "AUTH %s", mech);
+
+        if(!result)
+          state(conn, state1);
+      }
+    }
+  }
+
+  return result;
+}
+
 /* For the SMTP "protocol connect" and "doing" phases only */
 static int smtp_getsock(struct connectdata *conn,
                         curl_socket_t *socks,
@@ -295,12 +453,12 @@ static CURLcode smtp_state_starttls_resp(struct connectdata *conn,
   (void)instate; /* no use for this yet */
 
   if(smtpcode != 220) {
-    if(data->set.ftp_ssl == CURLUSESSL_TRY)
-      state(conn, SMTP_STOP);
-    else {
+    if(data->set.ftp_ssl != CURLUSESSL_TRY) {
       failf(data, "STARTTLS denied. %c", smtpcode);
       result = CURLE_LOGIN_DENIED;
     }
+    else
+      result = smtp_authenticate(conn);
   }
   else {
     /* Curl_ssl_connect is BLOCKING */
@@ -324,23 +482,23 @@ static CURLcode smtp_state_ehlo_resp(struct connectdata *conn,
   (void)instate; /* no use for this yet */
 
   if(smtpcode/100 != 2) {
-    if(data->set.ftp_ssl <= CURLUSESSL_TRY)
+    if((data->set.ftp_ssl <= CURLUSESSL_TRY || conn->ssl[FIRSTSOCKET].use) &&
+     !conn->bits.user_passwd)
       result = smtp_state_helo(conn);
     else {
       failf(data, "Access denied: %d", smtpcode);
       result = CURLE_LOGIN_DENIED;
     }
-  } 
+  }
   else if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
     /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch
        to TLS connection now */
-    result = Curl_pp_sendf(&conn->proto.smtpc.pp, "STARTTLS", NULL);
+    result = Curl_pp_sendf(&conn->proto.smtpc.pp, "STARTTLS");
     state(conn, SMTP_STARTTLS);
   }
-  else {
-    /* end the connect phase */
-    state(conn, SMTP_STOP);
-  }
+  else
+    result = smtp_authenticate(conn);
+
   return result;
 }
 
@@ -357,7 +515,7 @@ static CURLcode smtp_state_helo_resp(struct connectdata *conn,
   if(smtpcode/100 != 2) {
     failf(data, "Access denied: %d", smtpcode);
     result = CURLE_LOGIN_DENIED;
-  } 
+  }
   else {
     /* end the connect phase */
     state(conn, SMTP_STOP);
@@ -365,6 +523,223 @@ static CURLcode smtp_state_helo_resp(struct connectdata *conn,
   return result;
 }
 
+/* for AUTH PLAIN (without initial response) responses */
+static CURLcode smtp_state_authplain_resp(struct connectdata *conn,
+                                          int smtpcode,
+                                          smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  int l;
+  char * plainauth;
+
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode != 334) {
+    failf(data, "Access denied: %d", smtpcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+  else {
+    l = smtp_auth_plain_data(conn, &plainauth);
+
+    if(l <= 0)
+      result = CURLE_OUT_OF_MEMORY;
+    else {
+      result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", plainauth);
+      free(plainauth);
+
+      if(!result)
+        state(conn, SMTP_AUTH);
+    }
+  }
+
+  return result;
+}
+
+/* for AUTH LOGIN (without initial response) responses */
+static CURLcode smtp_state_authlogin_resp(struct connectdata *conn,
+                                          int smtpcode,
+                                          smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  int l;
+  char * authuser;
+
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode != 334) {
+    failf(data, "Access denied: %d", smtpcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+  else {
+    l = smtp_auth_login_user(conn, &authuser);
+
+    if(l <= 0)
+      result = CURLE_OUT_OF_MEMORY;
+    else {
+      result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", authuser);
+      free(authuser);
+
+      if(!result)
+        state(conn, SMTP_AUTHPASSWD);
+    }
+  }
+
+  return result;
+}
+
+/* for responses to user entry of AUTH LOGIN. */
+static CURLcode smtp_state_authpasswd_resp(struct connectdata *conn,
+                                           int smtpcode,
+                                           smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  int plen;
+  int l;
+  char * authpasswd;
+
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode != 334) {
+    failf(data, "Access denied: %d", smtpcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+  else {
+    plen = strlen(conn->passwd);
+
+    if(!plen)
+      result = Curl_pp_sendf(&conn->proto.smtpc.pp, "=");
+    else {
+      l = Curl_base64_encode(data, conn->passwd, plen, &authpasswd);
+
+      if(l <= 0)
+        result = CURLE_OUT_OF_MEMORY;
+      else {
+        result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", authpasswd);
+        free(authpasswd);
+
+        if(!result)
+          state(conn, SMTP_AUTH);
+      }
+    }
+  }
+
+  return result;
+}
+
+#ifndef CURL_DISABLE_CRYPTO_AUTH
+
+/* for AUTH CRAM-MD5 responses. */
+static CURLcode smtp_state_authcram_resp(struct connectdata *conn,
+                                         int smtpcode,
+                                         smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  char * chlg64 = data->state.buffer;
+  unsigned char * chlg;
+  int chlglen;
+  int l;
+  char * rplyb64;
+  HMAC_context * ctxt;
+  unsigned char digest[16];
+  char reply[MAX_CURL_USER_LENGTH + 32 /* 2 * size of MD5 digest */ + 1];
+
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode != 334) {
+    failf(data, "Access denied: %d", smtpcode);
+    return CURLE_LOGIN_DENIED;
+  }
+
+  /* Get the challenge. */
+  for (chlg64 += 4; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++)
+    ;
+
+  chlg = (unsigned char *) NULL;
+  chlglen = 0;
+
+  if(*chlg64 != '=') {
+    for (l = strlen(chlg64); l--;)
+      if(chlg64[l] != '\r' && chlg64[l] != '\n' && chlg64[l] != ' ' &&
+       chlg64[l] != '\t')
+        break;
+
+    if(++l) {
+      chlg64[l] = '\0';
+
+      if(!(chlglen = Curl_base64_decode(chlg64, &chlg)))
+        return CURLE_OUT_OF_MEMORY;
+    }
+  }
+
+  /* Compute digest. */
+  strlen(conn->passwd);
+  ctxt = Curl_HMAC_init(Curl_HMAC_MD5,
+   (const unsigned char *) conn->passwd, strlen(conn->passwd));
+
+  if(!ctxt) {
+    if(chlg)
+      free(chlg);
+
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  if(chlglen > 0)
+    Curl_HMAC_update(ctxt, chlg, chlglen);
+
+  if(chlg)
+    free(chlg);
+
+  Curl_HMAC_final(ctxt, digest);
+
+  /* Prepare the reply. */
+  snprintf(reply, sizeof reply,
+   "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+   conn->user, digest[0], digest[1], digest[2], digest[3], digest[4], digest[5],
+   digest[6], digest[7], digest[8], digest[9], digest[10], digest[11],
+   digest[12], digest[13], digest[14], digest[15]);
+
+  /* Encode it to base64 and send it. */
+  l = Curl_base64_encode(data, reply, 0, &rplyb64);
+
+  if(l <= 0)
+    result = CURLE_OUT_OF_MEMORY;
+  else {
+    result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", rplyb64);
+    free(rplyb64);
+
+    if(!result)
+      state(conn, SMTP_AUTH);
+  }
+
+  return result;
+}
+
+#endif
+
+/* for final responses to AUTH sequences. */
+static CURLcode smtp_state_auth_resp(struct connectdata *conn,
+                                     int smtpcode,
+                                     smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode != 235) {
+    failf(data, "Authentication failed: %d", smtpcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+  else
+    state(conn, SMTP_STOP);             /* End of connect phase. */
+
+  return result;
+}
+
 /* start the DO phase */
 static CURLcode smtp_mail(struct connectdata *conn)
 {
@@ -451,7 +826,7 @@ static CURLcode smtp_state_rcpt_resp(struct connectdata *conn,
     }
 
     /* send DATA */
-    result = Curl_pp_sendf(&conn->proto.smtpc.pp, "DATA", "");
+    result = Curl_pp_sendf(&conn->proto.smtpc.pp, "DATA");
     if(result)
       return result;
 
@@ -541,6 +916,32 @@ static CURLcode smtp_statemach_act(struct connectdata *conn)
       result = smtp_state_helo_resp(conn, smtpcode, smtpc->state);
       break;
 
+    case SMTP_STARTTLS:
+      result = smtp_state_starttls_resp(conn, smtpcode, smtpc->state);
+      break;
+
+    case SMTP_AUTHPLAIN:
+      result = smtp_state_authplain_resp(conn, smtpcode, smtpc->state);
+      break;
+
+    case SMTP_AUTHLOGIN:
+      result = smtp_state_authlogin_resp(conn, smtpcode, smtpc->state);
+      break;
+
+    case SMTP_AUTHPASSWD:
+      result = smtp_state_authpasswd_resp(conn, smtpcode, smtpc->state);
+      break;
+
+#ifndef CURL_DISABLE_CRYPTO_AUTH
+    case SMTP_AUTHCRAM:
+      result = smtp_state_authcram_resp(conn, smtpcode, smtpc->state);
+      break;
+#endif
+
+    case SMTP_AUTH:
+      result = smtp_state_auth_resp(conn, smtpcode, smtpc->state);
+      break;
+
     case SMTP_MAIL:
       result = smtp_state_mail_resp(conn, smtpcode, smtpc->state);
       break;
@@ -549,10 +950,6 @@ static CURLcode smtp_statemach_act(struct connectdata *conn)
       result = smtp_state_rcpt_resp(conn, smtpcode, smtpc->state);
       break;
 
-    case SMTP_STARTTLS:
-      result = smtp_state_starttls_resp(conn, smtpcode, smtpc->state);
-      break;
-
     case SMTP_DATA:
       result = smtp_state_data_resp(conn, smtpcode, smtpc->state);
       break;
@@ -722,7 +1119,7 @@ static CURLcode smtp_connect(struct connectdata *conn,
 
   /* url decode the path and use it as domain with EHLO */
   smtpc->domain = curl_easy_unescape(conn->data, path, 0, &len);
-  if (!smtpc->domain)
+  if(!smtpc->domain)
     return CURLE_OUT_OF_MEMORY;
 
   /* When we connect, we start in the state where we await the server greeting
@@ -893,7 +1290,7 @@ static CURLcode smtp_quit(struct connectdata *conn)
 {
   CURLcode result = CURLE_OK;
 
-  result = Curl_pp_sendf(&conn->proto.smtpc.pp, "QUIT", NULL);
+  result = Curl_pp_sendf(&conn->proto.smtpc.pp, "QUIT");
   if(result)
     return result;
   state(conn, SMTP_QUIT);
@@ -921,7 +1318,7 @@ static CURLcode smtp_disconnect(struct connectdata *conn)
 
   /* The SMTP session may or may not have been allocated/setup at this
      point! */
-  if (smtpc->pp.conn)
+  if(smtpc->pp.conn)
     (void)smtp_quit(conn); /* ignore errors on the LOGOUT */
 
   Curl_pp_disconnect(&smtpc->pp);
index 4716338..417fd52 100644 (file)
@@ -34,6 +34,11 @@ typedef enum {
   SMTP_EHLO,
   SMTP_HELO,
   SMTP_STARTTLS,
+  SMTP_AUTHPLAIN,
+  SMTP_AUTHLOGIN,
+  SMTP_AUTHPASSWD,
+  SMTP_AUTHCRAM,
+  SMTP_AUTH,
   SMTP_MAIL, /* MAIL FROM */
   SMTP_RCPT, /* RCPT TO */
   SMTP_DATA,
@@ -49,10 +54,19 @@ struct smtp_conn {
   char *domain;    /* what to send in the EHLO */
   size_t eob;         /* number of bytes of the EOB (End Of Body) that has been
                          received thus far */
+  unsigned int authmechs;       /* Accepted authentication methods. */
   smtpstate state; /* always use smtp.c:state() to change state! */
   struct curl_slist *rcpt;
 };
 
+/* Authentication mechanism flags. */
+#define SMTP_AUTH_LOGIN         0x0001
+#define SMTP_AUTH_PLAIN         0x0002
+#define SMTP_AUTH_CRAM_MD5      0x0004
+#define SMTP_AUTH_DIGEST_MD5    0x0008
+#define SMTP_AUTH_GSSAPI        0x0010
+#define SMTP_AUTH_EXTERNAL      0x0020
+
 extern const struct Curl_handler Curl_handler_smtp;
 extern const struct Curl_handler Curl_handler_smtps;
 
index 0517f72..071fa48 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2010, 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
index 6f2c090..b28c5ad 100644 (file)
@@ -65,7 +65,7 @@ EXTRA_DIST = test1 test108 test117 test127 test20 test27 test34 test46           \
  test564 test1101 test1102 test1103 test1104 test299 test310 test311       \
  test312 test1105 test565 test800 test1106 test801 test566 test802 test803 \
  test1107 test1108 test1109 test1110 test1111 test1112 test129 test567     \
- test568 test569 test570 test571 test804 test572
+ test568 test569 test570 test571 test572 test804 test805 test806 test807
 
 filecheck:
        @mkdir test-place; \
diff --git a/tests/data/test805 b/tests/data/test805
new file mode 100644 (file)
index 0000000..fba5124
--- /dev/null
@@ -0,0 +1,54 @@
+<testcase>
+<info>
+<keywords>
+SMTP
+SMTP AUTH PLAIN
+RFC4616
+RFC4954
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<servercmd>
+REPLY EHLO 220 AUTH PLAIN
+REPLY AUTH 235 Authenticated
+</servercmd>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+smtp
+</server>
+ <name>
+SMTP plain authentication
+ </name>
+<stdin>
+mail body
+</stdin>
+ <command>
+smtp://%HOSTIP:%SMTPPORT/user --mail-rcpt 805@foo --mail-from 805@from -u test:1234 -T -
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+EHLO user\r
+AUTH PLAIN dGVzdAB0ZXN0ADEyMzQ=\r
+MAIL FROM:805@from\r
+RCPT TO:<805@foo>\r
+DATA\r
+QUIT\r
+</protocol>
+<upload>
+mail body
+\r
+.\r
+</upload>
+</verify>
+</testcase>
diff --git a/tests/data/test806 b/tests/data/test806
new file mode 100644 (file)
index 0000000..8ece5dc
--- /dev/null
@@ -0,0 +1,55 @@
+<testcase>
+<info>
+<keywords>
+SMTP
+SMTP AUTH LOGIN
+RFC4954
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<servercmd>
+REPLY EHLO 220 AUTH LOGIN
+REPLY AUTH 334 UGFzc3dvcmQ6
+REPLY MTIzNA== 235 Authenticated
+</servercmd>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+smtp
+</server>
+ <name>
+SMTP login authentication
+ </name>
+<stdin>
+mail body
+</stdin>
+ <command>
+smtp://%HOSTIP:%SMTPPORT/user --mail-rcpt 806@foo --mail-from 806@from -u test:1234 -T -
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+EHLO user\r
+AUTH LOGIN dGVzdA==\r
+MTIzNA==\r
+MAIL FROM:806@from\r
+RCPT TO:<806@foo>\r
+DATA\r
+QUIT\r
+</protocol>
+<upload>
+mail body
+\r
+.\r
+</upload>
+</verify>
+</testcase>
diff --git a/tests/data/test807 b/tests/data/test807
new file mode 100644 (file)
index 0000000..6daa1d0
--- /dev/null
@@ -0,0 +1,59 @@
+<testcase>
+<info>
+<keywords>
+SMTP
+SMTP AUTH CRAM-MD5
+RFC2195
+RFC4954
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<servercmd>
+REPLY EHLO 220 AUTH CRAM-MD5
+REPLY AUTH 334 PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+
+REPLY dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw 235 Authenticated
+</servercmd>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+smtp
+</server>
+<features>
+crypto
+</features>
+ <name>
+SMTP CRAM-MD5 authentication
+ </name>
+<stdin>
+mail body
+</stdin>
+ <command>
+smtp://%HOSTIP:%SMTPPORT/user --mail-rcpt 807@foo --mail-from 807@from -u tim:tanstaaftanstaaf -T -
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+EHLO user\r
+AUTH CRAM-MD5\r
+dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw\r
+MAIL FROM:807@from\r
+RCPT TO:<807@foo>\r
+DATA\r
+QUIT\r
+</protocol>
+<upload>
+mail body
+\r
+.\r
+</upload>
+</verify>
+</testcase>
index d6b676c..cc69585 100755 (executable)
@@ -1118,7 +1118,7 @@ sub customize {
     logmsg "FTPD: Getting commands from log/ftpserver.cmd\n";
 
     while(<CUSTOM>) {
-        if($_ =~ /REPLY ([A-Z]+) (.*)/) {
+        if($_ =~ /REPLY ([A-Za-z0-9+\/=]+) (.*)/) {
             $customreply{$1}=eval "qq{$2}";
             logmsg "FTPD: set custom reply for $1\n";
         }
@@ -1380,13 +1380,18 @@ while(1) {
             $FTPCMD=$2;
             $FTPARG=$3;
         }
-        else {
-            unless (m/^([A-Z]{3,4})\s?(.*)/i) {
-                sendcontrol "500 '$_': command not understood.\r\n";
-                last;
-            }
+        elsif (m/^([A-Z]{3,4})(\s(.*))?$/i) {
             $FTPCMD=$1;
-            $FTPARG=$2;
+            $FTPARG=$3;
+        }
+        elsif($proto eq "smtp" && m/^[A-Z0-9+\/]{0,512}={0,2}$/i) {
+            # SMTP long "commands" are base64 authentication data.
+            $FTPCMD=$_;
+            $FTPARG="";
+        }
+        else {
+            sendcontrol "500 '$_': command not understood.\r\n";
+            last;
         }
 
         logmsg "< \"$full\"\n";
index 6e3b5b9..c8a54f0 100755 (executable)
@@ -755,7 +755,6 @@ sub verifyftp {
         # has _no_ output!
         $extra .= "--mail-rcpt verifiedserver ";
         $extra .= "--mail-from fake ";
-        $extra .= "--user localhost:unused ";
         $extra .= "--upload /dev/null ";
         $extra .= "--stderr - "; # move stderr to parse the verbose stuff
     }