]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
lib: reorder protocol functions to avoid forward declarations (email)
authorViktor Szakats <commit@vsz.me>
Sun, 21 Dec 2025 15:33:06 +0000 (16:33 +0100)
committerViktor Szakats <commit@vsz.me>
Tue, 13 Jan 2026 00:31:27 +0000 (01:31 +0100)
For protocols: imap, pop3, smtp.

Move protocol hander table to the end of sources, rearrange static
functions is reverse dependency order as necessary.

Closes #20275

lib/imap.c
lib/pop3.c
lib/smtp.c

index 8fe87d993258a1cc0895e0f8237922fb5a104fb0..76b78a12a84f202aa40134b1e60f411c4266e48b 100644 (file)
@@ -137,128 +137,101 @@ struct IMAP {
   BIT(uidvalidity_set);
 };
 
-/* Local API functions */
-static CURLcode imap_regular_transfer(struct Curl_easy *data,
-                                      struct IMAP *imap,
-                                      bool *done);
-static CURLcode imap_do(struct Curl_easy *data, bool *done);
-static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
-                          bool premature);
-static CURLcode imap_connect(struct Curl_easy *data, bool *done);
-static CURLcode imap_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn, bool dead);
-static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done);
-static CURLcode imap_pollset(struct Curl_easy *data,
-                             struct easy_pollset *ps);
-static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done);
-static CURLcode imap_setup_connection(struct Curl_easy *data,
-                                      struct connectdata *conn);
-static char *imap_atom(const char *str, bool escape_only);
+#define IMAP_RESP_OK       1
+#define IMAP_RESP_NOT_OK   2
+#define IMAP_RESP_PREAUTH  3
+
+struct ulbits {
+  int bit;
+  const char *flag;
+};
+
+/***********************************************************************
+ *
+ * imap_sendf()
+ *
+ * Sends the formatted string as an IMAP command to the server.
+ *
+ * Designed to never block.
+ */
 static CURLcode imap_sendf(struct Curl_easy *data,
                            struct imap_conn *imapc,
-                           const char *fmt, ...) CURL_PRINTF(3, 4);
-static CURLcode imap_parse_url_options(struct connectdata *conn,
-                                       struct imap_conn *imapc);
-static CURLcode imap_parse_url_path(struct Curl_easy *data,
-                                    struct IMAP *imap);
-static CURLcode imap_parse_custom_request(struct Curl_easy *data,
-                                          struct IMAP *imap);
-static CURLcode imap_perform_authenticate(struct Curl_easy *data,
-                                          const char *mech,
-                                          const struct bufref *initresp);
-static CURLcode imap_continue_authenticate(struct Curl_easy *data,
-                                           const char *mech,
-                                           const struct bufref *resp);
-static CURLcode imap_cancel_authenticate(struct Curl_easy *data,
-                                         const char *mech);
-static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out);
-static void imap_easy_reset(struct IMAP *imap);
+                           const char *fmt, ...)
+{
+  CURLcode result = CURLE_OK;
 
-/*
- * IMAP protocol handler.
- */
+  DEBUGASSERT(fmt);
 
-const struct Curl_handler Curl_handler_imap = {
-  "imap",                           /* scheme */
-  imap_setup_connection,            /* setup_connection */
-  imap_do,                          /* do_it */
-  imap_done,                        /* done */
-  ZERO_NULL,                        /* do_more */
-  imap_connect,                     /* connect_it */
-  imap_multi_statemach,             /* connecting */
-  imap_doing,                       /* doing */
-  imap_pollset,                     /* proto_pollset */
-  imap_pollset,                     /* doing_pollset */
-  ZERO_NULL,                        /* domore_pollset */
-  ZERO_NULL,                        /* perform_pollset */
-  imap_disconnect,                  /* disconnect */
-  ZERO_NULL,                        /* write_resp */
-  ZERO_NULL,                        /* write_resp_hd */
-  ZERO_NULL,                        /* connection_check */
-  ZERO_NULL,                        /* attach connection */
-  ZERO_NULL,                        /* follow */
-  PORT_IMAP,                        /* defport */
-  CURLPROTO_IMAP,                   /* protocol */
-  CURLPROTO_IMAP,                   /* family */
-  PROTOPT_CLOSEACTION |             /* flags */
-  PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE |
-  PROTOPT_CONN_REUSE
-};
+  /* Calculate the tag based on the connection ID and command ID */
+  curl_msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
+                 'A' + curlx_sltosi((long)(data->conn->connection_id % 26)),
+                 ++imapc->cmdid);
 
-#ifdef USE_SSL
-/*
- * IMAPS protocol handler.
- */
+  /* start with a blank buffer */
+  curlx_dyn_reset(&imapc->dyn);
 
-const struct Curl_handler Curl_handler_imaps = {
-  "imaps",                          /* scheme */
-  imap_setup_connection,            /* setup_connection */
-  imap_do,                          /* do_it */
-  imap_done,                        /* done */
-  ZERO_NULL,                        /* do_more */
-  imap_connect,                     /* connect_it */
-  imap_multi_statemach,             /* connecting */
-  imap_doing,                       /* doing */
-  imap_pollset,                     /* proto_pollset */
-  imap_pollset,                     /* doing_pollset */
-  ZERO_NULL,                        /* domore_pollset */
-  ZERO_NULL,                        /* perform_pollset */
-  imap_disconnect,                  /* disconnect */
-  ZERO_NULL,                        /* write_resp */
-  ZERO_NULL,                        /* write_resp_hd */
-  ZERO_NULL,                        /* connection_check */
-  ZERO_NULL,                        /* attach connection */
-  ZERO_NULL,                        /* follow */
-  PORT_IMAPS,                       /* defport */
-  CURLPROTO_IMAPS,                  /* protocol */
-  CURLPROTO_IMAP,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
-  PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE
-};
+  /* append tag + space + fmt */
+  result = curlx_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt);
+  if(!result) {
+    va_list ap;
+    va_start(ap, fmt);
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wformat-nonliteral"
+#endif
+    result = Curl_pp_vsendf(data, &imapc->pp, curlx_dyn_ptr(&imapc->dyn), ap);
+#ifdef __clang__
+#pragma clang diagnostic pop
 #endif
+    va_end(ap);
+  }
+  return result;
+}
 
-#define IMAP_RESP_OK       1
-#define IMAP_RESP_NOT_OK   2
-#define IMAP_RESP_PREAUTH  3
+/***********************************************************************
+ *
+ * imap_atom()
+ *
+ * Checks the input string for characters that need escaping and returns an
+ * atom ready for sending to the server.
+ *
+ * The returned string needs to be freed.
+ *
+ */
+static char *imap_atom(const char *str, bool escape_only)
+{
+  struct dynbuf line;
+  size_t nclean;
+  size_t len;
 
-/* SASL parameters for the imap protocol */
-static const struct SASLproto saslimap = {
-  "imap",                     /* The service name */
-  imap_perform_authenticate,  /* Send authentication command */
-  imap_continue_authenticate, /* Send authentication continuation */
-  imap_cancel_authenticate,   /* Send authentication cancellation */
-  imap_get_message,           /* Get SASL response message */
-  0,                          /* No maximum initial response length */
-  '+',                        /* Code received when continuation is expected */
-  IMAP_RESP_OK,               /* Code to receive upon authentication success */
-  SASL_AUTH_DEFAULT,          /* Default mechanisms */
-  SASL_FLAG_BASE64            /* Configuration flags */
-};
+  if(!str)
+    return NULL;
 
-struct ulbits {
-  int bit;
-  const char *flag;
-};
+  len = strlen(str);
+  nclean = strcspn(str, "() {%*]\\\"");
+  if(len == nclean)
+    /* nothing to escape, return a strdup */
+    return curlx_strdup(str);
+
+  curlx_dyn_init(&line, 2000);
+
+  if(!escape_only && curlx_dyn_addn(&line, "\"", 1))
+    return NULL;
+
+  while(*str) {
+    if((*str == '\\' || *str == '"') &&
+       curlx_dyn_addn(&line, "\\", 1))
+      return NULL;
+    if(curlx_dyn_addn(&line, str, 1))
+      return NULL;
+    str++;
+  }
+
+  if(!escape_only && curlx_dyn_addn(&line, "\"", 1))
+    return NULL;
+
+  return curlx_dyn_ptr(&line);
+}
 
 /***********************************************************************
  *
@@ -1650,261 +1623,465 @@ static CURLcode imap_pollset(struct Curl_easy *data,
   return imapc ? Curl_pp_pollset(data, &imapc->pp, ps) : CURLE_OK;
 }
 
+static void imap_easy_reset(struct IMAP *imap)
+{
+  Curl_safefree(imap->mailbox);
+  Curl_safefree(imap->uid);
+  Curl_safefree(imap->mindex);
+  Curl_safefree(imap->section);
+  Curl_safefree(imap->partial);
+  Curl_safefree(imap->query);
+  Curl_safefree(imap->custom);
+  Curl_safefree(imap->custom_params);
+  /* Clear the transfer mode for the next request */
+  imap->transfer = PPTRANSFER_BODY;
+}
+
 /***********************************************************************
  *
- * imap_connect()
- *
- * This function should do everything that is to be considered a part of the
- * connection phase.
+ * imap_is_bchar()
  *
- * The variable 'done' points to will be TRUE if the protocol-layer connect
- * phase is done when this function returns, or FALSE if not.
+ * Portable test of whether the specified char is a "bchar" as defined in the
+ * grammar of RFC-5092.
  */
-static CURLcode imap_connect(struct Curl_easy *data, bool *done)
+static bool imap_is_bchar(char ch)
 {
-  struct imap_conn *imapc =
-    Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN);
-  CURLcode result = CURLE_OK;
-
-  *done = FALSE; /* default to not done yet */
-  if(!imapc)
-    return CURLE_FAILED_INIT;
-
-  /* Parse the URL options */
-  result = imap_parse_url_options(data->conn, imapc);
-  if(result)
-    return result;
-
-  /* Start off waiting for the server greeting response */
-  imap_state(data, imapc, IMAP_SERVERGREET);
-
-  /* Start off with an response id of '*' */
-  curlx_strcopy(imapc->resptag, sizeof(imapc->resptag), "*", 1);
+  /* Performing the alnum check with this macro is faster because of ASCII
+     arithmetic */
+  if(ISALNUM(ch))
+    return TRUE;
 
-  result = imap_multi_statemach(data, done);
+  switch(ch) {
+  /* bchar */
+  case ':':
+  case '@':
+  case '/':
+  /* bchar -> achar */
+  case '&':
+  case '=':
+  /* bchar -> achar -> uchar -> unreserved (without alphanumeric) */
+  case '-':
+  case '.':
+  case '_':
+  case '~':
+  /* bchar -> achar -> uchar -> sub-delims-sh */
+  case '!':
+  case '$':
+  case '\'':
+  case '(':
+  case ')':
+  case '*':
+  case '+':
+  case ',':
+  /* bchar -> achar -> uchar -> pct-encoded */
+  case '%': /* HEXDIG chars are already included above */
+    return TRUE;
 
-  return result;
+  default:
+    return FALSE;
+  }
 }
 
 /***********************************************************************
  *
- * imap_done()
+ * imap_parse_url_options()
  *
- * The DONE function. This does what needs to be done after a single DO has
- * performed.
- *
- * Input argument is already checked for validity.
+ * Parse the URL login options.
  */
-static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
-                          bool premature)
+static CURLcode imap_parse_url_options(struct connectdata *conn,
+                                       struct imap_conn *imapc)
 {
   CURLcode result = CURLE_OK;
-  struct connectdata *conn = data->conn;
-  struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
-  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
+  const char *ptr = conn->options;
+  bool prefer_login = FALSE;
 
-  (void)premature;
+  while(!result && ptr && *ptr) {
+    const char *key = ptr;
+    const char *value;
 
-  if(!imapc)
-    return CURLE_FAILED_INIT;
-  if(!imap)
-    return CURLE_OK;
+    while(*ptr && *ptr != '=')
+      ptr++;
 
-  if(status) {
-    connclose(conn, "IMAP done with bad status"); /* marked for closure */
-    result = status;         /* use the already set error code */
-  }
-  else if(!data->set.connect_only &&
-          ((!imap->custom && (imap->uid || imap->mindex)) ||
-           (imap->custom && data->req.maxdownload > 0) ||
-           data->state.upload || IS_MIME_POST(data))) {
-    /* Handle responses after FETCH or APPEND transfer has finished.
-       For custom commands, check if we set up a download which indicates
-       a FETCH-like command with literal data. */
+    value = ptr + 1;
 
-    if(!data->state.upload && !IS_MIME_POST(data))
-      imap_state(data, imapc, IMAP_FETCH_FINAL);
+    while(*ptr && *ptr != ';')
+      ptr++;
+
+    if(curl_strnequal(key, "AUTH=+LOGIN", 11)) {
+      /* User prefers plaintext LOGIN over any SASL, including SASL LOGIN */
+      prefer_login = TRUE;
+      imapc->sasl.prefmech = SASL_AUTH_NONE;
+    }
+    else if(curl_strnequal(key, "AUTH=", 5)) {
+      prefer_login = FALSE;
+      result = Curl_sasl_parse_url_auth_option(&imapc->sasl,
+                                               value, ptr - value);
+    }
     else {
-      /* End the APPEND command first by sending an empty line */
-      result = Curl_pp_sendf(data, &imapc->pp, "%s", "");
-      if(!result)
-        imap_state(data, imapc, IMAP_APPEND_FINAL);
+      prefer_login = FALSE;
+      result = CURLE_URL_MALFORMAT;
     }
 
-    /* Run the state-machine */
-    if(!result)
-      result = imap_block_statemach(data, imapc, FALSE);
+    if(*ptr == ';')
+      ptr++;
+  }
+
+  if(prefer_login)
+    imapc->preftype = IMAP_TYPE_CLEARTEXT;
+  else {
+    switch(imapc->sasl.prefmech) {
+    case SASL_AUTH_NONE:
+      imapc->preftype = IMAP_TYPE_NONE;
+      break;
+    case SASL_AUTH_DEFAULT:
+      imapc->preftype = IMAP_TYPE_ANY;
+      break;
+    default:
+      imapc->preftype = IMAP_TYPE_SASL;
+      break;
+    }
   }
 
-  imap_easy_reset(imap);
   return result;
 }
 
 /***********************************************************************
  *
- * imap_perform()
+ * imap_parse_url_path()
+ *
+ * Parse the URL path into separate path components.
  *
- * This is the actual DO function for IMAP. Fetch or append a message, or do
- * other things according to the options previously setup.
  */
-static CURLcode imap_perform(struct Curl_easy *data, bool *connected,
-                             bool *dophase_done)
+static CURLcode imap_parse_url_path(struct Curl_easy *data,
+                                    struct IMAP *imap)
 {
-  /* This is IMAP and no proxy */
+  /* The imap struct is already initialised in imap_connect() */
   CURLcode result = CURLE_OK;
-  struct connectdata *conn = data->conn;
-  struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
-  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
-  bool selected = FALSE;
+  const char *begin = &data->state.up.path[1]; /* skip leading slash */
+  const char *ptr = begin;
 
-  DEBUGF(infof(data, "DO phase starts"));
-  if(!imapc || !imap)
-    return CURLE_FAILED_INIT;
+  /* See how much of the URL is a valid path and decode it */
+  while(imap_is_bchar(*ptr))
+    ptr++;
 
-  if(data->req.no_body) {
-    /* Requested no body means no transfer */
-    imap->transfer = PPTRANSFER_INFO;
+  if(ptr != begin) {
+    /* Remove the trailing slash if present */
+    const char *end = ptr;
+    if(end > begin && end[-1] == '/')
+      end--;
+
+    result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL,
+                            REJECT_CTRL);
+    if(result)
+      return result;
   }
+  else
+    imap->mailbox = NULL;
 
-  *dophase_done = FALSE; /* not done yet */
+  /* There can be any number of parameters in the form ";NAME=VALUE" */
+  while(*ptr == ';') {
+    char *name;
+    char *value;
+    size_t valuelen;
 
-  /* Determine if the requested mailbox (with the same UIDVALIDITY if set)
-     has already been selected on this connection */
-  if(imap->mailbox && imapc->mailbox &&
-     curl_strequal(imap->mailbox, imapc->mailbox) &&
-     (!imap->uidvalidity_set || !imapc->mb_uidvalidity_set ||
-      (imap->uidvalidity == imapc->mb_uidvalidity)))
-    selected = TRUE;
+    /* Find the length of the name parameter */
+    begin = ++ptr;
+    while(*ptr && *ptr != '=')
+      ptr++;
 
-  /* Start the first command in the DO phase */
-  if(data->state.upload || IS_MIME_POST(data))
-    /* APPEND can be executed directly */
-    result = imap_perform_append(data, imapc, imap);
-  else if(imap->custom && (selected || !imap->mailbox))
-    /* Custom command using the same mailbox or no mailbox */
-    result = imap_perform_list(data, imapc, imap);
-  else if(!imap->custom && selected && (imap->uid || imap->mindex))
-    /* FETCH from the same mailbox */
-    result = imap_perform_fetch(data, imapc, imap);
-  else if(!imap->custom && selected && imap->query)
-    /* SEARCH the current mailbox */
-    result = imap_perform_search(data, imapc, imap);
-  else if(imap->mailbox && !selected &&
-          (imap->custom || imap->uid || imap->mindex || imap->query))
-    /* SELECT the mailbox */
-    result = imap_perform_select(data, imapc, imap);
-  else
-    /* LIST */
-    result = imap_perform_list(data, imapc, imap);
+    if(!*ptr)
+      return CURLE_URL_MALFORMAT;
 
-  if(result)
-    return result;
+    /* Decode the name parameter */
+    result = Curl_urldecode(begin, ptr - begin, &name, NULL,
+                            REJECT_CTRL);
+    if(result)
+      return result;
 
-  /* Run the state-machine */
-  result = imap_multi_statemach(data, dophase_done);
+    /* Find the length of the value parameter */
+    begin = ++ptr;
+    while(imap_is_bchar(*ptr))
+      ptr++;
 
-  *connected = Curl_conn_is_connected(conn, FIRSTSOCKET);
+    /* Decode the value parameter */
+    result = Curl_urldecode(begin, ptr - begin, &value, &valuelen,
+                            REJECT_CTRL);
+    if(result) {
+      curlx_free(name);
+      return result;
+    }
 
-  if(*dophase_done)
-    DEBUGF(infof(data, "DO phase is complete"));
+    DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value));
 
-  return result;
+    /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION
+       and PARTIAL) stripping of the trailing slash character if it is
+       present.
+
+       Note: Unknown parameters trigger a URL_MALFORMAT error. */
+    if(valuelen > 0 && value[valuelen - 1] == '/')
+      value[valuelen - 1] = '\0';
+    if(valuelen) {
+      if(curl_strequal(name, "UIDVALIDITY") && !imap->uidvalidity_set) {
+        curl_off_t num;
+        const char *p = (const char *)value;
+        if(!curlx_str_number(&p, &num, UINT_MAX)) {
+          imap->uidvalidity = (unsigned int)num;
+          imap->uidvalidity_set = TRUE;
+        }
+        curlx_free(value);
+      }
+      else if(curl_strequal(name, "UID") && !imap->uid) {
+        imap->uid = value;
+      }
+      else if(curl_strequal(name, "MAILINDEX") && !imap->mindex) {
+        imap->mindex = value;
+      }
+      else if(curl_strequal(name, "SECTION") && !imap->section) {
+        imap->section = value;
+      }
+      else if(curl_strequal(name, "PARTIAL") && !imap->partial) {
+        imap->partial = value;
+      }
+      else {
+        curlx_free(name);
+        curlx_free(value);
+        return CURLE_URL_MALFORMAT;
+      }
+    }
+    else
+      /* blank? */
+      curlx_free(value);
+    curlx_free(name);
+  }
+
+  /* Does the URL contain a query parameter? Only valid when we have a mailbox
+     and no UID as per RFC-5092 */
+  if(imap->mailbox && !imap->uid && !imap->mindex) {
+    /* Get the query parameter, URL decoded */
+    CURLUcode uc = curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query,
+                                CURLU_URLDECODE);
+    if(uc == CURLUE_OUT_OF_MEMORY)
+      return CURLE_OUT_OF_MEMORY;
+  }
+
+  /* Any extra stuff at the end of the URL is an error */
+  if(*ptr)
+    return CURLE_URL_MALFORMAT;
+
+  return CURLE_OK;
 }
 
 /***********************************************************************
  *
- * imap_do()
- *
- * This function is registered as 'curl_do' function. It decodes the path
- * parts etc as a wrapper to the actual DO function (imap_perform).
+ * imap_parse_custom_request()
  *
- * The input argument is already checked for validity.
+ * Parse the custom request.
  */
-static CURLcode imap_do(struct Curl_easy *data, bool *done)
+static CURLcode imap_parse_custom_request(struct Curl_easy *data,
+                                          struct IMAP *imap)
 {
-  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
   CURLcode result = CURLE_OK;
-  *done = FALSE; /* default to false */
+  const char *custom = data->set.str[STRING_CUSTOMREQUEST];
 
-  if(!imap)
-    return CURLE_FAILED_INIT;
-  /* Parse the URL path */
-  result = imap_parse_url_path(data, imap);
-  if(result)
-    return result;
+  if(custom) {
+    /* URL decode the custom request */
+    result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL);
 
-  /* Parse the custom request */
-  result = imap_parse_custom_request(data, imap);
+    /* Extract the parameters if specified */
+    if(!result) {
+      const char *params = imap->custom;
+
+      while(*params && *params != ' ')
+        params++;
+
+      if(*params) {
+        imap->custom_params = curlx_strdup(params);
+        imap->custom[params - imap->custom] = '\0';
+
+        if(!imap->custom_params)
+          result = CURLE_OUT_OF_MEMORY;
+      }
+    }
+  }
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * imap_connect()
+ *
+ * This function should do everything that is to be considered a part of the
+ * connection phase.
+ *
+ * The variable 'done' points to will be TRUE if the protocol-layer connect
+ * phase is done when this function returns, or FALSE if not.
+ */
+static CURLcode imap_connect(struct Curl_easy *data, bool *done)
+{
+  struct imap_conn *imapc =
+    Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN);
+  CURLcode result = CURLE_OK;
+
+  *done = FALSE; /* default to not done yet */
+  if(!imapc)
+    return CURLE_FAILED_INIT;
+
+  /* Parse the URL options */
+  result = imap_parse_url_options(data->conn, imapc);
   if(result)
     return result;
 
-  result = imap_regular_transfer(data, imap, done);
+  /* Start off waiting for the server greeting response */
+  imap_state(data, imapc, IMAP_SERVERGREET);
+
+  /* Start off with an response id of '*' */
+  curlx_strcopy(imapc->resptag, sizeof(imapc->resptag), "*", 1);
+
+  result = imap_multi_statemach(data, done);
 
   return result;
 }
 
 /***********************************************************************
  *
- * imap_disconnect()
+ * imap_done()
  *
- * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
- * resources. BLOCKING.
+ * The DONE function. This does what needs to be done after a single DO has
+ * performed.
+ *
+ * Input argument is already checked for validity.
  */
-static CURLcode imap_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn, bool dead_connection)
+static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
+                          bool premature)
 {
+  CURLcode result = CURLE_OK;
+  struct connectdata *conn = data->conn;
   struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
+  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
 
-  (void)data;
-  if(imapc) {
-    /* We cannot send quit unconditionally. If this connection is stale or
-       bad in any way (pingpong has pending data to send),
-       sending quit and waiting around here will make the
-       disconnect wait in vain and cause more problems than we need to. */
-    if(!dead_connection && conn->bits.protoconnstart &&
-       !Curl_pp_needs_flush(data, &imapc->pp)) {
-      if(!imap_perform_logout(data, imapc))
-        (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */
-    }
+  (void)premature;
+
+  if(!imapc)
+    return CURLE_FAILED_INIT;
+  if(!imap)
+    return CURLE_OK;
+
+  if(status) {
+    connclose(conn, "IMAP done with bad status"); /* marked for closure */
+    result = status;         /* use the already set error code */
   }
-  return CURLE_OK;
-}
+  else if(!data->set.connect_only &&
+          ((!imap->custom && (imap->uid || imap->mindex)) ||
+           (imap->custom && data->req.maxdownload > 0) ||
+           data->state.upload || IS_MIME_POST(data))) {
+    /* Handle responses after FETCH or APPEND transfer has finished.
+       For custom commands, check if we set up a download which indicates
+       a FETCH-like command with literal data. */
 
-/* Call this when the DO phase has completed */
-static CURLcode imap_dophase_done(struct Curl_easy *data,
-                                  struct IMAP *imap,
-                                  bool connected)
-{
-  (void)connected;
+    if(!data->state.upload && !IS_MIME_POST(data))
+      imap_state(data, imapc, IMAP_FETCH_FINAL);
+    else {
+      /* End the APPEND command first by sending an empty line */
+      result = Curl_pp_sendf(data, &imapc->pp, "%s", "");
+      if(!result)
+        imap_state(data, imapc, IMAP_APPEND_FINAL);
+    }
 
-  if(imap->transfer != PPTRANSFER_BODY)
-    /* no data to transfer */
-    Curl_xfer_setup_nop(data);
+    /* Run the state-machine */
+    if(!result)
+      result = imap_block_statemach(data, imapc, FALSE);
+  }
 
-  return CURLE_OK;
+  imap_easy_reset(imap);
+  return result;
 }
 
-/* Called from multi.c while DOing */
-static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done)
+/***********************************************************************
+ *
+ * imap_perform()
+ *
+ * This is the actual DO function for IMAP. Fetch or append a message, or do
+ * other things according to the options previously setup.
+ */
+static CURLcode imap_perform(struct Curl_easy *data, bool *connected,
+                             bool *dophase_done)
 {
+  /* This is IMAP and no proxy */
+  CURLcode result = CURLE_OK;
+  struct connectdata *conn = data->conn;
+  struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
   struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
-  CURLcode result;
+  bool selected = FALSE;
 
-  if(!imap)
+  DEBUGF(infof(data, "DO phase starts"));
+  if(!imapc || !imap)
     return CURLE_FAILED_INIT;
 
-  result = imap_multi_statemach(data, dophase_done);
+  if(data->req.no_body) {
+    /* Requested no body means no transfer */
+    imap->transfer = PPTRANSFER_INFO;
+  }
+
+  *dophase_done = FALSE; /* not done yet */
+
+  /* Determine if the requested mailbox (with the same UIDVALIDITY if set)
+     has already been selected on this connection */
+  if(imap->mailbox && imapc->mailbox &&
+     curl_strequal(imap->mailbox, imapc->mailbox) &&
+     (!imap->uidvalidity_set || !imapc->mb_uidvalidity_set ||
+      (imap->uidvalidity == imapc->mb_uidvalidity)))
+    selected = TRUE;
+
+  /* Start the first command in the DO phase */
+  if(data->state.upload || IS_MIME_POST(data))
+    /* APPEND can be executed directly */
+    result = imap_perform_append(data, imapc, imap);
+  else if(imap->custom && (selected || !imap->mailbox))
+    /* Custom command using the same mailbox or no mailbox */
+    result = imap_perform_list(data, imapc, imap);
+  else if(!imap->custom && selected && (imap->uid || imap->mindex))
+    /* FETCH from the same mailbox */
+    result = imap_perform_fetch(data, imapc, imap);
+  else if(!imap->custom && selected && imap->query)
+    /* SEARCH the current mailbox */
+    result = imap_perform_search(data, imapc, imap);
+  else if(imap->mailbox && !selected &&
+          (imap->custom || imap->uid || imap->mindex || imap->query))
+    /* SELECT the mailbox */
+    result = imap_perform_select(data, imapc, imap);
+  else
+    /* LIST */
+    result = imap_perform_list(data, imapc, imap);
+
   if(result)
-    DEBUGF(infof(data, "DO phase failed"));
-  else if(*dophase_done) {
-    result = imap_dophase_done(data, imap, FALSE /* not connected */);
+    return result;
+
+  /* Run the state-machine */
+  result = imap_multi_statemach(data, dophase_done);
+
+  *connected = Curl_conn_is_connected(conn, FIRSTSOCKET);
 
+  if(*dophase_done)
     DEBUGF(infof(data, "DO phase is complete"));
-  }
 
   return result;
 }
 
+/* Call this when the DO phase has completed */
+static CURLcode imap_dophase_done(struct Curl_easy *data,
+                                  struct IMAP *imap,
+                                  bool connected)
+{
+  (void)connected;
+
+  if(imap->transfer != PPTRANSFER_BODY)
+    /* no data to transfer */
+    Curl_xfer_setup_nop(data);
+
+  return CURLE_OK;
+}
+
 /***********************************************************************
  *
  * imap_regular_transfer()
@@ -1937,31 +2114,97 @@ static CURLcode imap_regular_transfer(struct Curl_easy *data,
   return result;
 }
 
-static void imap_easy_reset(struct IMAP *imap)
+/***********************************************************************
+ *
+ * imap_do()
+ *
+ * This function is registered as 'curl_do' function. It decodes the path
+ * parts etc as a wrapper to the actual DO function (imap_perform).
+ *
+ * The input argument is already checked for validity.
+ */
+static CURLcode imap_do(struct Curl_easy *data, bool *done)
 {
-  Curl_safefree(imap->mailbox);
-  Curl_safefree(imap->uid);
-  Curl_safefree(imap->mindex);
-  Curl_safefree(imap->section);
-  Curl_safefree(imap->partial);
-  Curl_safefree(imap->query);
-  Curl_safefree(imap->custom);
-  Curl_safefree(imap->custom_params);
-  /* Clear the transfer mode for the next request */
-  imap->transfer = PPTRANSFER_BODY;
-}
+  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
+  CURLcode result = CURLE_OK;
+  *done = FALSE; /* default to false */
 
-static void imap_easy_dtor(void *key, size_t klen, void *entry)
-{
-  struct IMAP *imap = entry;
-  (void)key;
-  (void)klen;
-  imap_easy_reset(imap);
-  curlx_free(imap);
+  if(!imap)
+    return CURLE_FAILED_INIT;
+  /* Parse the URL path */
+  result = imap_parse_url_path(data, imap);
+  if(result)
+    return result;
+
+  /* Parse the custom request */
+  result = imap_parse_custom_request(data, imap);
+  if(result)
+    return result;
+
+  result = imap_regular_transfer(data, imap, done);
+
+  return result;
 }
 
-static void imap_conn_dtor(void *key, size_t klen, void *entry)
-{
+/***********************************************************************
+ *
+ * imap_disconnect()
+ *
+ * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
+ * resources. BLOCKING.
+ */
+static CURLcode imap_disconnect(struct Curl_easy *data,
+                                struct connectdata *conn, bool dead_connection)
+{
+  struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
+
+  (void)data;
+  if(imapc) {
+    /* We cannot send quit unconditionally. If this connection is stale or
+       bad in any way (pingpong has pending data to send),
+       sending quit and waiting around here will make the
+       disconnect wait in vain and cause more problems than we need to. */
+    if(!dead_connection && conn->bits.protoconnstart &&
+       !Curl_pp_needs_flush(data, &imapc->pp)) {
+      if(!imap_perform_logout(data, imapc))
+        (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */
+    }
+  }
+  return CURLE_OK;
+}
+
+/* Called from multi.c while DOing */
+static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done)
+{
+  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
+  CURLcode result;
+
+  if(!imap)
+    return CURLE_FAILED_INIT;
+
+  result = imap_multi_statemach(data, dophase_done);
+  if(result)
+    DEBUGF(infof(data, "DO phase failed"));
+  else if(*dophase_done) {
+    result = imap_dophase_done(data, imap, FALSE /* not connected */);
+
+    DEBUGF(infof(data, "DO phase is complete"));
+  }
+
+  return result;
+}
+
+static void imap_easy_dtor(void *key, size_t klen, void *entry)
+{
+  struct IMAP *imap = entry;
+  (void)key;
+  (void)klen;
+  imap_easy_reset(imap);
+  curlx_free(imap);
+}
+
+static void imap_conn_dtor(void *key, size_t klen, void *entry)
+{
   struct imap_conn *imapc = entry;
   (void)key;
   (void)klen;
@@ -1971,6 +2214,20 @@ static void imap_conn_dtor(void *key, size_t klen, void *entry)
   curlx_free(imapc);
 }
 
+/* SASL parameters for the imap protocol */
+static const struct SASLproto saslimap = {
+  "imap",                     /* The service name */
+  imap_perform_authenticate,  /* Send authentication command */
+  imap_continue_authenticate, /* Send authentication continuation */
+  imap_cancel_authenticate,   /* Send authentication cancellation */
+  imap_get_message,           /* Get SASL response message */
+  0,                          /* No maximum initial response length */
+  '+',                        /* Code received when continuation is expected */
+  IMAP_RESP_OK,               /* Code to receive upon authentication success */
+  SASL_AUTH_DEFAULT,          /* Default mechanisms */
+  SASL_FLAG_BASE64            /* Configuration flags */
+};
+
 static CURLcode imap_setup_connection(struct Curl_easy *data,
                                       struct connectdata *conn)
 {
@@ -2003,361 +2260,65 @@ static CURLcode imap_setup_connection(struct Curl_easy *data,
   return CURLE_OK;
 }
 
-/***********************************************************************
- *
- * imap_sendf()
- *
- * Sends the formatted string as an IMAP command to the server.
- *
- * Designed to never block.
- */
-static CURLcode imap_sendf(struct Curl_easy *data,
-                           struct imap_conn *imapc,
-                           const char *fmt, ...)
-{
-  CURLcode result = CURLE_OK;
-
-  DEBUGASSERT(fmt);
-
-  /* Calculate the tag based on the connection ID and command ID */
-  curl_msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
-                 'A' + curlx_sltosi((long)(data->conn->connection_id % 26)),
-                 ++imapc->cmdid);
-
-  /* start with a blank buffer */
-  curlx_dyn_reset(&imapc->dyn);
-
-  /* append tag + space + fmt */
-  result = curlx_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt);
-  if(!result) {
-    va_list ap;
-    va_start(ap, fmt);
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wformat-nonliteral"
-#endif
-    result = Curl_pp_vsendf(data, &imapc->pp, curlx_dyn_ptr(&imapc->dyn), ap);
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
-    va_end(ap);
-  }
-  return result;
-}
-
-/***********************************************************************
- *
- * imap_atom()
- *
- * Checks the input string for characters that need escaping and returns an
- * atom ready for sending to the server.
- *
- * The returned string needs to be freed.
- *
- */
-static char *imap_atom(const char *str, bool escape_only)
-{
-  struct dynbuf line;
-  size_t nclean;
-  size_t len;
-
-  if(!str)
-    return NULL;
-
-  len = strlen(str);
-  nclean = strcspn(str, "() {%*]\\\"");
-  if(len == nclean)
-    /* nothing to escape, return a strdup */
-    return curlx_strdup(str);
-
-  curlx_dyn_init(&line, 2000);
-
-  if(!escape_only && curlx_dyn_addn(&line, "\"", 1))
-    return NULL;
-
-  while(*str) {
-    if((*str == '\\' || *str == '"') &&
-       curlx_dyn_addn(&line, "\\", 1))
-      return NULL;
-    if(curlx_dyn_addn(&line, str, 1))
-      return NULL;
-    str++;
-  }
-
-  if(!escape_only && curlx_dyn_addn(&line, "\"", 1))
-    return NULL;
-
-  return curlx_dyn_ptr(&line);
-}
-
-/***********************************************************************
- *
- * imap_is_bchar()
- *
- * Portable test of whether the specified char is a "bchar" as defined in the
- * grammar of RFC-5092.
- */
-static bool imap_is_bchar(char ch)
-{
-  /* Performing the alnum check with this macro is faster because of ASCII
-     arithmetic */
-  if(ISALNUM(ch))
-    return TRUE;
-
-  switch(ch) {
-  /* bchar */
-  case ':':
-  case '@':
-  case '/':
-  /* bchar -> achar */
-  case '&':
-  case '=':
-  /* bchar -> achar -> uchar -> unreserved (without alphanumeric) */
-  case '-':
-  case '.':
-  case '_':
-  case '~':
-  /* bchar -> achar -> uchar -> sub-delims-sh */
-  case '!':
-  case '$':
-  case '\'':
-  case '(':
-  case ')':
-  case '*':
-  case '+':
-  case ',':
-  /* bchar -> achar -> uchar -> pct-encoded */
-  case '%': /* HEXDIG chars are already included above */
-    return TRUE;
-
-  default:
-    return FALSE;
-  }
-}
-
-/***********************************************************************
- *
- * imap_parse_url_options()
- *
- * Parse the URL login options.
- */
-static CURLcode imap_parse_url_options(struct connectdata *conn,
-                                       struct imap_conn *imapc)
-{
-  CURLcode result = CURLE_OK;
-  const char *ptr = conn->options;
-  bool prefer_login = FALSE;
-
-  while(!result && ptr && *ptr) {
-    const char *key = ptr;
-    const char *value;
-
-    while(*ptr && *ptr != '=')
-      ptr++;
-
-    value = ptr + 1;
-
-    while(*ptr && *ptr != ';')
-      ptr++;
-
-    if(curl_strnequal(key, "AUTH=+LOGIN", 11)) {
-      /* User prefers plaintext LOGIN over any SASL, including SASL LOGIN */
-      prefer_login = TRUE;
-      imapc->sasl.prefmech = SASL_AUTH_NONE;
-    }
-    else if(curl_strnequal(key, "AUTH=", 5)) {
-      prefer_login = FALSE;
-      result = Curl_sasl_parse_url_auth_option(&imapc->sasl,
-                                               value, ptr - value);
-    }
-    else {
-      prefer_login = FALSE;
-      result = CURLE_URL_MALFORMAT;
-    }
-
-    if(*ptr == ';')
-      ptr++;
-  }
-
-  if(prefer_login)
-    imapc->preftype = IMAP_TYPE_CLEARTEXT;
-  else {
-    switch(imapc->sasl.prefmech) {
-    case SASL_AUTH_NONE:
-      imapc->preftype = IMAP_TYPE_NONE;
-      break;
-    case SASL_AUTH_DEFAULT:
-      imapc->preftype = IMAP_TYPE_ANY;
-      break;
-    default:
-      imapc->preftype = IMAP_TYPE_SASL;
-      break;
-    }
-  }
-
-  return result;
-}
-
-/***********************************************************************
- *
- * imap_parse_url_path()
- *
- * Parse the URL path into separate path components.
- *
+/*
+ * IMAP protocol handler.
  */
-static CURLcode imap_parse_url_path(struct Curl_easy *data,
-                                    struct IMAP *imap)
-{
-  /* The imap struct is already initialised in imap_connect() */
-  CURLcode result = CURLE_OK;
-  const char *begin = &data->state.up.path[1]; /* skip leading slash */
-  const char *ptr = begin;
-
-  /* See how much of the URL is a valid path and decode it */
-  while(imap_is_bchar(*ptr))
-    ptr++;
-
-  if(ptr != begin) {
-    /* Remove the trailing slash if present */
-    const char *end = ptr;
-    if(end > begin && end[-1] == '/')
-      end--;
-
-    result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL,
-                            REJECT_CTRL);
-    if(result)
-      return result;
-  }
-  else
-    imap->mailbox = NULL;
-
-  /* There can be any number of parameters in the form ";NAME=VALUE" */
-  while(*ptr == ';') {
-    char *name;
-    char *value;
-    size_t valuelen;
-
-    /* Find the length of the name parameter */
-    begin = ++ptr;
-    while(*ptr && *ptr != '=')
-      ptr++;
-
-    if(!*ptr)
-      return CURLE_URL_MALFORMAT;
-
-    /* Decode the name parameter */
-    result = Curl_urldecode(begin, ptr - begin, &name, NULL,
-                            REJECT_CTRL);
-    if(result)
-      return result;
-
-    /* Find the length of the value parameter */
-    begin = ++ptr;
-    while(imap_is_bchar(*ptr))
-      ptr++;
-
-    /* Decode the value parameter */
-    result = Curl_urldecode(begin, ptr - begin, &value, &valuelen,
-                            REJECT_CTRL);
-    if(result) {
-      curlx_free(name);
-      return result;
-    }
-
-    DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value));
-
-    /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION
-       and PARTIAL) stripping of the trailing slash character if it is
-       present.
-
-       Note: Unknown parameters trigger a URL_MALFORMAT error. */
-    if(valuelen > 0 && value[valuelen - 1] == '/')
-      value[valuelen - 1] = '\0';
-    if(valuelen) {
-      if(curl_strequal(name, "UIDVALIDITY") && !imap->uidvalidity_set) {
-        curl_off_t num;
-        const char *p = (const char *)value;
-        if(!curlx_str_number(&p, &num, UINT_MAX)) {
-          imap->uidvalidity = (unsigned int)num;
-          imap->uidvalidity_set = TRUE;
-        }
-        curlx_free(value);
-      }
-      else if(curl_strequal(name, "UID") && !imap->uid) {
-        imap->uid = value;
-      }
-      else if(curl_strequal(name, "MAILINDEX") && !imap->mindex) {
-        imap->mindex = value;
-      }
-      else if(curl_strequal(name, "SECTION") && !imap->section) {
-        imap->section = value;
-      }
-      else if(curl_strequal(name, "PARTIAL") && !imap->partial) {
-        imap->partial = value;
-      }
-      else {
-        curlx_free(name);
-        curlx_free(value);
-        return CURLE_URL_MALFORMAT;
-      }
-    }
-    else
-      /* blank? */
-      curlx_free(value);
-    curlx_free(name);
-  }
-
-  /* Does the URL contain a query parameter? Only valid when we have a mailbox
-     and no UID as per RFC-5092 */
-  if(imap->mailbox && !imap->uid && !imap->mindex) {
-    /* Get the query parameter, URL decoded */
-    CURLUcode uc = curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query,
-                                CURLU_URLDECODE);
-    if(uc == CURLUE_OUT_OF_MEMORY)
-      return CURLE_OUT_OF_MEMORY;
-  }
-
-  /* Any extra stuff at the end of the URL is an error */
-  if(*ptr)
-    return CURLE_URL_MALFORMAT;
-
-  return CURLE_OK;
-}
+const struct Curl_handler Curl_handler_imap = {
+  "imap",                           /* scheme */
+  imap_setup_connection,            /* setup_connection */
+  imap_do,                          /* do_it */
+  imap_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  imap_connect,                     /* connect_it */
+  imap_multi_statemach,             /* connecting */
+  imap_doing,                       /* doing */
+  imap_pollset,                     /* proto_pollset */
+  imap_pollset,                     /* doing_pollset */
+  ZERO_NULL,                        /* domore_pollset */
+  ZERO_NULL,                        /* perform_pollset */
+  imap_disconnect,                  /* disconnect */
+  ZERO_NULL,                        /* write_resp */
+  ZERO_NULL,                        /* write_resp_hd */
+  ZERO_NULL,                        /* connection_check */
+  ZERO_NULL,                        /* attach connection */
+  ZERO_NULL,                        /* follow */
+  PORT_IMAP,                        /* defport */
+  CURLPROTO_IMAP,                   /* protocol */
+  CURLPROTO_IMAP,                   /* family */
+  PROTOPT_CLOSEACTION |             /* flags */
+  PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE |
+  PROTOPT_CONN_REUSE
+};
 
-/***********************************************************************
- *
- * imap_parse_custom_request()
- *
- * Parse the custom request.
+#ifdef USE_SSL
+/*
+ * IMAPS protocol handler.
  */
-static CURLcode imap_parse_custom_request(struct Curl_easy *data,
-                                          struct IMAP *imap)
-{
-  CURLcode result = CURLE_OK;
-  const char *custom = data->set.str[STRING_CUSTOMREQUEST];
-
-  if(custom) {
-    /* URL decode the custom request */
-    result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL);
-
-    /* Extract the parameters if specified */
-    if(!result) {
-      const char *params = imap->custom;
-
-      while(*params && *params != ' ')
-        params++;
-
-      if(*params) {
-        imap->custom_params = curlx_strdup(params);
-        imap->custom[params - imap->custom] = '\0';
-
-        if(!imap->custom_params)
-          result = CURLE_OUT_OF_MEMORY;
-      }
-    }
-  }
-
-  return result;
-}
+const struct Curl_handler Curl_handler_imaps = {
+  "imaps",                          /* scheme */
+  imap_setup_connection,            /* setup_connection */
+  imap_do,                          /* do_it */
+  imap_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  imap_connect,                     /* connect_it */
+  imap_multi_statemach,             /* connecting */
+  imap_doing,                       /* doing */
+  imap_pollset,                     /* proto_pollset */
+  imap_pollset,                     /* doing_pollset */
+  ZERO_NULL,                        /* domore_pollset */
+  ZERO_NULL,                        /* perform_pollset */
+  imap_disconnect,                  /* disconnect */
+  ZERO_NULL,                        /* write_resp */
+  ZERO_NULL,                        /* write_resp_hd */
+  ZERO_NULL,                        /* connection_check */
+  ZERO_NULL,                        /* attach connection */
+  ZERO_NULL,                        /* follow */
+  PORT_IMAPS,                       /* defport */
+  CURLPROTO_IMAPS,                  /* protocol */
+  CURLPROTO_IMAP,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
+  PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE
+};
+#endif
 
 #endif /* CURL_DISABLE_IMAP */
index 8e2c8aa32e55e137f64f93187cf9a7d0f40438ff..122b34a274f8497bc81b1c940e19bb97b9c4ac59 100644 (file)
@@ -135,111 +135,6 @@ struct pop3_conn {
   BIT(tls_supported);     /* StartTLS capability supported by server */
 };
 
-/* Local API functions */
-static CURLcode pop3_regular_transfer(struct Curl_easy *data, bool *done);
-static CURLcode pop3_do(struct Curl_easy *data, bool *done);
-static CURLcode pop3_done(struct Curl_easy *data, CURLcode status,
-                          bool premature);
-static CURLcode pop3_connect(struct Curl_easy *data, bool *done);
-static CURLcode pop3_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn, bool dead);
-static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done);
-static CURLcode pop3_pollset(struct Curl_easy *data,
-                             struct easy_pollset *ps);
-static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done);
-static CURLcode pop3_setup_connection(struct Curl_easy *data,
-                                      struct connectdata *conn);
-static CURLcode pop3_parse_url_options(struct connectdata *conn);
-static CURLcode pop3_parse_url_path(struct Curl_easy *data);
-static CURLcode pop3_parse_custom_request(struct Curl_easy *data);
-static CURLcode pop3_perform_auth(struct Curl_easy *data, const char *mech,
-                                  const struct bufref *initresp);
-static CURLcode pop3_continue_auth(struct Curl_easy *data, const char *mech,
-                                   const struct bufref *resp);
-static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech);
-static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out);
-
-/* This function scans the body after the end-of-body and writes everything
- * until the end is found */
-static CURLcode pop3_write(struct Curl_easy *data,
-                           const char *str, size_t nread, bool is_eos);
-
-/*
- * POP3 protocol handler.
- */
-
-const struct Curl_handler Curl_handler_pop3 = {
-  "pop3",                           /* scheme */
-  pop3_setup_connection,            /* setup_connection */
-  pop3_do,                          /* do_it */
-  pop3_done,                        /* done */
-  ZERO_NULL,                        /* do_more */
-  pop3_connect,                     /* connect_it */
-  pop3_multi_statemach,             /* connecting */
-  pop3_doing,                       /* doing */
-  pop3_pollset,                     /* proto_pollset */
-  pop3_pollset,                     /* doing_pollset */
-  ZERO_NULL,                        /* domore_pollset */
-  ZERO_NULL,                        /* perform_pollset */
-  pop3_disconnect,                  /* disconnect */
-  pop3_write,                       /* write_resp */
-  ZERO_NULL,                        /* write_resp_hd */
-  ZERO_NULL,                        /* connection_check */
-  ZERO_NULL,                        /* attach connection */
-  ZERO_NULL,                        /* follow */
-  PORT_POP3,                        /* defport */
-  CURLPROTO_POP3,                   /* protocol */
-  CURLPROTO_POP3,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */
-    PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE
-};
-
-#ifdef USE_SSL
-/*
- * POP3S protocol handler.
- */
-
-const struct Curl_handler Curl_handler_pop3s = {
-  "pop3s",                          /* scheme */
-  pop3_setup_connection,            /* setup_connection */
-  pop3_do,                          /* do_it */
-  pop3_done,                        /* done */
-  ZERO_NULL,                        /* do_more */
-  pop3_connect,                     /* connect_it */
-  pop3_multi_statemach,             /* connecting */
-  pop3_doing,                       /* doing */
-  pop3_pollset,                     /* proto_pollset */
-  pop3_pollset,                     /* doing_pollset */
-  ZERO_NULL,                        /* domore_pollset */
-  ZERO_NULL,                        /* perform_pollset */
-  pop3_disconnect,                  /* disconnect */
-  pop3_write,                       /* write_resp */
-  ZERO_NULL,                        /* write_resp_hd */
-  ZERO_NULL,                        /* connection_check */
-  ZERO_NULL,                        /* attach connection */
-  ZERO_NULL,                        /* follow */
-  PORT_POP3S,                       /* defport */
-  CURLPROTO_POP3S,                  /* protocol */
-  CURLPROTO_POP3,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
-    PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE
-};
-#endif
-
-/* SASL parameters for the pop3 protocol */
-static const struct SASLproto saslpop3 = {
-  "pop",                /* The service name */
-  pop3_perform_auth,    /* Send authentication command */
-  pop3_continue_auth,   /* Send authentication continuation */
-  pop3_cancel_auth,     /* Send authentication cancellation */
-  pop3_get_message,     /* Get SASL response message */
-  255 - 8,              /* Max line len - strlen("AUTH ") - 1 space - crlf */
-  '*',                  /* Code received when continuation is expected */
-  '+',                  /* Code to receive upon authentication success */
-  SASL_AUTH_DEFAULT,    /* Default mechanisms */
-  SASL_FLAG_BASE64      /* Configuration flags */
-};
-
 struct pop3_cmd {
   const char *name;
   unsigned short nlen;
@@ -268,6 +163,105 @@ static const struct pop3_cmd pop3cmds[] = {
   { "XTND", 4, TRUE, TRUE },
 };
 
+/***********************************************************************
+ *
+ * pop3_parse_url_options()
+ *
+ * Parse the URL login options.
+ */
+static CURLcode pop3_parse_url_options(struct connectdata *conn)
+{
+  struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN);
+  CURLcode result = CURLE_OK;
+  const char *ptr = conn->options;
+
+  if(!pop3c)
+    return CURLE_FAILED_INIT;
+
+  while(!result && ptr && *ptr) {
+    const char *key = ptr;
+    const char *value;
+
+    while(*ptr && *ptr != '=')
+      ptr++;
+
+    value = ptr + 1;
+
+    while(*ptr && *ptr != ';')
+      ptr++;
+
+    if(curl_strnequal(key, "AUTH=", 5)) {
+      result = Curl_sasl_parse_url_auth_option(&pop3c->sasl,
+                                               value, ptr - value);
+
+      if(result && curl_strnequal(value, "+APOP", ptr - value)) {
+        pop3c->preftype = POP3_TYPE_APOP;
+        pop3c->sasl.prefmech = SASL_AUTH_NONE;
+        result = CURLE_OK;
+      }
+    }
+    else
+      result = CURLE_URL_MALFORMAT;
+
+    if(*ptr == ';')
+      ptr++;
+  }
+
+  if(pop3c->preftype != POP3_TYPE_APOP)
+    switch(pop3c->sasl.prefmech) {
+    case SASL_AUTH_NONE:
+      pop3c->preftype = POP3_TYPE_NONE;
+      break;
+    case SASL_AUTH_DEFAULT:
+      pop3c->preftype = POP3_TYPE_ANY;
+      break;
+    default:
+      pop3c->preftype = POP3_TYPE_SASL;
+      break;
+    }
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * pop3_parse_url_path()
+ *
+ * Parse the URL path into separate path components.
+ */
+static CURLcode pop3_parse_url_path(struct Curl_easy *data)
+{
+  /* The POP3 struct is already initialised in pop3_connect() */
+  struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY);
+  const char *path = &data->state.up.path[1]; /* skip leading path */
+
+  if(!pop3)
+    return CURLE_FAILED_INIT;
+  /* URL decode the path for the message ID */
+  return Curl_urldecode(path, 0, &pop3->id, NULL, REJECT_CTRL);
+}
+
+/***********************************************************************
+ *
+ * pop3_parse_custom_request()
+ *
+ * Parse the custom request.
+ */
+static CURLcode pop3_parse_custom_request(struct Curl_easy *data)
+{
+  CURLcode result = CURLE_OK;
+  struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY);
+  const char *custom = data->set.str[STRING_CUSTOMREQUEST];
+
+  if(!pop3)
+    return CURLE_FAILED_INIT;
+  /* URL decode the custom request */
+  if(custom)
+    result = Curl_urldecode(custom, 0, &pop3->custom, NULL, REJECT_CTRL);
+
+  return result;
+}
+
 /* Return iff a command is defined as "multi-line" (RFC 1939),
  * has a response terminated by a last line with a '.'.
  */
@@ -1070,59 +1064,199 @@ static CURLcode pop3_state_pass_resp(struct Curl_easy *data, int pop3code,
   return result;
 }
 
-/* For command responses */
-static CURLcode pop3_state_command_resp(struct Curl_easy *data,
-                                        int pop3code,
-                                        pop3state instate)
+/***********************************************************************
+ *
+ * pop3_write()
+ *
+ * This function scans the body after the end-of-body and writes everything
+ * until the end is found.
+ */
+static CURLcode pop3_write(struct Curl_easy *data, const char *str,
+                           size_t nread, bool is_eos)
 {
+  /* This code could be made into a special function in the handler struct */
   CURLcode result = CURLE_OK;
+  struct SingleRequest *k = &data->req;
   struct connectdata *conn = data->conn;
-  struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY);
   struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN);
-  struct pingpong *pp;
+  bool strip_dot = FALSE;
+  size_t last = 0;
+  size_t i;
+  (void)is_eos;
 
-  (void)instate;
-  if(!pop3 || !pop3c)
+  if(!pop3c)
     return CURLE_FAILED_INIT;
 
-  pp = &pop3c->pp;
-  if(pop3code != '+') {
-    pop3_state(data, POP3_STOP);
-    return CURLE_WEIRD_SERVER_REPLY;
-  }
-
-  /* This 'OK' line ends with a CR LF pair which is the two first bytes of the
-     EOB string so count this is two matching bytes. This is necessary to make
-     the code detect the EOB if the only data than comes now is %2e CR LF like
-     when there is no body to return. */
-  pop3c->eob = 2;
-
-  /* But since this initial CR LF pair is not part of the actual body, we set
-     the strip counter here so that these bytes will not be delivered. */
-  pop3c->strip = 2;
+  /* Search through the buffer looking for the end-of-body marker which is
+     5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches
+     the eob so the server will have prefixed it with an extra dot which we
+     need to strip out. Additionally the marker could of course be spread out
+     over 5 different data chunks. */
+  for(i = 0; i < nread; i++) {
+    size_t prev = pop3c->eob;
 
-  if(pop3->transfer == PPTRANSFER_BODY) {
-    /* POP3 download */
-    Curl_xfer_setup_recv(data, FIRSTSOCKET, -1);
+    switch(str[i]) {
+    case 0x0d:
+      if(pop3c->eob == 0) {
+        pop3c->eob++;
 
-    if(pp->overflow) {
-      /* The recv buffer contains data that is actually body content so send
-         it as such. Note that there may even be additional "headers" after
-         the body */
+        if(i) {
+          /* Write out the body part that did not match */
+          result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last],
+                                     i - last);
 
-      /* keep only the overflow */
-      curlx_dyn_tail(&pp->recvbuf, pp->overflow);
-      pp->nfinal = 0; /* done */
+          if(result)
+            return result;
 
-      if(!data->req.no_body) {
-        result = pop3_write(data, curlx_dyn_ptr(&pp->recvbuf),
-                            curlx_dyn_len(&pp->recvbuf), FALSE);
-        if(result)
-          return result;
+          last = i;
+        }
       }
-
-      /* reset the buffer */
-      curlx_dyn_reset(&pp->recvbuf);
+      else if(pop3c->eob == 3)
+        pop3c->eob++;
+      else
+        /* If the character match was not at position 0 or 3 then restart the
+           pattern matching */
+        pop3c->eob = 1;
+      break;
+
+    case 0x0a:
+      if(pop3c->eob == 1 || pop3c->eob == 4)
+        pop3c->eob++;
+      else
+        /* If the character match was not at position 1 or 4 then start the
+           search again */
+        pop3c->eob = 0;
+      break;
+
+    case 0x2e:
+      if(pop3c->eob == 2)
+        pop3c->eob++;
+      else if(pop3c->eob == 3) {
+        /* We have an extra dot after the CRLF which we need to strip off */
+        strip_dot = TRUE;
+        pop3c->eob = 0;
+      }
+      else
+        /* If the character match was not at position 2 then start the search
+           again */
+        pop3c->eob = 0;
+      break;
+
+    default:
+      pop3c->eob = 0;
+      break;
+    }
+
+    /* Did we have a partial match which has subsequently failed? */
+    if(prev && prev >= pop3c->eob) {
+      /* Strip can only be non-zero for the first mismatch after CRLF and
+         then both prev and strip are equal and nothing will be output below */
+      while(prev && pop3c->strip) {
+        prev--;
+        pop3c->strip--;
+      }
+
+      if(prev) {
+        /* If the partial match was the CRLF and dot then only write the CRLF
+           as the server would have inserted the dot */
+        if(strip_dot && prev - 1 > 0) {
+          result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB,
+                                     prev - 1);
+        }
+        else if(!strip_dot) {
+          result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB,
+                                     prev);
+        }
+        else {
+          result = CURLE_OK;
+        }
+
+        if(result)
+          return result;
+
+        last = i;
+        strip_dot = FALSE;
+      }
+    }
+  }
+
+  if(pop3c->eob == POP3_EOB_LEN) {
+    /* We have a full match so the transfer is done, however we must transfer
+    the CRLF at the start of the EOB as this is considered to be part of the
+    message as per RFC-1939, sect. 3 */
+    result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, 2);
+
+    k->keepon &= ~KEEP_RECV;
+    pop3c->eob = 0;
+
+    return result;
+  }
+
+  if(pop3c->eob)
+    /* While EOB is matching nothing should be output */
+    return CURLE_OK;
+
+  if(nread - last) {
+    result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last],
+                               nread - last);
+  }
+
+  return result;
+}
+
+/* For command responses */
+static CURLcode pop3_state_command_resp(struct Curl_easy *data,
+                                        int pop3code,
+                                        pop3state instate)
+{
+  CURLcode result = CURLE_OK;
+  struct connectdata *conn = data->conn;
+  struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY);
+  struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN);
+  struct pingpong *pp;
+
+  (void)instate;
+  if(!pop3 || !pop3c)
+    return CURLE_FAILED_INIT;
+
+  pp = &pop3c->pp;
+  if(pop3code != '+') {
+    pop3_state(data, POP3_STOP);
+    return CURLE_WEIRD_SERVER_REPLY;
+  }
+
+  /* This 'OK' line ends with a CR LF pair which is the two first bytes of the
+     EOB string so count this is two matching bytes. This is necessary to make
+     the code detect the EOB if the only data than comes now is %2e CR LF like
+     when there is no body to return. */
+  pop3c->eob = 2;
+
+  /* But since this initial CR LF pair is not part of the actual body, we set
+     the strip counter here so that these bytes will not be delivered. */
+  pop3c->strip = 2;
+
+  if(pop3->transfer == PPTRANSFER_BODY) {
+    /* POP3 download */
+    Curl_xfer_setup_recv(data, FIRSTSOCKET, -1);
+
+    if(pp->overflow) {
+      /* The recv buffer contains data that is actually body content so send
+         it as such. Note that there may even be additional "headers" after
+         the body */
+
+      /* keep only the overflow */
+      curlx_dyn_tail(&pp->recvbuf, pp->overflow);
+      pp->nfinal = 0; /* done */
+
+      if(!data->req.no_body) {
+        result = pop3_write(data, curlx_dyn_ptr(&pp->recvbuf),
+                            curlx_dyn_len(&pp->recvbuf), FALSE);
+        if(result)
+          return result;
+      }
+
+      /* reset the buffer */
+      curlx_dyn_reset(&pp->recvbuf);
       pp->overflow = 0;
     }
   }
@@ -1264,6 +1398,20 @@ static CURLcode pop3_pollset(struct Curl_easy *data,
   return pop3c ? Curl_pp_pollset(data, &pop3c->pp, ps) : CURLE_OK;
 }
 
+/* SASL parameters for the pop3 protocol */
+static const struct SASLproto saslpop3 = {
+  "pop",                /* The service name */
+  pop3_perform_auth,    /* Send authentication command */
+  pop3_continue_auth,   /* Send authentication continuation */
+  pop3_cancel_auth,     /* Send authentication cancellation */
+  pop3_get_message,     /* Get SASL response message */
+  255 - 8,              /* Max line len - strlen("AUTH ") - 1 space - crlf */
+  '*',                  /* Code received when continuation is expected */
+  '+',                  /* Code to receive upon authentication success */
+  SASL_AUTH_DEFAULT,    /* Default mechanisms */
+  SASL_FLAG_BASE64      /* Configuration flags */
+};
+
 /***********************************************************************
  *
  * pop3_connect()
@@ -1382,6 +1530,46 @@ static CURLcode pop3_perform(struct Curl_easy *data, bool *connected,
   return result;
 }
 
+/* Call this when the DO phase has completed */
+static CURLcode pop3_dophase_done(struct Curl_easy *data, bool connected)
+{
+  (void)data;
+  (void)connected;
+
+  return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * pop3_regular_transfer()
+ *
+ * The input argument is already checked for validity.
+ *
+ * Performs all commands done before a regular transfer between a local and a
+ * remote host.
+ */
+static CURLcode pop3_regular_transfer(struct Curl_easy *data,
+                                      bool *dophase_done)
+{
+  CURLcode result = CURLE_OK;
+  bool connected = FALSE;
+
+  /* Make sure size is unknown at this point */
+  data->req.size = -1;
+
+  /* Set the progress data */
+  Curl_pgrsReset(data);
+
+  /* Carry out the perform */
+  result = pop3_perform(data, &connected, dophase_done);
+
+  /* Perform post DO phase operations if necessary */
+  if(!result && *dophase_done)
+    result = pop3_dophase_done(data, connected);
+
+  return result;
+}
+
 /***********************************************************************
  *
  * pop3_do()
@@ -1446,15 +1634,6 @@ static CURLcode pop3_disconnect(struct Curl_easy *data,
   return CURLE_OK;
 }
 
-/* Call this when the DO phase has completed */
-static CURLcode pop3_dophase_done(struct Curl_easy *data, bool connected)
-{
-  (void)data;
-  (void)connected;
-
-  return CURLE_OK;
-}
-
 /* Called from multi.c while DOing */
 static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done)
 {
@@ -1471,37 +1650,6 @@ static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done)
   return result;
 }
 
-/***********************************************************************
- *
- * pop3_regular_transfer()
- *
- * The input argument is already checked for validity.
- *
- * Performs all commands done before a regular transfer between a local and a
- * remote host.
- */
-static CURLcode pop3_regular_transfer(struct Curl_easy *data,
-                                      bool *dophase_done)
-{
-  CURLcode result = CURLE_OK;
-  bool connected = FALSE;
-
-  /* Make sure size is unknown at this point */
-  data->req.size = -1;
-
-  /* Set the progress data */
-  Curl_pgrsReset(data);
-
-  /* Carry out the perform */
-  result = pop3_perform(data, &connected, dophase_done);
-
-  /* Perform post DO phase operations if necessary */
-  if(!result && *dophase_done)
-    result = pop3_dophase_done(data, connected);
-
-  return result;
-}
-
 static void pop3_easy_dtor(void *key, size_t klen, void *entry)
 {
   struct POP3 *pop3 = entry;
@@ -1542,243 +1690,64 @@ static CURLcode pop3_setup_connection(struct Curl_easy *data,
   return CURLE_OK;
 }
 
-/***********************************************************************
- *
- * pop3_parse_url_options()
- *
- * Parse the URL login options.
+/*
+ * POP3 protocol handler.
  */
-static CURLcode pop3_parse_url_options(struct connectdata *conn)
-{
-  struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN);
-  CURLcode result = CURLE_OK;
-  const char *ptr = conn->options;
-
-  if(!pop3c)
-    return CURLE_FAILED_INIT;
-
-  while(!result && ptr && *ptr) {
-    const char *key = ptr;
-    const char *value;
-
-    while(*ptr && *ptr != '=')
-      ptr++;
-
-    value = ptr + 1;
-
-    while(*ptr && *ptr != ';')
-      ptr++;
-
-    if(curl_strnequal(key, "AUTH=", 5)) {
-      result = Curl_sasl_parse_url_auth_option(&pop3c->sasl,
-                                               value, ptr - value);
-
-      if(result && curl_strnequal(value, "+APOP", ptr - value)) {
-        pop3c->preftype = POP3_TYPE_APOP;
-        pop3c->sasl.prefmech = SASL_AUTH_NONE;
-        result = CURLE_OK;
-      }
-    }
-    else
-      result = CURLE_URL_MALFORMAT;
-
-    if(*ptr == ';')
-      ptr++;
-  }
-
-  if(pop3c->preftype != POP3_TYPE_APOP)
-    switch(pop3c->sasl.prefmech) {
-    case SASL_AUTH_NONE:
-      pop3c->preftype = POP3_TYPE_NONE;
-      break;
-    case SASL_AUTH_DEFAULT:
-      pop3c->preftype = POP3_TYPE_ANY;
-      break;
-    default:
-      pop3c->preftype = POP3_TYPE_SASL;
-      break;
-    }
-
-  return result;
-}
-
-/***********************************************************************
- *
- * pop3_parse_url_path()
- *
- * Parse the URL path into separate path components.
- */
-static CURLcode pop3_parse_url_path(struct Curl_easy *data)
-{
-  /* The POP3 struct is already initialised in pop3_connect() */
-  struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY);
-  const char *path = &data->state.up.path[1]; /* skip leading path */
-
-  if(!pop3)
-    return CURLE_FAILED_INIT;
-  /* URL decode the path for the message ID */
-  return Curl_urldecode(path, 0, &pop3->id, NULL, REJECT_CTRL);
-}
-
-/***********************************************************************
- *
- * pop3_parse_custom_request()
- *
- * Parse the custom request.
- */
-static CURLcode pop3_parse_custom_request(struct Curl_easy *data)
-{
-  CURLcode result = CURLE_OK;
-  struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY);
-  const char *custom = data->set.str[STRING_CUSTOMREQUEST];
-
-  if(!pop3)
-    return CURLE_FAILED_INIT;
-  /* URL decode the custom request */
-  if(custom)
-    result = Curl_urldecode(custom, 0, &pop3->custom, NULL, REJECT_CTRL);
-
-  return result;
-}
+const struct Curl_handler Curl_handler_pop3 = {
+  "pop3",                           /* scheme */
+  pop3_setup_connection,            /* setup_connection */
+  pop3_do,                          /* do_it */
+  pop3_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  pop3_connect,                     /* connect_it */
+  pop3_multi_statemach,             /* connecting */
+  pop3_doing,                       /* doing */
+  pop3_pollset,                     /* proto_pollset */
+  pop3_pollset,                     /* doing_pollset */
+  ZERO_NULL,                        /* domore_pollset */
+  ZERO_NULL,                        /* perform_pollset */
+  pop3_disconnect,                  /* disconnect */
+  pop3_write,                       /* write_resp */
+  ZERO_NULL,                        /* write_resp_hd */
+  ZERO_NULL,                        /* connection_check */
+  ZERO_NULL,                        /* attach connection */
+  ZERO_NULL,                        /* follow */
+  PORT_POP3,                        /* defport */
+  CURLPROTO_POP3,                   /* protocol */
+  CURLPROTO_POP3,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */
+    PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE
+};
 
-/***********************************************************************
- *
- * pop3_write()
- *
- * This function scans the body after the end-of-body and writes everything
- * until the end is found.
+#ifdef USE_SSL
+/*
+ * POP3S protocol handler.
  */
-static CURLcode pop3_write(struct Curl_easy *data, const char *str,
-                           size_t nread, bool is_eos)
-{
-  /* This code could be made into a special function in the handler struct */
-  CURLcode result = CURLE_OK;
-  struct SingleRequest *k = &data->req;
-  struct connectdata *conn = data->conn;
-  struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN);
-  bool strip_dot = FALSE;
-  size_t last = 0;
-  size_t i;
-  (void)is_eos;
-
-  if(!pop3c)
-    return CURLE_FAILED_INIT;
-
-  /* Search through the buffer looking for the end-of-body marker which is
-     5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches
-     the eob so the server will have prefixed it with an extra dot which we
-     need to strip out. Additionally the marker could of course be spread out
-     over 5 different data chunks. */
-  for(i = 0; i < nread; i++) {
-    size_t prev = pop3c->eob;
-
-    switch(str[i]) {
-    case 0x0d:
-      if(pop3c->eob == 0) {
-        pop3c->eob++;
-
-        if(i) {
-          /* Write out the body part that did not match */
-          result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last],
-                                     i - last);
-
-          if(result)
-            return result;
-
-          last = i;
-        }
-      }
-      else if(pop3c->eob == 3)
-        pop3c->eob++;
-      else
-        /* If the character match was not at position 0 or 3 then restart the
-           pattern matching */
-        pop3c->eob = 1;
-      break;
-
-    case 0x0a:
-      if(pop3c->eob == 1 || pop3c->eob == 4)
-        pop3c->eob++;
-      else
-        /* If the character match was not at position 1 or 4 then start the
-           search again */
-        pop3c->eob = 0;
-      break;
-
-    case 0x2e:
-      if(pop3c->eob == 2)
-        pop3c->eob++;
-      else if(pop3c->eob == 3) {
-        /* We have an extra dot after the CRLF which we need to strip off */
-        strip_dot = TRUE;
-        pop3c->eob = 0;
-      }
-      else
-        /* If the character match was not at position 2 then start the search
-           again */
-        pop3c->eob = 0;
-      break;
-
-    default:
-      pop3c->eob = 0;
-      break;
-    }
-
-    /* Did we have a partial match which has subsequently failed? */
-    if(prev && prev >= pop3c->eob) {
-      /* Strip can only be non-zero for the first mismatch after CRLF and
-         then both prev and strip are equal and nothing will be output below */
-      while(prev && pop3c->strip) {
-        prev--;
-        pop3c->strip--;
-      }
-
-      if(prev) {
-        /* If the partial match was the CRLF and dot then only write the CRLF
-           as the server would have inserted the dot */
-        if(strip_dot && prev - 1 > 0) {
-          result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB,
-                                     prev - 1);
-        }
-        else if(!strip_dot) {
-          result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB,
-                                     prev);
-        }
-        else {
-          result = CURLE_OK;
-        }
-
-        if(result)
-          return result;
-
-        last = i;
-        strip_dot = FALSE;
-      }
-    }
-  }
-
-  if(pop3c->eob == POP3_EOB_LEN) {
-    /* We have a full match so the transfer is done, however we must transfer
-    the CRLF at the start of the EOB as this is considered to be part of the
-    message as per RFC-1939, sect. 3 */
-    result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, 2);
-
-    k->keepon &= ~KEEP_RECV;
-    pop3c->eob = 0;
-
-    return result;
-  }
-
-  if(pop3c->eob)
-    /* While EOB is matching nothing should be output */
-    return CURLE_OK;
-
-  if(nread - last) {
-    result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last],
-                               nread - last);
-  }
-
-  return result;
-}
+const struct Curl_handler Curl_handler_pop3s = {
+  "pop3s",                          /* scheme */
+  pop3_setup_connection,            /* setup_connection */
+  pop3_do,                          /* do_it */
+  pop3_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  pop3_connect,                     /* connect_it */
+  pop3_multi_statemach,             /* connecting */
+  pop3_doing,                       /* doing */
+  pop3_pollset,                     /* proto_pollset */
+  pop3_pollset,                     /* doing_pollset */
+  ZERO_NULL,                        /* domore_pollset */
+  ZERO_NULL,                        /* perform_pollset */
+  pop3_disconnect,                  /* disconnect */
+  pop3_write,                       /* write_resp */
+  ZERO_NULL,                        /* write_resp_hd */
+  ZERO_NULL,                        /* connection_check */
+  ZERO_NULL,                        /* attach connection */
+  ZERO_NULL,                        /* follow */
+  PORT_POP3S,                       /* defport */
+  CURLPROTO_POP3S,                  /* protocol */
+  CURLPROTO_POP3,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
+    PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE
+};
+#endif
 
 #endif /* CURL_DISABLE_POP3 */
index f3f3fe52d58856fd16a0c4335034d586d5a1e9c2..431113550d1c4b90cd7d76ac201f68146c71cffe 100644 (file)
@@ -136,163 +136,38 @@ struct SMTP {
   BIT(trailing_crlf);      /* Specifies if the trailing CRLF is present */
 };
 
-/* Local API functions */
-static CURLcode smtp_regular_transfer(struct Curl_easy *data,
-                                      struct smtp_conn *smtpc,
-                                      struct SMTP *smtp,
-                                      bool *done);
-static CURLcode smtp_do(struct Curl_easy *data, bool *done);
-static CURLcode smtp_done(struct Curl_easy *data, CURLcode status,
-                          bool premature);
-static CURLcode smtp_connect(struct Curl_easy *data, bool *done);
-static CURLcode smtp_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn, bool dead);
-static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done);
-static CURLcode smtp_pollset(struct Curl_easy *data,
-                             struct easy_pollset *ps);
-static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done);
-static CURLcode smtp_setup_connection(struct Curl_easy *data,
-                                      struct connectdata *conn);
-static CURLcode smtp_parse_url_options(struct connectdata *conn,
-                                       struct smtp_conn *smtpc);
-static CURLcode smtp_parse_url_path(struct Curl_easy *data,
-                                    struct smtp_conn *smtpc);
-static CURLcode smtp_parse_custom_request(struct Curl_easy *data,
-                                          struct SMTP *smtp);
-static CURLcode smtp_parse_address(const char *fqma,
-                                   char **address, struct hostname *host,
-                                   const char **suffix);
-static CURLcode smtp_perform_auth(struct Curl_easy *data, const char *mech,
-                                  const struct bufref *initresp);
-static CURLcode smtp_continue_auth(struct Curl_easy *data, const char *mech,
-                                   const struct bufref *resp);
-static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech);
-static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out);
-static CURLcode cr_eob_add(struct Curl_easy *data);
-
-/*
- * SMTP protocol handler.
- */
-
-const struct Curl_handler Curl_handler_smtp = {
-  "smtp",                           /* scheme */
-  smtp_setup_connection,            /* setup_connection */
-  smtp_do,                          /* do_it */
-  smtp_done,                        /* done */
-  ZERO_NULL,                        /* do_more */
-  smtp_connect,                     /* connect_it */
-  smtp_multi_statemach,             /* connecting */
-  smtp_doing,                       /* doing */
-  smtp_pollset,                     /* proto_pollset */
-  smtp_pollset,                     /* doing_pollset */
-  ZERO_NULL,                        /* domore_pollset */
-  ZERO_NULL,                        /* perform_pollset */
-  smtp_disconnect,                  /* disconnect */
-  ZERO_NULL,                        /* write_resp */
-  ZERO_NULL,                        /* write_resp_hd */
-  ZERO_NULL,                        /* connection_check */
-  ZERO_NULL,                        /* attach connection */
-  ZERO_NULL,                        /* follow */
-  PORT_SMTP,                        /* defport */
-  CURLPROTO_SMTP,                   /* protocol */
-  CURLPROTO_SMTP,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */
-    PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE
-};
-
-#ifdef USE_SSL
-/*
- * SMTPS protocol handler.
- */
-
-const struct Curl_handler Curl_handler_smtps = {
-  "smtps",                          /* scheme */
-  smtp_setup_connection,            /* setup_connection */
-  smtp_do,                          /* do_it */
-  smtp_done,                        /* done */
-  ZERO_NULL,                        /* do_more */
-  smtp_connect,                     /* connect_it */
-  smtp_multi_statemach,             /* connecting */
-  smtp_doing,                       /* doing */
-  smtp_pollset,                     /* proto_pollset */
-  smtp_pollset,                     /* doing_pollset */
-  ZERO_NULL,                        /* domore_pollset */
-  ZERO_NULL,                        /* perform_pollset */
-  smtp_disconnect,                  /* disconnect */
-  ZERO_NULL,                        /* write_resp */
-  ZERO_NULL,                        /* write_resp_hd */
-  ZERO_NULL,                        /* connection_check */
-  ZERO_NULL,                        /* attach connection */
-  ZERO_NULL,                        /* follow */
-  PORT_SMTPS,                       /* defport */
-  CURLPROTO_SMTPS,                  /* protocol */
-  CURLPROTO_SMTP,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
-    PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE
-};
-#endif
-
-/* SASL parameters for the smtp protocol */
-static const struct SASLproto saslsmtp = {
-  "smtp",               /* The service name */
-  smtp_perform_auth,    /* Send authentication command */
-  smtp_continue_auth,   /* Send authentication continuation */
-  smtp_cancel_auth,     /* Cancel authentication */
-  smtp_get_message,     /* Get SASL response message */
-  512 - 8,              /* Max line len - strlen("AUTH ") - 1 space - crlf */
-  334,                  /* Code received when continuation is expected */
-  235,                  /* Code to receive upon authentication success */
-  SASL_AUTH_DEFAULT,    /* Default mechanisms */
-  SASL_FLAG_BASE64      /* Configuration flags */
-};
-
 /***********************************************************************
  *
- * smtp_endofresp()
+ * smtp_parse_url_options()
  *
- * Checks for an ending SMTP status code at the start of the given string, but
- * also detects various capabilities from the EHLO response including the
- * supported authentication mechanisms.
+ * Parse the URL login options.
  */
-static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn,
-                           const char *line, size_t len, int *resp)
+static CURLcode smtp_parse_url_options(struct connectdata *conn,
+                                       struct smtp_conn *smtpc)
 {
-  struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN);
-  bool result = FALSE;
-  (void)data;
+  CURLcode result = CURLE_OK;
+  const char *ptr = conn->options;
 
-  DEBUGASSERT(smtpc);
-  if(!smtpc)
-    return FALSE;
+  while(!result && ptr && *ptr) {
+    const char *key = ptr;
+    const char *value;
 
-  /* Nothing for us */
-  if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2]))
-    return FALSE;
+    while(*ptr && *ptr != '=')
+      ptr++;
 
-  /* Do we have a command response? This should be the response code followed
-     by a space and optionally some text as per RFC-5321 and as outlined in
-     Section 4. Examples of RFC-4954 but some email servers ignore this and
-     only send the response code instead as per Section 4.2. */
-  if(line[3] == ' ' || len == 5) {
-    char tmpline[6];
-    curl_off_t code;
-    const char *p = tmpline;
-    result = TRUE;
-    memcpy(tmpline, line, (len == 5 ? 5 : 3));
-    tmpline[len == 5 ? 5 : 3] = 0;
-    if(curlx_str_number(&p, &code, len == 5 ? 99999 : 999))
-      return FALSE;
-    *resp = (int)code;
+    value = ptr + 1;
 
-    /* Make sure real server never sends internal value */
-    if(*resp == 1)
-      *resp = 0;
-  }
-  /* Do we have a multiline (continuation) response? */
-  else if(line[3] == '-' &&
-          (smtpc->state == SMTP_EHLO || smtpc->state == SMTP_COMMAND)) {
-    result = TRUE;
-    *resp = 1;  /* Internal response code */
+    while(*ptr && *ptr != ';')
+      ptr++;
+
+    if(curl_strnequal(key, "AUTH=", 5))
+      result = Curl_sasl_parse_url_auth_option(&smtpc->sasl,
+                                               value, ptr - value);
+    else
+      result = CURLE_URL_MALFORMAT;
+
+    if(*ptr == ';')
+      ptr++;
   }
 
   return result;
@@ -300,1785 +175,1874 @@ static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn,
 
 /***********************************************************************
  *
- * smtp_get_message()
+ * smtp_parse_url_path()
  *
- * Gets the authentication message from the response buffer.
+ * Parse the URL path into separate path components.
  */
-static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out)
+static CURLcode smtp_parse_url_path(struct Curl_easy *data,
+                                    struct smtp_conn *smtpc)
 {
-  struct smtp_conn *smtpc =
-    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
-  char *message;
-  size_t len;
-
-  if(!smtpc)
-    return CURLE_FAILED_INIT;
-
-  message = curlx_dyn_ptr(&smtpc->pp.recvbuf);
-  len = smtpc->pp.nfinal;
-  if(len > 4) {
-    /* Find the start of the message */
-    len -= 4;
-    for(message += 4; *message == ' ' || *message == '\t'; message++, len--)
-      ;
-
-    /* Find the end of the message */
-    while(len--)
-      if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' &&
-         message[len] != '\t')
-        break;
+  /* The SMTP struct is already initialised in smtp_connect() */
+  const char *path = &data->state.up.path[1]; /* skip leading path */
+  char localhost[HOSTNAME_MAX + 1];
 
-    /* Terminate the message */
-    message[++len] = '\0';
-    Curl_bufref_set(out, message, len, NULL);
+  /* Calculate the path if necessary */
+  if(!*path) {
+    if(!Curl_gethostname(localhost, sizeof(localhost)))
+      path = localhost;
+    else
+      path = "localhost";
   }
-  else
-    /* junk input => zero length output */
-    Curl_bufref_set(out, "", 0, NULL);
 
-  return CURLE_OK;
+  /* URL decode the path and use it as the domain in our EHLO */
+  return Curl_urldecode(path, 0, &smtpc->domain, NULL, REJECT_CTRL);
 }
 
 /***********************************************************************
  *
- * smtp_state()
+ * smtp_parse_custom_request()
  *
- * This is the ONLY way to change SMTP state!
+ * Parse the custom request.
  */
-static void smtp_state(struct Curl_easy *data,
-                       struct smtp_conn *smtpc,
-                       smtpstate newstate)
+static CURLcode smtp_parse_custom_request(struct Curl_easy *data,
+                                          struct SMTP *smtp)
 {
-#ifndef CURL_DISABLE_VERBOSE_STRINGS
-  /* for debug purposes */
-  static const char * const names[] = {
-    "STOP",
-    "SERVERGREET",
-    "EHLO",
-    "HELO",
-    "STARTTLS",
-    "UPGRADETLS",
-    "AUTH",
-    "COMMAND",
-    "MAIL",
-    "RCPT",
-    "DATA",
-    "POSTDATA",
-    "QUIT",
-    /* LAST */
-  };
+  CURLcode result = CURLE_OK;
+  const char *custom = data->set.str[STRING_CUSTOMREQUEST];
 
-  if(smtpc->state != newstate)
-    CURL_TRC_SMTP(data, "state change from %s to %s",
-                  names[smtpc->state], names[newstate]);
-#else
-  (void)data;
-#endif
+  /* URL decode the custom request */
+  if(custom)
+    result = Curl_urldecode(custom, 0, &smtp->custom, NULL, REJECT_CTRL);
 
-  smtpc->state = newstate;
+  return result;
 }
 
 /***********************************************************************
  *
- * smtp_perform_ehlo()
+ * smtp_parse_address()
  *
- * Sends the EHLO command to not only initialise communication with the ESMTP
- * server but to also obtain a list of server side supported capabilities.
- */
-static CURLcode smtp_perform_ehlo(struct Curl_easy *data,
-                                  struct smtp_conn *smtpc)
-{
-  CURLcode result = CURLE_OK;
-
-  smtpc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanism yet */
-  smtpc->sasl.authused = SASL_AUTH_NONE;  /* Clear the authentication mechanism
-                                             used for esmtp connections */
-  smtpc->tls_supported = FALSE;           /* Clear the TLS capability */
-  smtpc->auth_supported = FALSE;          /* Clear the AUTH capability */
-
-  /* Send the EHLO command */
-  result = Curl_pp_sendf(data, &smtpc->pp, "EHLO %s", smtpc->domain);
-
-  if(!result)
-    smtp_state(data, smtpc, SMTP_EHLO);
-
-  return result;
-}
-
-/***********************************************************************
+ * Parse the fully qualified mailbox address into a local address part and the
+ * hostname, converting the hostname to an IDN A-label, as per RFC-5890, if
+ * necessary.
  *
- * smtp_perform_helo()
+ * Parameters:
  *
- * Sends the HELO command to initialise communication with the SMTP server.
- */
-static CURLcode smtp_perform_helo(struct Curl_easy *data,
-                                  struct smtp_conn *smtpc)
-{
-  CURLcode result = CURLE_OK;
-
-  smtpc->sasl.authused = SASL_AUTH_NONE; /* No authentication mechanism used
-                                            in smtp connections */
-
-  /* Send the HELO command */
-  result = Curl_pp_sendf(data, &smtpc->pp, "HELO %s", smtpc->domain);
-
-  if(!result)
-    smtp_state(data, smtpc, SMTP_HELO);
-
-  return result;
-}
-
-/***********************************************************************
+ * conn  [in]              - The connection handle.
+ * fqma  [in]              - The fully qualified mailbox address (which may or
+ *                           may not contain UTF-8 characters).
+ * address        [in/out] - A new allocated buffer which holds the local
+ *                           address part of the mailbox. This buffer must be
+ *                           free'ed by the caller.
+ * host           [in/out] - The hostname structure that holds the original,
+ *                           and optionally encoded, hostname.
+ *                           Curl_free_idnconverted_hostname() must be called
+ *                           once the caller has finished with the structure.
  *
- * smtp_perform_starttls()
+ * Returns CURLE_OK on success.
  *
- * Sends the STLS command to start the upgrade to TLS.
- */
-static CURLcode smtp_perform_starttls(struct Curl_easy *data,
-                                      struct smtp_conn *smtpc)
-{
-  /* Send the STARTTLS command */
-  CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "STARTTLS");
-
-  if(!result)
-    smtp_state(data, smtpc, SMTP_STARTTLS);
-
-  return result;
-}
-
-/***********************************************************************
+ * Notes:
  *
- * smtp_perform_upgrade_tls()
+ * Should a UTF-8 hostname require conversion to IDN ACE and we cannot honor
+ * that conversion then we shall return success. This allow the caller to send
+ * the data to the server as a U-label (as per RFC-6531 sect. 3.2).
  *
- * Performs the upgrade to TLS.
+ * If an mailbox '@' separator cannot be located then the mailbox is considered
+ * to be either a local mailbox or an invalid mailbox (depending on what the
+ * calling function deems it to be) then the input will simply be returned in
+ * the address part with the hostname being NULL.
  */
-static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data,
-                                         struct smtp_conn *smtpc)
+static CURLcode smtp_parse_address(const char *fqma, char **address,
+                                   struct hostname *host, const char **suffix)
 {
-#ifdef USE_SSL
-  /* Start the SSL connection */
-  struct connectdata *conn = data->conn;
-  CURLcode result;
-  bool ssldone = FALSE;
+  CURLcode result = CURLE_OK;
+  size_t length;
+  char *addressend;
 
-  DEBUGASSERT(smtpc->state == SMTP_UPGRADETLS);
-  if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
-    result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
-    if(result)
-      goto out;
-    /* Change the connection handler and SMTP state */
-    conn->handler = &Curl_handler_smtps;
-  }
+  /* Duplicate the fully qualified email address so we can manipulate it,
+     ensuring it does not contain the delimiters if specified */
+  char *dup = curlx_strdup(fqma[0] == '<' ? fqma + 1 : fqma);
+  if(!dup)
+    return CURLE_OUT_OF_MEMORY;
 
-  DEBUGASSERT(!smtpc->ssldone);
-  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
-  DEBUGF(infof(data, "smtp_perform_upgrade_tls, connect -> %d, %d",
-           result, ssldone));
-  if(!result && ssldone) {
-    smtpc->ssldone = ssldone;
-    /* perform EHLO now, changes smtp->state out of SMTP_UPGRADETLS */
-    result = smtp_perform_ehlo(data, smtpc);
+  if(fqma[0] != '<') {
+    length = strlen(dup);
+    if(length) {
+      if(dup[length - 1] == '>')
+        dup[length - 1] = '\0';
+    }
+  }
+  else {
+    addressend = strrchr(dup, '>');
+    if(addressend) {
+      *addressend = '\0';
+      *suffix = addressend + 1;
+    }
   }
-out:
-  return result;
-#else
-  (void)data;
-  (void)smtpc;
-  return CURLE_NOT_BUILT_IN;
-#endif
-}
 
-/***********************************************************************
- *
- * smtp_perform_auth()
- *
- * Sends an AUTH command allowing the client to login with the given SASL
- * authentication mechanism.
- */
-static CURLcode smtp_perform_auth(struct Curl_easy *data,
-                                  const char *mech,
-                                  const struct bufref *initresp)
-{
-  CURLcode result = CURLE_OK;
-  struct smtp_conn *smtpc =
-    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
-  const char *ir = Curl_bufref_ptr(initresp);
+  /* Extract the hostname from the address (if we can) */
+  host->name = strpbrk(dup, "@");
+  if(host->name) {
+    *host->name = '\0';
+    host->name = host->name + 1;
 
-  if(!smtpc)
-    return CURLE_FAILED_INIT;
+    /* Attempt to convert the hostname to IDN ACE */
+    (void)Curl_idnconvert_hostname(host);
 
-  if(ir) {                                  /* AUTH <mech> ...<crlf> */
-    /* Send the AUTH command with the initial response */
-    result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir);
-  }
-  else {
-    /* Send the AUTH command */
-    result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s", mech);
+    /* If Curl_idnconvert_hostname() fails then we shall attempt to continue
+       and send the hostname using UTF-8 rather than as 7-bit ACE (which is
+       our preference) */
   }
 
+  /* Extract the local address from the mailbox */
+  *address = dup;
+
   return result;
 }
 
-/***********************************************************************
- *
- * smtp_continue_auth()
- *
- * Sends SASL continuation data.
- */
-static CURLcode smtp_continue_auth(struct Curl_easy *data,
-                                   const char *mech,
-                                   const struct bufref *resp)
-{
-  struct smtp_conn *smtpc =
-    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
+struct cr_eob_ctx {
+  struct Curl_creader super;
+  struct bufq buf;
+  size_t n_eob; /* how many EOB bytes we matched so far */
+  size_t eob;       /* Number of bytes of the EOB (End Of Body) that
+                       have been received so far */
+  BIT(read_eos);  /* we read an EOS from the next reader */
+  BIT(processed_eos);  /* we read and processed an EOS */
+  BIT(eos);       /* we have returned an EOS */
+};
 
-  (void)mech;
-  if(!smtpc)
-    return CURLE_FAILED_INIT;
-  return Curl_pp_sendf(data, &smtpc->pp, "%s", Curl_bufref_ptr(resp));
+static CURLcode cr_eob_init(struct Curl_easy *data,
+                            struct Curl_creader *reader)
+{
+  struct cr_eob_ctx *ctx = reader->ctx;
+  (void)data;
+  /* The first char we read is the first on a line, as if we had
+   * read CRLF just before */
+  ctx->n_eob = 2;
+  Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT);
+  return CURLE_OK;
 }
 
-/***********************************************************************
- *
- * smtp_cancel_auth()
- *
- * Sends SASL cancellation.
- */
-static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech)
+static void cr_eob_close(struct Curl_easy *data, struct Curl_creader *reader)
 {
-  struct smtp_conn *smtpc =
-    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
-
-  (void)mech;
-  if(!smtpc)
-    return CURLE_FAILED_INIT;
-  return Curl_pp_sendf(data, &smtpc->pp, "*");
+  struct cr_eob_ctx *ctx = reader->ctx;
+  (void)data;
+  Curl_bufq_free(&ctx->buf);
 }
 
-/***********************************************************************
- *
- * smtp_perform_authentication()
- *
- * Initiates the authentication sequence, with the appropriate SASL
- * authentication mechanism.
- */
-static CURLcode smtp_perform_authentication(struct Curl_easy *data,
-                                            struct smtp_conn *smtpc)
+/* this is the 5-bytes End-Of-Body marker for SMTP */
+#define SMTP_EOB          "\r\n.\r\n"
+#define SMTP_EOB_FIND_LEN 3
+
+/* client reader doing SMTP End-Of-Body escaping. */
+static CURLcode cr_eob_read(struct Curl_easy *data,
+                            struct Curl_creader *reader,
+                            char *buf, size_t blen,
+                            size_t *pnread, bool *peos)
 {
+  struct cr_eob_ctx *ctx = reader->ctx;
   CURLcode result = CURLE_OK;
-  saslprogress progress;
-
-  /* Check we have enough data to authenticate with, and the
-     server supports authentication, and end the connect phase if not */
-  if(!smtpc->auth_supported ||
-     !Curl_sasl_can_authenticate(&smtpc->sasl, data)) {
-    smtp_state(data, smtpc, SMTP_STOP);
-    return result;
-  }
+  size_t nread, i, start, n;
+  bool eos;
 
-  /* Calculate the SASL login details */
-  result = Curl_sasl_start(&smtpc->sasl, data, FALSE, &progress);
+  if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) {
+    /* Get more and convert it when needed */
+    result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos);
+    CURL_TRC_SMTP(data, "cr_eob_read, next_read(len=%zu) -> %d, %zu eos=%d",
+                  blen, result, nread, eos);
+    if(result)
+      return result;
 
-  if(!result) {
-    if(progress == SASL_INPROGRESS)
-      smtp_state(data, smtpc, SMTP_AUTH);
-    else
-      result = Curl_sasl_is_blocked(&smtpc->sasl, data);
-  }
-
-  return result;
-}
-
-/***********************************************************************
- *
- * smtp_perform_command()
- *
- * Sends an SMTP based command.
- */
-static CURLcode smtp_perform_command(struct Curl_easy *data,
-                                     struct smtp_conn *smtpc,
-                                     struct SMTP *smtp)
-{
-  CURLcode result = CURLE_OK;
-
-  if(smtp->rcpt) {
-    /* We notify the server we are sending UTF-8 data if a) it supports the
-       SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in
-       either the local address or hostname parts. This is regardless of
-       whether the hostname is encoded using IDN ACE */
-    bool utf8 = FALSE;
+    ctx->read_eos = eos;
+    if(nread) {
+      if(!ctx->n_eob && !memchr(buf, SMTP_EOB[0], nread)) {
+        /* not in the middle of a match, no EOB start found, just pass */
+        *pnread = nread;
+        *peos = FALSE;
+        return CURLE_OK;
+      }
+      /* scan for EOB (continuation) and convert */
+      for(i = start = 0; i < nread; ++i) {
+        if(ctx->n_eob >= SMTP_EOB_FIND_LEN) {
+          /* matched the EOB prefix and seeing additional char, add '.' */
+          result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n);
+          if(result)
+            return result;
+          result = Curl_bufq_cwrite(&ctx->buf, ".", 1, &n);
+          if(result)
+            return result;
+          ctx->n_eob = 0;
+          start = i;
+          if(data->state.infilesize > 0)
+            data->state.infilesize++;
+        }
 
-    if((!smtp->custom) || (!smtp->custom[0])) {
-      char *address = NULL;
-      struct hostname host = { NULL, NULL, NULL, NULL };
-      const char *suffix = "";
+        if(buf[i] != SMTP_EOB[ctx->n_eob])
+          ctx->n_eob = 0;
 
-      /* Parse the mailbox to verify into the local address and hostname
-         parts, converting the hostname to an IDN A-label if necessary */
-      result = smtp_parse_address(smtp->rcpt->data,
-                                  &address, &host, &suffix);
-      if(result)
-        return result;
+        if(buf[i] == SMTP_EOB[ctx->n_eob]) {
+          /* matching another char of the EOB */
+          ++ctx->n_eob;
+        }
+      }
 
-      /* Establish whether we should report SMTPUTF8 to the server for this
-         mailbox as per RFC-6531 sect. 3.1 point 6 */
-      utf8 = (smtpc->utf8_supported) &&
-             ((host.encalloc) || (!Curl_is_ASCII_name(address)) ||
-              (!Curl_is_ASCII_name(host.name)));
+      /* add any remainder to buf */
+      if(start < nread) {
+        result = Curl_bufq_cwrite(&ctx->buf, buf + start, nread - start, &n);
+        if(result)
+          return result;
+      }
+    }
+  }
 
-      /* Send the VRFY command (Note: The hostname part may be absent when the
-         host is a local system) */
-      result = Curl_pp_sendf(data, &smtpc->pp, "VRFY %s%s%s%s",
-                             address,
-                             host.name ? "@" : "",
-                             host.name ? host.name : "",
-                             utf8 ? " SMTPUTF8" : "");
+  *peos = FALSE;
 
-      Curl_free_idnconverted_hostname(&host);
-      curlx_free(address);
+  if(ctx->read_eos && !ctx->processed_eos) {
+    /* if we last matched a CRLF or if the data was empty, add ".\r\n"
+     * to end the body. If we sent something and it did not end with "\r\n",
+     * add "\r\n.\r\n" to end the body */
+    const char *eob = SMTP_EOB;
+    CURL_TRC_SMTP(data, "auto-ending mail body with '\\r\\n.\\r\\n'");
+    switch(ctx->n_eob) {
+    case 2:
+      /* seen a CRLF at the end, just add the remainder */
+      eob = &SMTP_EOB[2];
+      break;
+    case 3:
+      /* ended with '\r\n.', we should escape the last '.' */
+      eob = "." SMTP_EOB;
+      break;
+    default:
+      break;
     }
-    else {
-      /* Establish whether we should report that we support SMTPUTF8 for EXPN
-         commands to the server as per RFC-6531 sect. 3.1 point 6 */
-      utf8 = (smtpc->utf8_supported) && (!strcmp(smtp->custom, "EXPN"));
+    result = Curl_bufq_cwrite(&ctx->buf, eob, strlen(eob), &n);
+    if(result)
+      return result;
+    ctx->processed_eos = TRUE;
+  }
 
-      /* Send the custom recipient based command such as the EXPN command */
-      result = Curl_pp_sendf(data, &smtpc->pp,
-                             "%s %s%s", smtp->custom,
-                             smtp->rcpt->data,
-                             utf8 ? " SMTPUTF8" : "");
-    }
+  if(!Curl_bufq_is_empty(&ctx->buf)) {
+    result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread);
   }
   else
-    /* Send the non-recipient based command such as HELP */
-    result = Curl_pp_sendf(data, &smtpc->pp, "%s",
-                           smtp->custom && smtp->custom[0] != '\0' ?
-                           smtp->custom : "HELP");
+    *pnread = 0;
+
+  if(ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) {
+    /* no more data, read all, done. */
+    CURL_TRC_SMTP(data, "mail body complete, returning EOS");
+    ctx->eos = TRUE;
+  }
+  *peos = ctx->eos;
+  DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d",
+         blen, result, *pnread, *peos));
+  return result;
+}
+
+static curl_off_t cr_eob_total_length(struct Curl_easy *data,
+                                      struct Curl_creader *reader)
+{
+  /* this reader changes length depending on input */
+  (void)data;
+  (void)reader;
+  return -1;
+}
+
+static const struct Curl_crtype cr_eob = {
+  "cr-smtp-eob",
+  cr_eob_init,
+  cr_eob_read,
+  cr_eob_close,
+  Curl_creader_def_needs_rewind,
+  cr_eob_total_length,
+  Curl_creader_def_resume_from,
+  Curl_creader_def_cntrl,
+  Curl_creader_def_is_paused,
+  Curl_creader_def_done,
+  sizeof(struct cr_eob_ctx)
+};
+
+static CURLcode cr_eob_add(struct Curl_easy *data)
+{
+  struct Curl_creader *reader = NULL;
+  CURLcode result;
 
+  result = Curl_creader_create(&reader, data, &cr_eob, CURL_CR_CONTENT_ENCODE);
   if(!result)
-    smtp_state(data, smtpc, SMTP_COMMAND);
+    result = Curl_creader_add(data, reader);
 
+  if(result && reader)
+    Curl_creader_free(data, reader);
   return result;
 }
 
 /***********************************************************************
  *
- * smtp_perform_mail()
+ * smtp_endofresp()
  *
- * Sends an MAIL command to initiate the upload of a message.
+ * Checks for an ending SMTP status code at the start of the given string, but
+ * also detects various capabilities from the EHLO response including the
+ * supported authentication mechanisms.
  */
-static CURLcode smtp_perform_mail(struct Curl_easy *data,
-                                  struct smtp_conn *smtpc,
-                                  struct SMTP *smtp)
+static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn,
+                           const char *line, size_t len, int *resp)
 {
-  char *from = NULL;
-  char *auth = NULL;
-  char *size = NULL;
-  CURLcode result = CURLE_OK;
-
-  /* We notify the server we are sending UTF-8 data if a) it supports the
-     SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in
-     either the local address or hostname parts. This is regardless of
-     whether the hostname is encoded using IDN ACE */
-  bool utf8 = FALSE;
-
-  /* Calculate the FROM parameter */
-  if(data->set.str[STRING_MAIL_FROM]) {
-    char *address = NULL;
-    struct hostname host = { NULL, NULL, NULL, NULL };
-    const char *suffix = "";
-
-    /* Parse the FROM mailbox into the local address and hostname parts,
-       converting the hostname to an IDN A-label if necessary */
-    result = smtp_parse_address(data->set.str[STRING_MAIL_FROM],
-                                &address, &host, &suffix);
-    if(result)
-      goto out;
+  struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN);
+  bool result = FALSE;
+  (void)data;
 
-    /* Establish whether we should report SMTPUTF8 to the server for this
-       mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */
-    utf8 = (smtpc->utf8_supported) &&
-           ((host.encalloc) || (!Curl_is_ASCII_name(address)) ||
-            (!Curl_is_ASCII_name(host.name)));
+  DEBUGASSERT(smtpc);
+  if(!smtpc)
+    return FALSE;
 
-    if(host.name) {
-      from = curl_maprintf("<%s@%s>%s", address, host.name, suffix);
+  /* Nothing for us */
+  if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2]))
+    return FALSE;
 
-      Curl_free_idnconverted_hostname(&host);
-    }
-    else
-      /* An invalid mailbox was provided but we will simply let the server
-         worry about that and reply with a 501 error */
-      from = curl_maprintf("<%s>%s", address, suffix);
+  /* Do we have a command response? This should be the response code followed
+     by a space and optionally some text as per RFC-5321 and as outlined in
+     Section 4. Examples of RFC-4954 but some email servers ignore this and
+     only send the response code instead as per Section 4.2. */
+  if(line[3] == ' ' || len == 5) {
+    char tmpline[6];
+    curl_off_t code;
+    const char *p = tmpline;
+    result = TRUE;
+    memcpy(tmpline, line, (len == 5 ? 5 : 3));
+    tmpline[len == 5 ? 5 : 3] = 0;
+    if(curlx_str_number(&p, &code, len == 5 ? 99999 : 999))
+      return FALSE;
+    *resp = (int)code;
 
-    curlx_free(address);
+    /* Make sure real server never sends internal value */
+    if(*resp == 1)
+      *resp = 0;
   }
-  else
-    /* Null reverse-path, RFC-5321, sect. 3.6.3 */
-    from = curlx_strdup("<>");
-
-  if(!from) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
+  /* Do we have a multiline (continuation) response? */
+  else if(line[3] == '-' &&
+          (smtpc->state == SMTP_EHLO || smtpc->state == SMTP_COMMAND)) {
+    result = TRUE;
+    *resp = 1;  /* Internal response code */
   }
 
-  /* Calculate the optional AUTH parameter */
-  if(data->set.str[STRING_MAIL_AUTH] && smtpc->sasl.authused) {
-    if(data->set.str[STRING_MAIL_AUTH][0] != '\0') {
-      char *address = NULL;
-      struct hostname host = { NULL, NULL, NULL, NULL };
-      const char *suffix = "";
-
-      /* Parse the AUTH mailbox into the local address and hostname parts,
-         converting the hostname to an IDN A-label if necessary */
-      result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH],
-                                  &address, &host, &suffix);
-      if(result)
-        goto out;
-
-      /* Establish whether we should report SMTPUTF8 to the server for this
-         mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */
-      if((!utf8) && (smtpc->utf8_supported) &&
-         ((host.encalloc) || (!Curl_is_ASCII_name(address)) ||
-          (!Curl_is_ASCII_name(host.name))))
-        utf8 = TRUE;
-
-      if(host.name) {
-        auth = curl_maprintf("<%s@%s>%s", address, host.name, suffix);
-
-        Curl_free_idnconverted_hostname(&host);
-      }
-      else
-        /* An invalid mailbox was provided but we will simply let the server
-           worry about it */
-        auth = curl_maprintf("<%s>%s", address, suffix);
-      curlx_free(address);
-    }
-    else
-      /* Empty AUTH, RFC-2554, sect. 5 */
-      auth = curlx_strdup("<>");
-
-    if(!auth) {
-      result = CURLE_OUT_OF_MEMORY;
-      goto out;
-    }
-  }
+  return result;
+}
 
-#ifndef CURL_DISABLE_MIME
-  /* Prepare the mime data if some. */
-  if(IS_MIME_POST(data)) {
-    curl_mimepart *postp = data->set.mimepostp;
+/***********************************************************************
+ *
+ * smtp_get_message()
+ *
+ * Gets the authentication message from the response buffer.
+ */
+static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out)
+{
+  struct smtp_conn *smtpc =
+    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
+  char *message;
+  size_t len;
 
-    /* Use the whole structure as data. */
-    postp->flags &= ~(unsigned int)MIME_BODY_ONLY;
+  if(!smtpc)
+    return CURLE_FAILED_INIT;
 
-    /* Add external headers and mime version. */
-    curl_mime_headers(postp, data->set.headers, 0);
-    result = Curl_mime_prepare_headers(data, postp, NULL,
-                                       NULL, MIMESTRATEGY_MAIL);
+  message = curlx_dyn_ptr(&smtpc->pp.recvbuf);
+  len = smtpc->pp.nfinal;
+  if(len > 4) {
+    /* Find the start of the message */
+    len -= 4;
+    for(message += 4; *message == ' ' || *message == '\t'; message++, len--)
+      ;
 
-    if(!result)
-      if(!Curl_checkheaders(data, STRCONST("Mime-Version")))
-        result = Curl_mime_add_header(&postp->curlheaders,
-                                      "Mime-Version: 1.0");
+    /* Find the end of the message */
+    while(len--)
+      if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' &&
+         message[len] != '\t')
+        break;
 
-    if(!result)
-      result = Curl_creader_set_mime(data, postp);
-    if(result)
-      goto out;
-    data->state.infilesize = Curl_creader_total_length(data);
+    /* Terminate the message */
+    message[++len] = '\0';
+    Curl_bufref_set(out, message, len, NULL);
   }
   else
-#endif
-  {
-    result = Curl_creader_set_fread(data, data->state.infilesize);
-    if(result)
-      goto out;
-  }
-
-  /* Calculate the optional SIZE parameter */
-  if(smtpc->size_supported && data->state.infilesize > 0) {
-    size = curl_maprintf("%" FMT_OFF_T, data->state.infilesize);
+    /* junk input => zero length output */
+    Curl_bufref_set(out, "", 0, NULL);
 
-    if(!size) {
-      result = CURLE_OUT_OF_MEMORY;
-      goto out;
-    }
-  }
+  return CURLE_OK;
+}
 
-  /* If the mailboxes in the FROM and AUTH parameters do not include a UTF-8
-     based address then quickly scan through the recipient list and check if
-     any there do, as we need to correctly identify our support for SMTPUTF8
-     in the envelope, as per RFC-6531 sect. 3.4 */
-  if(smtpc->utf8_supported && !utf8) {
-    struct curl_slist *rcpt = smtp->rcpt;
+/***********************************************************************
+ *
+ * smtp_state()
+ *
+ * This is the ONLY way to change SMTP state!
+ */
+static void smtp_state(struct Curl_easy *data,
+                       struct smtp_conn *smtpc,
+                       smtpstate newstate)
+{
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+  /* for debug purposes */
+  static const char * const names[] = {
+    "STOP",
+    "SERVERGREET",
+    "EHLO",
+    "HELO",
+    "STARTTLS",
+    "UPGRADETLS",
+    "AUTH",
+    "COMMAND",
+    "MAIL",
+    "RCPT",
+    "DATA",
+    "POSTDATA",
+    "QUIT",
+    /* LAST */
+  };
 
-    while(rcpt && !utf8) {
-      /* Does the hostname contain non-ASCII characters? */
-      if(!Curl_is_ASCII_name(rcpt->data))
-        utf8 = TRUE;
+  if(smtpc->state != newstate)
+    CURL_TRC_SMTP(data, "state change from %s to %s",
+                  names[smtpc->state], names[newstate]);
+#else
+  (void)data;
+#endif
 
-      rcpt = rcpt->next;
-    }
-  }
+  smtpc->state = newstate;
+}
 
-  /* Add the client reader doing STMP EOB escaping */
-  result = cr_eob_add(data);
-  if(result)
-    goto out;
+/***********************************************************************
+ *
+ * smtp_perform_ehlo()
+ *
+ * Sends the EHLO command to not only initialise communication with the ESMTP
+ * server but to also obtain a list of server side supported capabilities.
+ */
+static CURLcode smtp_perform_ehlo(struct Curl_easy *data,
+                                  struct smtp_conn *smtpc)
+{
+  CURLcode result = CURLE_OK;
 
-  /* Send the MAIL command */
-  result = Curl_pp_sendf(data, &smtpc->pp,
-                         "MAIL FROM:%s%s%s%s%s%s",
-                         from,                 /* Mandatory                 */
-                         auth ? " AUTH=" : "", /* Optional on AUTH support  */
-                         auth ? auth : "",     /*                           */
-                         size ? " SIZE=" : "", /* Optional on SIZE support  */
-                         size ? size : "",     /*                           */
-                         utf8 ? " SMTPUTF8"    /* Internationalised mailbox */
-                               : "");          /* included in our envelope  */
+  smtpc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanism yet */
+  smtpc->sasl.authused = SASL_AUTH_NONE;  /* Clear the authentication mechanism
+                                             used for esmtp connections */
+  smtpc->tls_supported = FALSE;           /* Clear the TLS capability */
+  smtpc->auth_supported = FALSE;          /* Clear the AUTH capability */
 
-out:
-  curlx_free(from);
-  curlx_free(auth);
-  curlx_free(size);
+  /* Send the EHLO command */
+  result = Curl_pp_sendf(data, &smtpc->pp, "EHLO %s", smtpc->domain);
 
   if(!result)
-    smtp_state(data, smtpc, SMTP_MAIL);
+    smtp_state(data, smtpc, SMTP_EHLO);
 
   return result;
 }
 
 /***********************************************************************
  *
- * smtp_perform_rcpt_to()
+ * smtp_perform_helo()
  *
- * Sends a RCPT TO command for a given recipient as part of the message upload
- * process.
+ * Sends the HELO command to initialise communication with the SMTP server.
  */
-static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data,
-                                     struct smtp_conn *smtpc,
-                                     struct SMTP *smtp)
+static CURLcode smtp_perform_helo(struct Curl_easy *data,
+                                  struct smtp_conn *smtpc)
 {
   CURLcode result = CURLE_OK;
-  char *address = NULL;
-  struct hostname host = { NULL, NULL, NULL, NULL };
-  const char *suffix = "";
 
-  /* Parse the recipient mailbox into the local address and hostname parts,
-     converting the hostname to an IDN A-label if necessary */
-  result = smtp_parse_address(smtp->rcpt->data,
-                              &address, &host, &suffix);
-  if(result)
-    return result;
-
-  /* Send the RCPT TO command */
-  if(host.name)
-    result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s@%s>%s",
-                           address, host.name, suffix);
-  else
-    /* An invalid mailbox was provided but we will simply let the server worry
-       about that and reply with a 501 error */
-    result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s>%s",
-                           address, suffix);
+  smtpc->sasl.authused = SASL_AUTH_NONE; /* No authentication mechanism used
+                                            in smtp connections */
 
-  Curl_free_idnconverted_hostname(&host);
-  curlx_free(address);
+  /* Send the HELO command */
+  result = Curl_pp_sendf(data, &smtpc->pp, "HELO %s", smtpc->domain);
 
   if(!result)
-    smtp_state(data, smtpc, SMTP_RCPT);
+    smtp_state(data, smtpc, SMTP_HELO);
 
   return result;
 }
 
 /***********************************************************************
  *
- * smtp_perform_quit()
+ * smtp_perform_starttls()
  *
- * Performs the quit action prior to sclose() being called.
+ * Sends the STLS command to start the upgrade to TLS.
  */
-static CURLcode smtp_perform_quit(struct Curl_easy *data,
-                                  struct smtp_conn *smtpc)
+static CURLcode smtp_perform_starttls(struct Curl_easy *data,
+                                      struct smtp_conn *smtpc)
 {
-  /* Send the QUIT command */
-  CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "QUIT");
+  /* Send the STARTTLS command */
+  CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "STARTTLS");
 
   if(!result)
-    smtp_state(data, smtpc, SMTP_QUIT);
+    smtp_state(data, smtpc, SMTP_STARTTLS);
 
   return result;
 }
 
-/* For the initial server greeting */
-static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data,
-                                            struct smtp_conn *smtpc,
-                                            int smtpcode,
-                                            smtpstate instate)
-{
-  CURLcode result = CURLE_OK;
-  (void)instate;
-
-  if(smtpcode / 100 != 2) {
-    failf(data, "Got unexpected smtp-server response: %d", smtpcode);
-    result = CURLE_WEIRD_SERVER_REPLY;
+/***********************************************************************
+ *
+ * smtp_perform_upgrade_tls()
+ *
+ * Performs the upgrade to TLS.
+ */
+static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data,
+                                         struct smtp_conn *smtpc)
+{
+#ifdef USE_SSL
+  /* Start the SSL connection */
+  struct connectdata *conn = data->conn;
+  CURLcode result;
+  bool ssldone = FALSE;
+
+  DEBUGASSERT(smtpc->state == SMTP_UPGRADETLS);
+  if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
+    result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
+    if(result)
+      goto out;
+    /* Change the connection handler and SMTP state */
+    conn->handler = &Curl_handler_smtps;
   }
-  else
-    result = smtp_perform_ehlo(data, smtpc);
 
+  DEBUGASSERT(!smtpc->ssldone);
+  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
+  DEBUGF(infof(data, "smtp_perform_upgrade_tls, connect -> %d, %d",
+           result, ssldone));
+  if(!result && ssldone) {
+    smtpc->ssldone = ssldone;
+    /* perform EHLO now, changes smtp->state out of SMTP_UPGRADETLS */
+    result = smtp_perform_ehlo(data, smtpc);
+  }
+out:
   return result;
+#else
+  (void)data;
+  (void)smtpc;
+  return CURLE_NOT_BUILT_IN;
+#endif
 }
 
-/* For STARTTLS responses */
-static CURLcode smtp_state_starttls_resp(struct Curl_easy *data,
-                                         struct smtp_conn *smtpc,
-                                         int smtpcode,
-                                         smtpstate instate)
+/***********************************************************************
+ *
+ * smtp_perform_auth()
+ *
+ * Sends an AUTH command allowing the client to login with the given SASL
+ * authentication mechanism.
+ */
+static CURLcode smtp_perform_auth(struct Curl_easy *data,
+                                  const char *mech,
+                                  const struct bufref *initresp)
 {
   CURLcode result = CURLE_OK;
-  (void)instate;
+  struct smtp_conn *smtpc =
+    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
+  const char *ir = Curl_bufref_ptr(initresp);
 
-  /* Pipelining in response is forbidden. */
-  if(smtpc->pp.overflow)
-    return CURLE_WEIRD_SERVER_REPLY;
+  if(!smtpc)
+    return CURLE_FAILED_INIT;
 
-  if(smtpcode != 220) {
-    if(data->set.use_ssl != CURLUSESSL_TRY) {
-      failf(data, "STARTTLS denied, code %d", smtpcode);
-      result = CURLE_USE_SSL_FAILED;
-    }
-    else
-      result = smtp_perform_authentication(data, smtpc);
+  if(ir) {                                  /* AUTH <mech> ...<crlf> */
+    /* Send the AUTH command with the initial response */
+    result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir);
+  }
+  else {
+    /* Send the AUTH command */
+    result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s", mech);
   }
-  else
-    smtp_state(data, smtpc, SMTP_UPGRADETLS);
 
   return result;
 }
 
-/* For EHLO responses */
-static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data,
-                                     struct smtp_conn *smtpc,
-                                     int smtpcode,
-                                     smtpstate instate)
+/***********************************************************************
+ *
+ * smtp_continue_auth()
+ *
+ * Sends SASL continuation data.
+ */
+static CURLcode smtp_continue_auth(struct Curl_easy *data,
+                                   const char *mech,
+                                   const struct bufref *resp)
 {
-  CURLcode result = CURLE_OK;
-  const char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf);
-  size_t len = smtpc->pp.nfinal;
-
-  (void)instate;
-
-  if(smtpcode / 100 != 2 && smtpcode != 1) {
-    if(data->set.use_ssl <= CURLUSESSL_TRY ||
-       Curl_conn_is_ssl(data->conn, FIRSTSOCKET))
-      result = smtp_perform_helo(data, smtpc);
-    else {
-      failf(data, "Remote access denied: %d", smtpcode);
-      result = CURLE_REMOTE_ACCESS_DENIED;
-    }
-  }
-  else if(len >= 4) {
-    line += 4;
-    len -= 4;
-
-    /* Does the server support the STARTTLS capability? */
-    if(len >= 8 && curl_strnequal(line, "STARTTLS", 8))
-      smtpc->tls_supported = TRUE;
-
-    /* Does the server support the SIZE capability? */
-    else if(len >= 4 && curl_strnequal(line, "SIZE", 4))
-      smtpc->size_supported = TRUE;
-
-    /* Does the server support the UTF-8 capability? */
-    else if(len >= 8 && curl_strnequal(line, "SMTPUTF8", 8))
-      smtpc->utf8_supported = TRUE;
-
-    /* Does the server support authentication? */
-    else if(len >= 5 && curl_strnequal(line, "AUTH ", 5)) {
-      smtpc->auth_supported = TRUE;
-
-      /* Advance past the AUTH keyword */
-      line += 5;
-      len -= 5;
-
-      /* Loop through the data line */
-      for(;;) {
-        size_t llen;
-        size_t wordlen;
-        unsigned short mechbit;
+  struct smtp_conn *smtpc =
+    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
 
-        while(len &&
-              (*line == ' ' || *line == '\t' ||
-               *line == '\r' || *line == '\n')) {
+  (void)mech;
+  if(!smtpc)
+    return CURLE_FAILED_INIT;
+  return Curl_pp_sendf(data, &smtpc->pp, "%s", Curl_bufref_ptr(resp));
+}
 
-          line++;
-          len--;
-        }
+/***********************************************************************
+ *
+ * smtp_cancel_auth()
+ *
+ * Sends SASL cancellation.
+ */
+static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech)
+{
+  struct smtp_conn *smtpc =
+    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
 
-        if(!len)
-          break;
+  (void)mech;
+  if(!smtpc)
+    return CURLE_FAILED_INIT;
+  return Curl_pp_sendf(data, &smtpc->pp, "*");
+}
 
-        /* Extract the word */
-        for(wordlen = 0; wordlen < len && line[wordlen] != ' ' &&
-              line[wordlen] != '\t' && line[wordlen] != '\r' &&
-              line[wordlen] != '\n';)
-          wordlen++;
+/***********************************************************************
+ *
+ * smtp_perform_authentication()
+ *
+ * Initiates the authentication sequence, with the appropriate SASL
+ * authentication mechanism.
+ */
+static CURLcode smtp_perform_authentication(struct Curl_easy *data,
+                                            struct smtp_conn *smtpc)
+{
+  CURLcode result = CURLE_OK;
+  saslprogress progress;
 
-        /* Test the word for a matching authentication mechanism */
-        mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
-        if(mechbit && llen == wordlen)
-          smtpc->sasl.authmechs |= mechbit;
+  /* Check we have enough data to authenticate with, and the
+     server supports authentication, and end the connect phase if not */
+  if(!smtpc->auth_supported ||
+     !Curl_sasl_can_authenticate(&smtpc->sasl, data)) {
+    smtp_state(data, smtpc, SMTP_STOP);
+    return result;
+  }
 
-        line += wordlen;
-        len -= wordlen;
-      }
-    }
+  /* Calculate the SASL login details */
+  result = Curl_sasl_start(&smtpc->sasl, data, FALSE, &progress);
 
-    if(smtpcode != 1) {
-      if(data->set.use_ssl && !Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) {
-        /* We do not have an SSL/TLS connection yet, but SSL is requested */
-        if(smtpc->tls_supported)
-          /* Switch to TLS connection now */
-          result = smtp_perform_starttls(data, smtpc);
-        else if(data->set.use_ssl == CURLUSESSL_TRY)
-          /* Fallback and carry on with authentication */
-          result = smtp_perform_authentication(data, smtpc);
-        else {
-          failf(data, "STARTTLS not supported.");
-          result = CURLE_USE_SSL_FAILED;
-        }
-      }
-      else
-        result = smtp_perform_authentication(data, smtpc);
-    }
-  }
-  else {
-    failf(data, "Unexpectedly short EHLO response");
-    result = CURLE_WEIRD_SERVER_REPLY;
+  if(!result) {
+    if(progress == SASL_INPROGRESS)
+      smtp_state(data, smtpc, SMTP_AUTH);
+    else
+      result = Curl_sasl_is_blocked(&smtpc->sasl, data);
   }
 
   return result;
 }
 
-/* For HELO responses */
-static CURLcode smtp_state_helo_resp(struct Curl_easy *data,
+/***********************************************************************
+ *
+ * smtp_perform_command()
+ *
+ * Sends an SMTP based command.
+ */
+static CURLcode smtp_perform_command(struct Curl_easy *data,
                                      struct smtp_conn *smtpc,
-                                     int smtpcode,
-                                     smtpstate instate)
+                                     struct SMTP *smtp)
 {
   CURLcode result = CURLE_OK;
-  (void)instate;
 
-  if(smtpcode / 100 != 2) {
-    failf(data, "Remote access denied: %d", smtpcode);
-    result = CURLE_REMOTE_ACCESS_DENIED;
-  }
-  else
-    /* End of connect phase */
-    smtp_state(data, smtpc, SMTP_STOP);
+  if(smtp->rcpt) {
+    /* We notify the server we are sending UTF-8 data if a) it supports the
+       SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in
+       either the local address or hostname parts. This is regardless of
+       whether the hostname is encoded using IDN ACE */
+    bool utf8 = FALSE;
 
-  return result;
-}
+    if((!smtp->custom) || (!smtp->custom[0])) {
+      char *address = NULL;
+      struct hostname host = { NULL, NULL, NULL, NULL };
+      const char *suffix = "";
 
-/* For SASL authentication responses */
-static CURLcode smtp_state_auth_resp(struct Curl_easy *data,
-                                     struct smtp_conn *smtpc,
-                                     int smtpcode,
-                                     smtpstate instate)
-{
-  CURLcode result = CURLE_OK;
-  saslprogress progress;
+      /* Parse the mailbox to verify into the local address and hostname
+         parts, converting the hostname to an IDN A-label if necessary */
+      result = smtp_parse_address(smtp->rcpt->data,
+                                  &address, &host, &suffix);
+      if(result)
+        return result;
 
-  (void)instate;
+      /* Establish whether we should report SMTPUTF8 to the server for this
+         mailbox as per RFC-6531 sect. 3.1 point 6 */
+      utf8 = (smtpc->utf8_supported) &&
+             ((host.encalloc) || (!Curl_is_ASCII_name(address)) ||
+              (!Curl_is_ASCII_name(host.name)));
 
-  result = Curl_sasl_continue(&smtpc->sasl, data, smtpcode, &progress);
-  if(!result)
-    switch(progress) {
-    case SASL_DONE:
-      smtp_state(data, smtpc, SMTP_STOP);  /* Authenticated */
-      break;
-    case SASL_IDLE:            /* No mechanism left after cancellation */
-      failf(data, "Authentication cancelled");
-      result = CURLE_LOGIN_DENIED;
-      break;
-    default:
-      break;
+      /* Send the VRFY command (Note: The hostname part may be absent when the
+         host is a local system) */
+      result = Curl_pp_sendf(data, &smtpc->pp, "VRFY %s%s%s%s",
+                             address,
+                             host.name ? "@" : "",
+                             host.name ? host.name : "",
+                             utf8 ? " SMTPUTF8" : "");
+
+      Curl_free_idnconverted_hostname(&host);
+      curlx_free(address);
+    }
+    else {
+      /* Establish whether we should report that we support SMTPUTF8 for EXPN
+         commands to the server as per RFC-6531 sect. 3.1 point 6 */
+      utf8 = (smtpc->utf8_supported) && (!strcmp(smtp->custom, "EXPN"));
+
+      /* Send the custom recipient based command such as the EXPN command */
+      result = Curl_pp_sendf(data, &smtpc->pp,
+                             "%s %s%s", smtp->custom,
+                             smtp->rcpt->data,
+                             utf8 ? " SMTPUTF8" : "");
     }
+  }
+  else
+    /* Send the non-recipient based command such as HELP */
+    result = Curl_pp_sendf(data, &smtpc->pp, "%s",
+                           smtp->custom && smtp->custom[0] != '\0' ?
+                           smtp->custom : "HELP");
+
+  if(!result)
+    smtp_state(data, smtpc, SMTP_COMMAND);
 
   return result;
 }
 
-/* For command responses */
-static CURLcode smtp_state_command_resp(struct Curl_easy *data,
-                                        struct smtp_conn *smtpc,
-                                        struct SMTP *smtp,
-                                        int smtpcode,
-                                        smtpstate instate)
+/***********************************************************************
+ *
+ * smtp_perform_mail()
+ *
+ * Sends an MAIL command to initiate the upload of a message.
+ */
+static CURLcode smtp_perform_mail(struct Curl_easy *data,
+                                  struct smtp_conn *smtpc,
+                                  struct SMTP *smtp)
 {
+  char *from = NULL;
+  char *auth = NULL;
+  char *size = NULL;
   CURLcode result = CURLE_OK;
-  char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf);
-  size_t len = smtpc->pp.nfinal;
 
-  (void)instate;
+  /* We notify the server we are sending UTF-8 data if a) it supports the
+     SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in
+     either the local address or hostname parts. This is regardless of
+     whether the hostname is encoded using IDN ACE */
+  bool utf8 = FALSE;
 
-  if((smtp->rcpt && smtpcode / 100 != 2 && smtpcode != 553 && smtpcode != 1) ||
-     (!smtp->rcpt && smtpcode / 100 != 2 && smtpcode != 1)) {
-    failf(data, "Command failed: %d", smtpcode);
-    result = CURLE_WEIRD_SERVER_REPLY;
-  }
-  else {
-    if(!data->req.no_body)
-      result = Curl_client_write(data, CLIENTWRITE_BODY, line, len);
+  /* Calculate the FROM parameter */
+  if(data->set.str[STRING_MAIL_FROM]) {
+    char *address = NULL;
+    struct hostname host = { NULL, NULL, NULL, NULL };
+    const char *suffix = "";
 
-    if(!result && (smtpcode != 1)) {
-      if(smtp->rcpt) {
-        smtp->rcpt = smtp->rcpt->next;
+    /* Parse the FROM mailbox into the local address and hostname parts,
+       converting the hostname to an IDN A-label if necessary */
+    result = smtp_parse_address(data->set.str[STRING_MAIL_FROM],
+                                &address, &host, &suffix);
+    if(result)
+      goto out;
 
-        if(smtp->rcpt) {
-          /* Send the next command */
-          result = smtp_perform_command(data, smtpc, smtp);
-        }
-        else
-          /* End of DO phase */
-          smtp_state(data, smtpc, SMTP_STOP);
-      }
-      else
-        /* End of DO phase */
-        smtp_state(data, smtpc, SMTP_STOP);
-    }
-  }
+    /* Establish whether we should report SMTPUTF8 to the server for this
+       mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */
+    utf8 = (smtpc->utf8_supported) &&
+           ((host.encalloc) || (!Curl_is_ASCII_name(address)) ||
+            (!Curl_is_ASCII_name(host.name)));
 
-  return result;
-}
+    if(host.name) {
+      from = curl_maprintf("<%s@%s>%s", address, host.name, suffix);
 
-/* For MAIL responses */
-static CURLcode smtp_state_mail_resp(struct Curl_easy *data,
-                                     struct smtp_conn *smtpc,
-                                     struct SMTP *smtp,
-                                     int smtpcode,
-                                     smtpstate instate)
-{
-  CURLcode result = CURLE_OK;
-  (void)instate;
+      Curl_free_idnconverted_hostname(&host);
+    }
+    else
+      /* An invalid mailbox was provided but we will simply let the server
+         worry about that and reply with a 501 error */
+      from = curl_maprintf("<%s>%s", address, suffix);
 
-  if(smtpcode / 100 != 2) {
-    failf(data, "MAIL failed: %d", smtpcode);
-    result = CURLE_SEND_ERROR;
+    curlx_free(address);
   }
   else
-    /* Start the RCPT TO command */
-    result = smtp_perform_rcpt_to(data, smtpc, smtp);
+    /* Null reverse-path, RFC-5321, sect. 3.6.3 */
+    from = curlx_strdup("<>");
 
-  return result;
-}
+  if(!from) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
 
-/* For RCPT responses */
-static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data,
-                                     struct smtp_conn *smtpc,
-                                     struct SMTP *smtp,
-                                     int smtpcode,
-                                     smtpstate instate)
-{
-  CURLcode result = CURLE_OK;
-  bool is_smtp_err = FALSE;
-  bool is_smtp_blocking_err = FALSE;
+  /* Calculate the optional AUTH parameter */
+  if(data->set.str[STRING_MAIL_AUTH] && smtpc->sasl.authused) {
+    if(data->set.str[STRING_MAIL_AUTH][0] != '\0') {
+      char *address = NULL;
+      struct hostname host = { NULL, NULL, NULL, NULL };
+      const char *suffix = "";
 
-  (void)instate;
+      /* Parse the AUTH mailbox into the local address and hostname parts,
+         converting the hostname to an IDN A-label if necessary */
+      result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH],
+                                  &address, &host, &suffix);
+      if(result)
+        goto out;
 
-  is_smtp_err = (smtpcode / 100 != 2);
+      /* Establish whether we should report SMTPUTF8 to the server for this
+         mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */
+      if((!utf8) && (smtpc->utf8_supported) &&
+         ((host.encalloc) || (!Curl_is_ASCII_name(address)) ||
+          (!Curl_is_ASCII_name(host.name))))
+        utf8 = TRUE;
 
-  /* If there is multiple RCPT TO to be issued, it is possible to ignore errors
-     and proceed with only the valid addresses. */
-  is_smtp_blocking_err = (is_smtp_err && !data->set.mail_rcpt_allowfails);
+      if(host.name) {
+        auth = curl_maprintf("<%s@%s>%s", address, host.name, suffix);
 
-  if(is_smtp_err) {
-    /* Remembering the last failure which we can report if all "RCPT TO" have
-       failed and we cannot proceed. */
-    smtp->rcpt_last_error = smtpcode;
+        Curl_free_idnconverted_hostname(&host);
+      }
+      else
+        /* An invalid mailbox was provided but we will simply let the server
+           worry about it */
+        auth = curl_maprintf("<%s>%s", address, suffix);
+      curlx_free(address);
+    }
+    else
+      /* Empty AUTH, RFC-2554, sect. 5 */
+      auth = curlx_strdup("<>");
 
-    if(is_smtp_blocking_err) {
-      failf(data, "RCPT failed: %d", smtpcode);
-      result = CURLE_SEND_ERROR;
+    if(!auth) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto out;
     }
   }
-  else {
-    /* Some RCPT TO commands have succeeded. */
-    smtp->rcpt_had_ok = TRUE;
-  }
 
-  if(!is_smtp_blocking_err) {
-    smtp->rcpt = smtp->rcpt->next;
+#ifndef CURL_DISABLE_MIME
+  /* Prepare the mime data if some. */
+  if(IS_MIME_POST(data)) {
+    curl_mimepart *postp = data->set.mimepostp;
 
-    if(smtp->rcpt)
-      /* Send the next RCPT TO command */
-      result = smtp_perform_rcpt_to(data, smtpc, smtp);
-    else {
-      /* We were not able to issue a successful RCPT TO command while going
-         over recipients (potentially multiple). Sending back last error. */
-      if(!smtp->rcpt_had_ok) {
-        failf(data, "RCPT failed: %d (last error)", smtp->rcpt_last_error);
-        result = CURLE_SEND_ERROR;
-      }
-      else {
-        /* Send the DATA command */
-        result = Curl_pp_sendf(data, &smtpc->pp, "%s", "DATA");
+    /* Use the whole structure as data. */
+    postp->flags &= ~(unsigned int)MIME_BODY_ONLY;
 
-        if(!result)
-          smtp_state(data, smtpc, SMTP_DATA);
-      }
-    }
-  }
+    /* Add external headers and mime version. */
+    curl_mime_headers(postp, data->set.headers, 0);
+    result = Curl_mime_prepare_headers(data, postp, NULL,
+                                       NULL, MIMESTRATEGY_MAIL);
+
+    if(!result)
+      if(!Curl_checkheaders(data, STRCONST("Mime-Version")))
+        result = Curl_mime_add_header(&postp->curlheaders,
+                                      "Mime-Version: 1.0");
+
+    if(!result)
+      result = Curl_creader_set_mime(data, postp);
+    if(result)
+      goto out;
+    data->state.infilesize = Curl_creader_total_length(data);
+  }
+  else
+#endif
+  {
+    result = Curl_creader_set_fread(data, data->state.infilesize);
+    if(result)
+      goto out;
+  }
+
+  /* Calculate the optional SIZE parameter */
+  if(smtpc->size_supported && data->state.infilesize > 0) {
+    size = curl_maprintf("%" FMT_OFF_T, data->state.infilesize);
+
+    if(!size) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto out;
+    }
+  }
+
+  /* If the mailboxes in the FROM and AUTH parameters do not include a UTF-8
+     based address then quickly scan through the recipient list and check if
+     any there do, as we need to correctly identify our support for SMTPUTF8
+     in the envelope, as per RFC-6531 sect. 3.4 */
+  if(smtpc->utf8_supported && !utf8) {
+    struct curl_slist *rcpt = smtp->rcpt;
+
+    while(rcpt && !utf8) {
+      /* Does the hostname contain non-ASCII characters? */
+      if(!Curl_is_ASCII_name(rcpt->data))
+        utf8 = TRUE;
+
+      rcpt = rcpt->next;
+    }
+  }
+
+  /* Add the client reader doing STMP EOB escaping */
+  result = cr_eob_add(data);
+  if(result)
+    goto out;
+
+  /* Send the MAIL command */
+  result = Curl_pp_sendf(data, &smtpc->pp,
+                         "MAIL FROM:%s%s%s%s%s%s",
+                         from,                 /* Mandatory                 */
+                         auth ? " AUTH=" : "", /* Optional on AUTH support  */
+                         auth ? auth : "",     /*                           */
+                         size ? " SIZE=" : "", /* Optional on SIZE support  */
+                         size ? size : "",     /*                           */
+                         utf8 ? " SMTPUTF8"    /* Internationalised mailbox */
+                               : "");          /* included in our envelope  */
+
+out:
+  curlx_free(from);
+  curlx_free(auth);
+  curlx_free(size);
+
+  if(!result)
+    smtp_state(data, smtpc, SMTP_MAIL);
 
   return result;
 }
 
-/* For DATA response */
-static CURLcode smtp_state_data_resp(struct Curl_easy *data,
+/***********************************************************************
+ *
+ * smtp_perform_rcpt_to()
+ *
+ * Sends a RCPT TO command for a given recipient as part of the message upload
+ * process.
+ */
+static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data,
                                      struct smtp_conn *smtpc,
-                                     int smtpcode,
-                                     smtpstate instate)
+                                     struct SMTP *smtp)
 {
   CURLcode result = CURLE_OK;
-  (void)instate;
+  char *address = NULL;
+  struct hostname host = { NULL, NULL, NULL, NULL };
+  const char *suffix = "";
 
-  if(smtpcode != 354) {
-    failf(data, "DATA failed: %d", smtpcode);
-    result = CURLE_SEND_ERROR;
-  }
-  else {
-    /* Set the progress upload size */
-    Curl_pgrsSetUploadSize(data, data->state.infilesize);
+  /* Parse the recipient mailbox into the local address and hostname parts,
+     converting the hostname to an IDN A-label if necessary */
+  result = smtp_parse_address(smtp->rcpt->data,
+                              &address, &host, &suffix);
+  if(result)
+    return result;
 
-    /* SMTP upload */
-    Curl_xfer_setup_send(data, FIRSTSOCKET);
+  /* Send the RCPT TO command */
+  if(host.name)
+    result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s@%s>%s",
+                           address, host.name, suffix);
+  else
+    /* An invalid mailbox was provided but we will simply let the server worry
+       about that and reply with a 501 error */
+    result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s>%s",
+                           address, suffix);
 
-    /* End of DO phase */
-    smtp_state(data, smtpc, SMTP_STOP);
+  Curl_free_idnconverted_hostname(&host);
+  curlx_free(address);
+
+  if(!result)
+    smtp_state(data, smtpc, SMTP_RCPT);
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * smtp_perform_quit()
+ *
+ * Performs the quit action prior to sclose() being called.
+ */
+static CURLcode smtp_perform_quit(struct Curl_easy *data,
+                                  struct smtp_conn *smtpc)
+{
+  /* Send the QUIT command */
+  CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "QUIT");
+
+  if(!result)
+    smtp_state(data, smtpc, SMTP_QUIT);
+
+  return result;
+}
+
+/* For the initial server greeting */
+static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data,
+                                            struct smtp_conn *smtpc,
+                                            int smtpcode,
+                                            smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  (void)instate;
+
+  if(smtpcode / 100 != 2) {
+    failf(data, "Got unexpected smtp-server response: %d", smtpcode);
+    result = CURLE_WEIRD_SERVER_REPLY;
   }
+  else
+    result = smtp_perform_ehlo(data, smtpc);
 
   return result;
 }
 
-/* For POSTDATA responses, which are received after the entire DATA
-   part has been sent to the server */
-static CURLcode smtp_state_postdata_resp(struct Curl_easy *data,
+/* For STARTTLS responses */
+static CURLcode smtp_state_starttls_resp(struct Curl_easy *data,
                                          struct smtp_conn *smtpc,
                                          int smtpcode,
                                          smtpstate instate)
 {
   CURLcode result = CURLE_OK;
-
   (void)instate;
 
-  if(smtpcode != 250)
-    result = CURLE_WEIRD_SERVER_REPLY;
+  /* Pipelining in response is forbidden. */
+  if(smtpc->pp.overflow)
+    return CURLE_WEIRD_SERVER_REPLY;
 
-  /* End of DONE phase */
-  smtp_state(data, smtpc, SMTP_STOP);
+  if(smtpcode != 220) {
+    if(data->set.use_ssl != CURLUSESSL_TRY) {
+      failf(data, "STARTTLS denied, code %d", smtpcode);
+      result = CURLE_USE_SSL_FAILED;
+    }
+    else
+      result = smtp_perform_authentication(data, smtpc);
+  }
+  else
+    smtp_state(data, smtpc, SMTP_UPGRADETLS);
 
   return result;
 }
 
-static CURLcode smtp_pp_statemachine(struct Curl_easy *data,
-                                     struct connectdata *conn)
+/* For EHLO responses */
+static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data,
+                                     struct smtp_conn *smtpc,
+                                     int smtpcode,
+                                     smtpstate instate)
 {
   CURLcode result = CURLE_OK;
-  int smtpcode;
-  struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN);
-  struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY);
-  size_t nread = 0;
+  const char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf);
+  size_t len = smtpc->pp.nfinal;
 
-  if(!smtpc || !smtp)
-    return CURLE_FAILED_INIT;
+  (void)instate;
 
-  /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */
-upgrade_tls:
-  if(smtpc->state == SMTP_UPGRADETLS) {
-    result = smtp_perform_upgrade_tls(data, smtpc);
-    if(result || (smtpc->state == SMTP_UPGRADETLS))
-      return result;
+  if(smtpcode / 100 != 2 && smtpcode != 1) {
+    if(data->set.use_ssl <= CURLUSESSL_TRY ||
+       Curl_conn_is_ssl(data->conn, FIRSTSOCKET))
+      result = smtp_perform_helo(data, smtpc);
+    else {
+      failf(data, "Remote access denied: %d", smtpcode);
+      result = CURLE_REMOTE_ACCESS_DENIED;
+    }
   }
+  else if(len >= 4) {
+    line += 4;
+    len -= 4;
 
-  /* Flush any data that needs to be sent */
-  if(smtpc->pp.sendleft)
-    return Curl_pp_flushsend(data, &smtpc->pp);
-
-  do {
-    /* Read the response from the server */
-    result = Curl_pp_readresp(data, FIRSTSOCKET, &smtpc->pp,
-                              &smtpcode, &nread);
-    if(result)
-      return result;
+    /* Does the server support the STARTTLS capability? */
+    if(len >= 8 && curl_strnequal(line, "STARTTLS", 8))
+      smtpc->tls_supported = TRUE;
 
-    /* Store the latest response for later retrieval if necessary */
-    if(smtpc->state != SMTP_QUIT && smtpcode != 1)
-      data->info.httpcode = smtpcode;
+    /* Does the server support the SIZE capability? */
+    else if(len >= 4 && curl_strnequal(line, "SIZE", 4))
+      smtpc->size_supported = TRUE;
 
-    if(!smtpcode)
-      break;
+    /* Does the server support the UTF-8 capability? */
+    else if(len >= 8 && curl_strnequal(line, "SMTPUTF8", 8))
+      smtpc->utf8_supported = TRUE;
 
-    /* We have now received a full SMTP server response */
-    switch(smtpc->state) {
-    case SMTP_SERVERGREET:
-      result = smtp_state_servergreet_resp(data, smtpc,
-                                           smtpcode, smtpc->state);
-      break;
+    /* Does the server support authentication? */
+    else if(len >= 5 && curl_strnequal(line, "AUTH ", 5)) {
+      smtpc->auth_supported = TRUE;
 
-    case SMTP_EHLO:
-      result = smtp_state_ehlo_resp(data, smtpc, smtpcode, smtpc->state);
-      break;
+      /* Advance past the AUTH keyword */
+      line += 5;
+      len -= 5;
 
-    case SMTP_HELO:
-      result = smtp_state_helo_resp(data, smtpc, smtpcode, smtpc->state);
-      break;
+      /* Loop through the data line */
+      for(;;) {
+        size_t llen;
+        size_t wordlen;
+        unsigned short mechbit;
 
-    case SMTP_STARTTLS:
-      result = smtp_state_starttls_resp(data, smtpc, smtpcode, smtpc->state);
-      /* During UPGRADETLS, leave the read loop as we need to connect
-       * (e.g. TLS handshake) before we continue sending/receiving. */
-      if(!result && (smtpc->state == SMTP_UPGRADETLS))
-        goto upgrade_tls;
-      break;
-
-    case SMTP_AUTH:
-      result = smtp_state_auth_resp(data, smtpc, smtpcode, smtpc->state);
-      break;
+        while(len &&
+              (*line == ' ' || *line == '\t' ||
+               *line == '\r' || *line == '\n')) {
 
-    case SMTP_COMMAND:
-      result = smtp_state_command_resp(data, smtpc, smtp,
-                                       smtpcode, smtpc->state);
-      break;
+          line++;
+          len--;
+        }
 
-    case SMTP_MAIL:
-      result = smtp_state_mail_resp(data, smtpc, smtp, smtpcode, smtpc->state);
-      break;
+        if(!len)
+          break;
 
-    case SMTP_RCPT:
-      result = smtp_state_rcpt_resp(data, smtpc, smtp, smtpcode, smtpc->state);
-      break;
+        /* Extract the word */
+        for(wordlen = 0; wordlen < len && line[wordlen] != ' ' &&
+              line[wordlen] != '\t' && line[wordlen] != '\r' &&
+              line[wordlen] != '\n';)
+          wordlen++;
 
-    case SMTP_DATA:
-      result = smtp_state_data_resp(data, smtpc, smtpcode, smtpc->state);
-      break;
+        /* Test the word for a matching authentication mechanism */
+        mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
+        if(mechbit && llen == wordlen)
+          smtpc->sasl.authmechs |= mechbit;
 
-    case SMTP_POSTDATA:
-      result = smtp_state_postdata_resp(data, smtpc, smtpcode, smtpc->state);
-      break;
+        line += wordlen;
+        len -= wordlen;
+      }
+    }
 
-    case SMTP_QUIT:
-    default:
-      /* internal error */
-      smtp_state(data, smtpc, SMTP_STOP);
-      break;
+    if(smtpcode != 1) {
+      if(data->set.use_ssl && !Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) {
+        /* We do not have an SSL/TLS connection yet, but SSL is requested */
+        if(smtpc->tls_supported)
+          /* Switch to TLS connection now */
+          result = smtp_perform_starttls(data, smtpc);
+        else if(data->set.use_ssl == CURLUSESSL_TRY)
+          /* Fallback and carry on with authentication */
+          result = smtp_perform_authentication(data, smtpc);
+        else {
+          failf(data, "STARTTLS not supported.");
+          result = CURLE_USE_SSL_FAILED;
+        }
+      }
+      else
+        result = smtp_perform_authentication(data, smtpc);
     }
-  } while(!result && smtpc->state != SMTP_STOP &&
-          Curl_pp_moredata(&smtpc->pp));
+  }
+  else {
+    failf(data, "Unexpectedly short EHLO response");
+    result = CURLE_WEIRD_SERVER_REPLY;
+  }
 
   return result;
 }
 
-/* Called repeatedly until done from multi.c */
-static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done)
+/* For HELO responses */
+static CURLcode smtp_state_helo_resp(struct Curl_easy *data,
+                                     struct smtp_conn *smtpc,
+                                     int smtpcode,
+                                     smtpstate instate)
 {
   CURLcode result = CURLE_OK;
-  struct smtp_conn *smtpc =
-    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
+  (void)instate;
 
-  *done = FALSE;
-  if(!smtpc)
-    return CURLE_FAILED_INIT;
+  if(smtpcode / 100 != 2) {
+    failf(data, "Remote access denied: %d", smtpcode);
+    result = CURLE_REMOTE_ACCESS_DENIED;
+  }
+  else
+    /* End of connect phase */
+    smtp_state(data, smtpc, SMTP_STOP);
 
-  result = Curl_pp_statemach(data, &smtpc->pp, FALSE, FALSE);
-  *done = (smtpc->state == SMTP_STOP);
   return result;
 }
 
-static CURLcode smtp_block_statemach(struct Curl_easy *data,
+/* For SASL authentication responses */
+static CURLcode smtp_state_auth_resp(struct Curl_easy *data,
                                      struct smtp_conn *smtpc,
-                                     bool disconnecting)
+                                     int smtpcode,
+                                     smtpstate instate)
 {
   CURLcode result = CURLE_OK;
+  saslprogress progress;
 
-  while(smtpc->state != SMTP_STOP && !result)
-    result = Curl_pp_statemach(data, &smtpc->pp, TRUE, disconnecting);
+  (void)instate;
 
-  return result;
-}
+  result = Curl_sasl_continue(&smtpc->sasl, data, smtpcode, &progress);
+  if(!result)
+    switch(progress) {
+    case SASL_DONE:
+      smtp_state(data, smtpc, SMTP_STOP);  /* Authenticated */
+      break;
+    case SASL_IDLE:            /* No mechanism left after cancellation */
+      failf(data, "Authentication cancelled");
+      result = CURLE_LOGIN_DENIED;
+      break;
+    default:
+      break;
+    }
 
-/* For the SMTP "protocol connect" and "doing" phases only */
-static CURLcode smtp_pollset(struct Curl_easy *data,
-                             struct easy_pollset *ps)
-{
-  struct smtp_conn *smtpc =
-    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
-  return smtpc ? Curl_pp_pollset(data, &smtpc->pp, ps) : CURLE_OK;
+  return result;
 }
 
-/***********************************************************************
- *
- * smtp_connect()
- *
- * This function should do everything that is to be considered a part of
- * the connection phase.
- *
- * The variable pointed to by 'done' will be TRUE if the protocol-layer
- * connect phase is done when this function returns, or FALSE if not.
- */
-static CURLcode smtp_connect(struct Curl_easy *data, bool *done)
+/* For command responses */
+static CURLcode smtp_state_command_resp(struct Curl_easy *data,
+                                        struct smtp_conn *smtpc,
+                                        struct SMTP *smtp,
+                                        int smtpcode,
+                                        smtpstate instate)
 {
-  struct smtp_conn *smtpc =
-    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
   CURLcode result = CURLE_OK;
+  char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf);
+  size_t len = smtpc->pp.nfinal;
 
-  *done = FALSE; /* default to not done yet */
-  if(!smtpc)
-    return CURLE_FAILED_INIT;
-
-  PINGPONG_SETUP(&smtpc->pp, smtp_pp_statemachine, smtp_endofresp);
+  (void)instate;
 
-  /* Initialize the SASL storage */
-  Curl_sasl_init(&smtpc->sasl, data, &saslsmtp);
+  if((smtp->rcpt && smtpcode / 100 != 2 && smtpcode != 553 && smtpcode != 1) ||
+     (!smtp->rcpt && smtpcode / 100 != 2 && smtpcode != 1)) {
+    failf(data, "Command failed: %d", smtpcode);
+    result = CURLE_WEIRD_SERVER_REPLY;
+  }
+  else {
+    if(!data->req.no_body)
+      result = Curl_client_write(data, CLIENTWRITE_BODY, line, len);
 
-  /* Initialise the pingpong layer */
-  Curl_pp_init(&smtpc->pp, Curl_pgrs_now(data));
+    if(!result && (smtpcode != 1)) {
+      if(smtp->rcpt) {
+        smtp->rcpt = smtp->rcpt->next;
 
-  /* Parse the URL options */
-  result = smtp_parse_url_options(data->conn, smtpc);
-  if(result)
-    return result;
+        if(smtp->rcpt) {
+          /* Send the next command */
+          result = smtp_perform_command(data, smtpc, smtp);
+        }
+        else
+          /* End of DO phase */
+          smtp_state(data, smtpc, SMTP_STOP);
+      }
+      else
+        /* End of DO phase */
+        smtp_state(data, smtpc, SMTP_STOP);
+    }
+  }
 
-  /* Parse the URL path */
-  result = smtp_parse_url_path(data, smtpc);
-  if(result)
-    return result;
+  return result;
+}
 
-  /* Start off waiting for the server greeting response */
-  smtp_state(data, smtpc, SMTP_SERVERGREET);
+/* For MAIL responses */
+static CURLcode smtp_state_mail_resp(struct Curl_easy *data,
+                                     struct smtp_conn *smtpc,
+                                     struct SMTP *smtp,
+                                     int smtpcode,
+                                     smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  (void)instate;
 
-  result = smtp_multi_statemach(data, done);
+  if(smtpcode / 100 != 2) {
+    failf(data, "MAIL failed: %d", smtpcode);
+    result = CURLE_SEND_ERROR;
+  }
+  else
+    /* Start the RCPT TO command */
+    result = smtp_perform_rcpt_to(data, smtpc, smtp);
 
   return result;
 }
 
-/***********************************************************************
- *
- * smtp_done()
- *
- * The DONE function. This does what needs to be done after a single DO has
- * performed.
- *
- * Input argument is already checked for validity.
- */
-static CURLcode smtp_done(struct Curl_easy *data, CURLcode status,
-                          bool premature)
+/* For RCPT responses */
+static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data,
+                                     struct smtp_conn *smtpc,
+                                     struct SMTP *smtp,
+                                     int smtpcode,
+                                     smtpstate instate)
 {
-  struct smtp_conn *smtpc =
-    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
   CURLcode result = CURLE_OK;
-  struct connectdata *conn = data->conn;
-  struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY);
+  bool is_smtp_err = FALSE;
+  bool is_smtp_blocking_err = FALSE;
 
-  (void)premature;
+  (void)instate;
 
-  if(!smtpc)
-    return CURLE_FAILED_INIT;
-  if(!smtp)
-    return CURLE_OK;
+  is_smtp_err = (smtpcode / 100 != 2);
 
-  /* Cleanup our per-request based variables */
-  Curl_safefree(smtp->custom);
+  /* If there is multiple RCPT TO to be issued, it is possible to ignore errors
+     and proceed with only the valid addresses. */
+  is_smtp_blocking_err = (is_smtp_err && !data->set.mail_rcpt_allowfails);
 
-  if(status) {
-    connclose(conn, "SMTP done with bad status"); /* marked for closure */
-    result = status;         /* use the already set error code */
-  }
-  else if(!data->set.connect_only && data->set.mail_rcpt &&
-          (data->state.upload || IS_MIME_POST(data))) {
+  if(is_smtp_err) {
+    /* Remembering the last failure which we can report if all "RCPT TO" have
+       failed and we cannot proceed. */
+    smtp->rcpt_last_error = smtpcode;
 
-    smtp_state(data, smtpc, SMTP_POSTDATA);
+    if(is_smtp_blocking_err) {
+      failf(data, "RCPT failed: %d", smtpcode);
+      result = CURLE_SEND_ERROR;
+    }
+  }
+  else {
+    /* Some RCPT TO commands have succeeded. */
+    smtp->rcpt_had_ok = TRUE;
+  }
 
-    /* Run the state-machine */
-    result = smtp_block_statemach(data, smtpc, FALSE);
+  if(!is_smtp_blocking_err) {
+    smtp->rcpt = smtp->rcpt->next;
+
+    if(smtp->rcpt)
+      /* Send the next RCPT TO command */
+      result = smtp_perform_rcpt_to(data, smtpc, smtp);
+    else {
+      /* We were not able to issue a successful RCPT TO command while going
+         over recipients (potentially multiple). Sending back last error. */
+      if(!smtp->rcpt_had_ok) {
+        failf(data, "RCPT failed: %d (last error)", smtp->rcpt_last_error);
+        result = CURLE_SEND_ERROR;
+      }
+      else {
+        /* Send the DATA command */
+        result = Curl_pp_sendf(data, &smtpc->pp, "%s", "DATA");
+
+        if(!result)
+          smtp_state(data, smtpc, SMTP_DATA);
+      }
+    }
   }
 
-  /* Clear the transfer mode for the next request */
-  smtp->transfer = PPTRANSFER_BODY;
-  CURL_TRC_SMTP(data, "smtp_done(status=%d, premature=%d) -> %d",
-                status, premature, result);
   return result;
 }
 
-/***********************************************************************
- *
- * smtp_perform()
- *
- * This is the actual DO function for SMTP. Transfer a mail, send a command
- * or get some data according to the options previously setup.
- */
-static CURLcode smtp_perform(struct Curl_easy *data,
-                             struct smtp_conn *smtpc,
-                             struct SMTP *smtp,
-                             bool *connected,
-                             bool *dophase_done)
+/* For DATA response */
+static CURLcode smtp_state_data_resp(struct Curl_easy *data,
+                                     struct smtp_conn *smtpc,
+                                     int smtpcode,
+                                     smtpstate instate)
 {
-  /* This is SMTP and no proxy */
   CURLcode result = CURLE_OK;
+  (void)instate;
 
-  CURL_TRC_SMTP(data, "smtp_perform(), start");
-
-  if(data->req.no_body) {
-    /* Requested no body means no transfer */
-    smtp->transfer = PPTRANSFER_INFO;
+  if(smtpcode != 354) {
+    failf(data, "DATA failed: %d", smtpcode);
+    result = CURLE_SEND_ERROR;
   }
+  else {
+    /* Set the progress upload size */
+    Curl_pgrsSetUploadSize(data, data->state.infilesize);
 
-  *dophase_done = FALSE; /* not done yet */
-
-  /* Store the first recipient (or NULL if not specified) */
-  smtp->rcpt = data->set.mail_rcpt;
-
-  /* Track of whether we have successfully sent at least one RCPT TO command */
-  smtp->rcpt_had_ok = FALSE;
+    /* SMTP upload */
+    Curl_xfer_setup_send(data, FIRSTSOCKET);
 
-  /* Track of the last error we have received by sending RCPT TO command */
-  smtp->rcpt_last_error = 0;
+    /* End of DO phase */
+    smtp_state(data, smtpc, SMTP_STOP);
+  }
 
-  /* Initial data character is the first character in line: it is implicitly
-     preceded by a virtual CRLF. */
-  smtp->trailing_crlf = TRUE;
-  smtp->eob = 2;
+  return result;
+}
 
-  /* Start the first command in the DO phase */
-  if((data->state.upload || IS_MIME_POST(data)) && data->set.mail_rcpt)
-    /* MAIL transfer */
-    result = smtp_perform_mail(data, smtpc, smtp);
-  else
-    /* SMTP based command (VRFY, EXPN, NOOP, RSET or HELP) */
-    result = smtp_perform_command(data, smtpc, smtp);
+/* For POSTDATA responses, which are received after the entire DATA
+   part has been sent to the server */
+static CURLcode smtp_state_postdata_resp(struct Curl_easy *data,
+                                         struct smtp_conn *smtpc,
+                                         int smtpcode,
+                                         smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
 
-  if(result)
-    goto out;
+  (void)instate;
 
-  /* Run the state-machine */
-  result = smtp_multi_statemach(data, dophase_done);
+  if(smtpcode != 250)
+    result = CURLE_WEIRD_SERVER_REPLY;
 
-  *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET);
+  /* End of DONE phase */
+  smtp_state(data, smtpc, SMTP_STOP);
 
-out:
-  CURL_TRC_SMTP(data, "smtp_perform() -> %d, connected=%d, done=%d",
-                result, *connected, *dophase_done);
   return result;
 }
 
-/***********************************************************************
- *
- * smtp_do()
- *
- * This function is registered as 'curl_do' function. It decodes the path
- * parts etc as a wrapper to the actual DO function (smtp_perform).
- *
- * The input argument is already checked for validity.
- */
-static CURLcode smtp_do(struct Curl_easy *data, bool *done)
+static CURLcode smtp_pp_statemachine(struct Curl_easy *data,
+                                     struct connectdata *conn)
 {
-  struct smtp_conn *smtpc =
-    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
-  struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY);
   CURLcode result = CURLE_OK;
+  int smtpcode;
+  struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN);
+  struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY);
+  size_t nread = 0;
 
-  DEBUGASSERT(data);
-  DEBUGASSERT(data->conn);
-  *done = FALSE; /* default to false */
   if(!smtpc || !smtp)
     return CURLE_FAILED_INIT;
 
-  /* Parse the custom request */
-  result = smtp_parse_custom_request(data, smtp);
-  if(result)
-    return result;
+  /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */
+upgrade_tls:
+  if(smtpc->state == SMTP_UPGRADETLS) {
+    result = smtp_perform_upgrade_tls(data, smtpc);
+    if(result || (smtpc->state == SMTP_UPGRADETLS))
+      return result;
+  }
 
-  result = smtp_regular_transfer(data, smtpc, smtp, done);
-  CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", result, *done);
-  return result;
-}
+  /* Flush any data that needs to be sent */
+  if(smtpc->pp.sendleft)
+    return Curl_pp_flushsend(data, &smtpc->pp);
 
-/***********************************************************************
- *
- * smtp_disconnect()
- *
- * Disconnect from an SMTP server. Cleanup protocol-specific per-connection
- * resources. BLOCKING.
- */
-static CURLcode smtp_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                bool dead_connection)
-{
-  struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN);
+  do {
+    /* Read the response from the server */
+    result = Curl_pp_readresp(data, FIRSTSOCKET, &smtpc->pp,
+                              &smtpcode, &nread);
+    if(result)
+      return result;
 
-  (void)data;
-  if(!smtpc)
-    return CURLE_FAILED_INIT;
+    /* Store the latest response for later retrieval if necessary */
+    if(smtpc->state != SMTP_QUIT && smtpcode != 1)
+      data->info.httpcode = smtpcode;
 
-  /* We cannot send quit unconditionally. If this connection is stale or
-     bad in any way, sending quit and waiting around here will make the
-     disconnect wait in vain and cause more problems than we need to. */
+    if(!smtpcode)
+      break;
 
-  if(!dead_connection && conn->bits.protoconnstart &&
-     !Curl_pp_needs_flush(data, &smtpc->pp)) {
-    if(!smtp_perform_quit(data, smtpc))
-      (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */
-  }
+    /* We have now received a full SMTP server response */
+    switch(smtpc->state) {
+    case SMTP_SERVERGREET:
+      result = smtp_state_servergreet_resp(data, smtpc,
+                                           smtpcode, smtpc->state);
+      break;
 
-  CURL_TRC_SMTP(data, "smtp_disconnect(), finished");
-  return CURLE_OK;
-}
+    case SMTP_EHLO:
+      result = smtp_state_ehlo_resp(data, smtpc, smtpcode, smtpc->state);
+      break;
 
-/* Call this when the DO phase has completed */
-static CURLcode smtp_dophase_done(struct Curl_easy *data,
-                                  struct SMTP *smtp,
-                                  bool connected)
-{
-  (void)connected;
+    case SMTP_HELO:
+      result = smtp_state_helo_resp(data, smtpc, smtpcode, smtpc->state);
+      break;
 
-  if(smtp->transfer != PPTRANSFER_BODY)
-    /* no data to transfer */
-    Curl_xfer_setup_nop(data);
+    case SMTP_STARTTLS:
+      result = smtp_state_starttls_resp(data, smtpc, smtpcode, smtpc->state);
+      /* During UPGRADETLS, leave the read loop as we need to connect
+       * (e.g. TLS handshake) before we continue sending/receiving. */
+      if(!result && (smtpc->state == SMTP_UPGRADETLS))
+        goto upgrade_tls;
+      break;
 
-  return CURLE_OK;
-}
+    case SMTP_AUTH:
+      result = smtp_state_auth_resp(data, smtpc, smtpcode, smtpc->state);
+      break;
 
-/* Called from multi.c while DOing */
-static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done)
-{
-  struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY);
-  CURLcode result;
+    case SMTP_COMMAND:
+      result = smtp_state_command_resp(data, smtpc, smtp,
+                                       smtpcode, smtpc->state);
+      break;
 
-  if(!smtp)
-    return CURLE_FAILED_INIT;
-  result = smtp_multi_statemach(data, dophase_done);
-  if(result)
-    DEBUGF(infof(data, "DO phase failed"));
-  else if(*dophase_done) {
-    result = smtp_dophase_done(data, smtp, FALSE /* not connected */);
+    case SMTP_MAIL:
+      result = smtp_state_mail_resp(data, smtpc, smtp, smtpcode, smtpc->state);
+      break;
 
-    DEBUGF(infof(data, "DO phase is complete"));
-  }
+    case SMTP_RCPT:
+      result = smtp_state_rcpt_resp(data, smtpc, smtp, smtpcode, smtpc->state);
+      break;
 
-  CURL_TRC_SMTP(data, "smtp_doing() -> %d, done=%d", result, *dophase_done);
-  return result;
-}
-
-/***********************************************************************
- *
- * smtp_regular_transfer()
- *
- * The input argument is already checked for validity.
- *
- * Performs all commands done before a regular transfer between a local and a
- * remote host.
- */
-static CURLcode smtp_regular_transfer(struct Curl_easy *data,
-                                      struct smtp_conn *smtpc,
-                                      struct SMTP *smtp,
-                                      bool *dophase_done)
-{
-  CURLcode result = CURLE_OK;
-  bool connected = FALSE;
-
-  /* Make sure size is unknown at this point */
-  data->req.size = -1;
-
-  /* Set the progress data */
-  Curl_pgrsReset(data);
+    case SMTP_DATA:
+      result = smtp_state_data_resp(data, smtpc, smtpcode, smtpc->state);
+      break;
 
-  /* Carry out the perform */
-  result = smtp_perform(data, smtpc, smtp, &connected, dophase_done);
+    case SMTP_POSTDATA:
+      result = smtp_state_postdata_resp(data, smtpc, smtpcode, smtpc->state);
+      break;
 
-  /* Perform post DO phase operations if necessary */
-  if(!result && *dophase_done)
-    result = smtp_dophase_done(data, smtp, connected);
+    case SMTP_QUIT:
+    default:
+      /* internal error */
+      smtp_state(data, smtpc, SMTP_STOP);
+      break;
+    }
+  } while(!result && smtpc->state != SMTP_STOP &&
+          Curl_pp_moredata(&smtpc->pp));
 
-  CURL_TRC_SMTP(data, "smtp_regular_transfer() -> %d, done=%d",
-                result, *dophase_done);
   return result;
 }
 
-static void smtp_easy_dtor(void *key, size_t klen, void *entry)
+/* Called repeatedly until done from multi.c */
+static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done)
 {
-  struct SMTP *smtp = entry;
-  (void)key;
-  (void)klen;
-  curlx_free(smtp);
-}
+  CURLcode result = CURLE_OK;
+  struct smtp_conn *smtpc =
+    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
 
-static void smtp_conn_dtor(void *key, size_t klen, void *entry)
-{
-  struct smtp_conn *smtpc = entry;
-  (void)key;
-  (void)klen;
-  Curl_pp_disconnect(&smtpc->pp);
-  Curl_safefree(smtpc->domain);
-  curlx_free(smtpc);
+  *done = FALSE;
+  if(!smtpc)
+    return CURLE_FAILED_INIT;
+
+  result = Curl_pp_statemach(data, &smtpc->pp, FALSE, FALSE);
+  *done = (smtpc->state == SMTP_STOP);
+  return result;
 }
 
-static CURLcode smtp_setup_connection(struct Curl_easy *data,
-                                      struct connectdata *conn)
+static CURLcode smtp_block_statemach(struct Curl_easy *data,
+                                     struct smtp_conn *smtpc,
+                                     bool disconnecting)
 {
-  struct smtp_conn *smtpc;
-  struct SMTP *smtp;
   CURLcode result = CURLE_OK;
 
-  smtpc = curlx_calloc(1, sizeof(*smtpc));
-  if(!smtpc ||
-     Curl_conn_meta_set(conn, CURL_META_SMTP_CONN, smtpc, smtp_conn_dtor)) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
-
-  smtp = curlx_calloc(1, sizeof(*smtp));
-  if(!smtp ||
-     Curl_meta_set(data, CURL_META_SMTP_EASY, smtp, smtp_easy_dtor))
-    result = CURLE_OUT_OF_MEMORY;
+  while(smtpc->state != SMTP_STOP && !result)
+    result = Curl_pp_statemach(data, &smtpc->pp, TRUE, disconnecting);
 
-out:
-  CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", result);
   return result;
 }
 
+/* For the SMTP "protocol connect" and "doing" phases only */
+static CURLcode smtp_pollset(struct Curl_easy *data,
+                             struct easy_pollset *ps)
+{
+  struct smtp_conn *smtpc =
+    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
+  return smtpc ? Curl_pp_pollset(data, &smtpc->pp, ps) : CURLE_OK;
+}
+
+/* SASL parameters for the smtp protocol */
+static const struct SASLproto saslsmtp = {
+  "smtp",               /* The service name */
+  smtp_perform_auth,    /* Send authentication command */
+  smtp_continue_auth,   /* Send authentication continuation */
+  smtp_cancel_auth,     /* Cancel authentication */
+  smtp_get_message,     /* Get SASL response message */
+  512 - 8,              /* Max line len - strlen("AUTH ") - 1 space - crlf */
+  334,                  /* Code received when continuation is expected */
+  235,                  /* Code to receive upon authentication success */
+  SASL_AUTH_DEFAULT,    /* Default mechanisms */
+  SASL_FLAG_BASE64      /* Configuration flags */
+};
+
 /***********************************************************************
  *
- * smtp_parse_url_options()
+ * smtp_connect()
  *
- * Parse the URL login options.
+ * This function should do everything that is to be considered a part of
+ * the connection phase.
+ *
+ * The variable pointed to by 'done' will be TRUE if the protocol-layer
+ * connect phase is done when this function returns, or FALSE if not.
  */
-static CURLcode smtp_parse_url_options(struct connectdata *conn,
-                                       struct smtp_conn *smtpc)
+static CURLcode smtp_connect(struct Curl_easy *data, bool *done)
 {
+  struct smtp_conn *smtpc =
+    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
   CURLcode result = CURLE_OK;
-  const char *ptr = conn->options;
 
-  while(!result && ptr && *ptr) {
-    const char *key = ptr;
-    const char *value;
+  *done = FALSE; /* default to not done yet */
+  if(!smtpc)
+    return CURLE_FAILED_INIT;
 
-    while(*ptr && *ptr != '=')
-      ptr++;
+  PINGPONG_SETUP(&smtpc->pp, smtp_pp_statemachine, smtp_endofresp);
 
-    value = ptr + 1;
+  /* Initialize the SASL storage */
+  Curl_sasl_init(&smtpc->sasl, data, &saslsmtp);
 
-    while(*ptr && *ptr != ';')
-      ptr++;
+  /* Initialise the pingpong layer */
+  Curl_pp_init(&smtpc->pp, Curl_pgrs_now(data));
 
-    if(curl_strnequal(key, "AUTH=", 5))
-      result = Curl_sasl_parse_url_auth_option(&smtpc->sasl,
-                                               value, ptr - value);
-    else
-      result = CURLE_URL_MALFORMAT;
+  /* Parse the URL options */
+  result = smtp_parse_url_options(data->conn, smtpc);
+  if(result)
+    return result;
 
-    if(*ptr == ';')
-      ptr++;
-  }
+  /* Parse the URL path */
+  result = smtp_parse_url_path(data, smtpc);
+  if(result)
+    return result;
+
+  /* Start off waiting for the server greeting response */
+  smtp_state(data, smtpc, SMTP_SERVERGREET);
+
+  result = smtp_multi_statemach(data, done);
 
   return result;
 }
 
 /***********************************************************************
  *
- * smtp_parse_url_path()
+ * smtp_done()
  *
- * Parse the URL path into separate path components.
+ * The DONE function. This does what needs to be done after a single DO has
+ * performed.
+ *
+ * Input argument is already checked for validity.
  */
-static CURLcode smtp_parse_url_path(struct Curl_easy *data,
-                                    struct smtp_conn *smtpc)
+static CURLcode smtp_done(struct Curl_easy *data, CURLcode status,
+                          bool premature)
 {
-  /* The SMTP struct is already initialised in smtp_connect() */
-  const char *path = &data->state.up.path[1]; /* skip leading path */
-  char localhost[HOSTNAME_MAX + 1];
+  struct smtp_conn *smtpc =
+    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
+  CURLcode result = CURLE_OK;
+  struct connectdata *conn = data->conn;
+  struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY);
 
-  /* Calculate the path if necessary */
-  if(!*path) {
-    if(!Curl_gethostname(localhost, sizeof(localhost)))
-      path = localhost;
-    else
-      path = "localhost";
+  (void)premature;
+
+  if(!smtpc)
+    return CURLE_FAILED_INIT;
+  if(!smtp)
+    return CURLE_OK;
+
+  /* Cleanup our per-request based variables */
+  Curl_safefree(smtp->custom);
+
+  if(status) {
+    connclose(conn, "SMTP done with bad status"); /* marked for closure */
+    result = status;         /* use the already set error code */
   }
+  else if(!data->set.connect_only && data->set.mail_rcpt &&
+          (data->state.upload || IS_MIME_POST(data))) {
 
-  /* URL decode the path and use it as the domain in our EHLO */
-  return Curl_urldecode(path, 0, &smtpc->domain, NULL, REJECT_CTRL);
+    smtp_state(data, smtpc, SMTP_POSTDATA);
+
+    /* Run the state-machine */
+    result = smtp_block_statemach(data, smtpc, FALSE);
+  }
+
+  /* Clear the transfer mode for the next request */
+  smtp->transfer = PPTRANSFER_BODY;
+  CURL_TRC_SMTP(data, "smtp_done(status=%d, premature=%d) -> %d",
+                status, premature, result);
+  return result;
 }
 
 /***********************************************************************
  *
- * smtp_parse_custom_request()
+ * smtp_perform()
  *
- * Parse the custom request.
+ * This is the actual DO function for SMTP. Transfer a mail, send a command
+ * or get some data according to the options previously setup.
  */
-static CURLcode smtp_parse_custom_request(struct Curl_easy *data,
-                                          struct SMTP *smtp)
+static CURLcode smtp_perform(struct Curl_easy *data,
+                             struct smtp_conn *smtpc,
+                             struct SMTP *smtp,
+                             bool *connected,
+                             bool *dophase_done)
 {
+  /* This is SMTP and no proxy */
   CURLcode result = CURLE_OK;
-  const char *custom = data->set.str[STRING_CUSTOMREQUEST];
 
-  /* URL decode the custom request */
-  if(custom)
-    result = Curl_urldecode(custom, 0, &smtp->custom, NULL, REJECT_CTRL);
+  CURL_TRC_SMTP(data, "smtp_perform(), start");
 
-  return result;
-}
+  if(data->req.no_body) {
+    /* Requested no body means no transfer */
+    smtp->transfer = PPTRANSFER_INFO;
+  }
 
-/***********************************************************************
- *
- * smtp_parse_address()
- *
- * Parse the fully qualified mailbox address into a local address part and the
- * hostname, converting the hostname to an IDN A-label, as per RFC-5890, if
- * necessary.
- *
- * Parameters:
- *
- * conn  [in]              - The connection handle.
- * fqma  [in]              - The fully qualified mailbox address (which may or
- *                           may not contain UTF-8 characters).
- * address        [in/out] - A new allocated buffer which holds the local
- *                           address part of the mailbox. This buffer must be
- *                           free'ed by the caller.
- * host           [in/out] - The hostname structure that holds the original,
- *                           and optionally encoded, hostname.
- *                           Curl_free_idnconverted_hostname() must be called
- *                           once the caller has finished with the structure.
- *
- * Returns CURLE_OK on success.
- *
- * Notes:
- *
- * Should a UTF-8 hostname require conversion to IDN ACE and we cannot honor
- * that conversion then we shall return success. This allow the caller to send
- * the data to the server as a U-label (as per RFC-6531 sect. 3.2).
- *
- * If an mailbox '@' separator cannot be located then the mailbox is considered
- * to be either a local mailbox or an invalid mailbox (depending on what the
- * calling function deems it to be) then the input will simply be returned in
- * the address part with the hostname being NULL.
- */
-static CURLcode smtp_parse_address(const char *fqma, char **address,
-                                   struct hostname *host, const char **suffix)
-{
-  CURLcode result = CURLE_OK;
-  size_t length;
-  char *addressend;
+  *dophase_done = FALSE; /* not done yet */
 
-  /* Duplicate the fully qualified email address so we can manipulate it,
-     ensuring it does not contain the delimiters if specified */
-  char *dup = curlx_strdup(fqma[0] == '<' ? fqma + 1 : fqma);
-  if(!dup)
-    return CURLE_OUT_OF_MEMORY;
+  /* Store the first recipient (or NULL if not specified) */
+  smtp->rcpt = data->set.mail_rcpt;
 
-  if(fqma[0] != '<') {
-    length = strlen(dup);
-    if(length) {
-      if(dup[length - 1] == '>')
-        dup[length - 1] = '\0';
-    }
-  }
-  else {
-    addressend = strrchr(dup, '>');
-    if(addressend) {
-      *addressend = '\0';
-      *suffix = addressend + 1;
-    }
-  }
+  /* Track of whether we have successfully sent at least one RCPT TO command */
+  smtp->rcpt_had_ok = FALSE;
 
-  /* Extract the hostname from the address (if we can) */
-  host->name = strpbrk(dup, "@");
-  if(host->name) {
-    *host->name = '\0';
-    host->name = host->name + 1;
+  /* Track of the last error we have received by sending RCPT TO command */
+  smtp->rcpt_last_error = 0;
 
-    /* Attempt to convert the hostname to IDN ACE */
-    (void)Curl_idnconvert_hostname(host);
+  /* Initial data character is the first character in line: it is implicitly
+     preceded by a virtual CRLF. */
+  smtp->trailing_crlf = TRUE;
+  smtp->eob = 2;
 
-    /* If Curl_idnconvert_hostname() fails then we shall attempt to continue
-       and send the hostname using UTF-8 rather than as 7-bit ACE (which is
-       our preference) */
-  }
+  /* Start the first command in the DO phase */
+  if((data->state.upload || IS_MIME_POST(data)) && data->set.mail_rcpt)
+    /* MAIL transfer */
+    result = smtp_perform_mail(data, smtpc, smtp);
+  else
+    /* SMTP based command (VRFY, EXPN, NOOP, RSET or HELP) */
+    result = smtp_perform_command(data, smtpc, smtp);
 
-  /* Extract the local address from the mailbox */
-  *address = dup;
+  if(result)
+    goto out;
+
+  /* Run the state-machine */
+  result = smtp_multi_statemach(data, dophase_done);
+
+  *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET);
 
+out:
+  CURL_TRC_SMTP(data, "smtp_perform() -> %d, connected=%d, done=%d",
+                result, *connected, *dophase_done);
   return result;
 }
 
-struct cr_eob_ctx {
-  struct Curl_creader super;
-  struct bufq buf;
-  size_t n_eob; /* how many EOB bytes we matched so far */
-  size_t eob;       /* Number of bytes of the EOB (End Of Body) that
-                       have been received so far */
-  BIT(read_eos);  /* we read an EOS from the next reader */
-  BIT(processed_eos);  /* we read and processed an EOS */
-  BIT(eos);       /* we have returned an EOS */
-};
-
-static CURLcode cr_eob_init(struct Curl_easy *data,
-                            struct Curl_creader *reader)
+/* Call this when the DO phase has completed */
+static CURLcode smtp_dophase_done(struct Curl_easy *data,
+                                  struct SMTP *smtp,
+                                  bool connected)
 {
-  struct cr_eob_ctx *ctx = reader->ctx;
-  (void)data;
-  /* The first char we read is the first on a line, as if we had
-   * read CRLF just before */
-  ctx->n_eob = 2;
-  Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT);
+  (void)connected;
+
+  if(smtp->transfer != PPTRANSFER_BODY)
+    /* no data to transfer */
+    Curl_xfer_setup_nop(data);
+
   return CURLE_OK;
 }
 
-static void cr_eob_close(struct Curl_easy *data, struct Curl_creader *reader)
+/***********************************************************************
+ *
+ * smtp_regular_transfer()
+ *
+ * The input argument is already checked for validity.
+ *
+ * Performs all commands done before a regular transfer between a local and a
+ * remote host.
+ */
+static CURLcode smtp_regular_transfer(struct Curl_easy *data,
+                                      struct smtp_conn *smtpc,
+                                      struct SMTP *smtp,
+                                      bool *dophase_done)
 {
-  struct cr_eob_ctx *ctx = reader->ctx;
-  (void)data;
-  Curl_bufq_free(&ctx->buf);
-}
+  CURLcode result = CURLE_OK;
+  bool connected = FALSE;
 
-/* this is the 5-bytes End-Of-Body marker for SMTP */
-#define SMTP_EOB          "\r\n.\r\n"
-#define SMTP_EOB_FIND_LEN 3
+  /* Make sure size is unknown at this point */
+  data->req.size = -1;
 
-/* client reader doing SMTP End-Of-Body escaping. */
-static CURLcode cr_eob_read(struct Curl_easy *data,
-                            struct Curl_creader *reader,
-                            char *buf, size_t blen,
-                            size_t *pnread, bool *peos)
+  /* Set the progress data */
+  Curl_pgrsReset(data);
+
+  /* Carry out the perform */
+  result = smtp_perform(data, smtpc, smtp, &connected, dophase_done);
+
+  /* Perform post DO phase operations if necessary */
+  if(!result && *dophase_done)
+    result = smtp_dophase_done(data, smtp, connected);
+
+  CURL_TRC_SMTP(data, "smtp_regular_transfer() -> %d, done=%d",
+                result, *dophase_done);
+  return result;
+}
+
+/***********************************************************************
+ *
+ * smtp_do()
+ *
+ * This function is registered as 'curl_do' function. It decodes the path
+ * parts etc as a wrapper to the actual DO function (smtp_perform).
+ *
+ * The input argument is already checked for validity.
+ */
+static CURLcode smtp_do(struct Curl_easy *data, bool *done)
 {
-  struct cr_eob_ctx *ctx = reader->ctx;
+  struct smtp_conn *smtpc =
+    Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN);
+  struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY);
   CURLcode result = CURLE_OK;
-  size_t nread, i, start, n;
-  bool eos;
 
-  if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) {
-    /* Get more and convert it when needed */
-    result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos);
-    CURL_TRC_SMTP(data, "cr_eob_read, next_read(len=%zu) -> %d, %zu eos=%d",
-                  blen, result, nread, eos);
-    if(result)
-      return result;
+  DEBUGASSERT(data);
+  DEBUGASSERT(data->conn);
+  *done = FALSE; /* default to false */
+  if(!smtpc || !smtp)
+    return CURLE_FAILED_INIT;
 
-    ctx->read_eos = eos;
-    if(nread) {
-      if(!ctx->n_eob && !memchr(buf, SMTP_EOB[0], nread)) {
-        /* not in the middle of a match, no EOB start found, just pass */
-        *pnread = nread;
-        *peos = FALSE;
-        return CURLE_OK;
-      }
-      /* scan for EOB (continuation) and convert */
-      for(i = start = 0; i < nread; ++i) {
-        if(ctx->n_eob >= SMTP_EOB_FIND_LEN) {
-          /* matched the EOB prefix and seeing additional char, add '.' */
-          result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n);
-          if(result)
-            return result;
-          result = Curl_bufq_cwrite(&ctx->buf, ".", 1, &n);
-          if(result)
-            return result;
-          ctx->n_eob = 0;
-          start = i;
-          if(data->state.infilesize > 0)
-            data->state.infilesize++;
-        }
+  /* Parse the custom request */
+  result = smtp_parse_custom_request(data, smtp);
+  if(result)
+    return result;
 
-        if(buf[i] != SMTP_EOB[ctx->n_eob])
-          ctx->n_eob = 0;
+  result = smtp_regular_transfer(data, smtpc, smtp, done);
+  CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", result, *done);
+  return result;
+}
 
-        if(buf[i] == SMTP_EOB[ctx->n_eob]) {
-          /* matching another char of the EOB */
-          ++ctx->n_eob;
-        }
-      }
+/***********************************************************************
+ *
+ * smtp_disconnect()
+ *
+ * Disconnect from an SMTP server. Cleanup protocol-specific per-connection
+ * resources. BLOCKING.
+ */
+static CURLcode smtp_disconnect(struct Curl_easy *data,
+                                struct connectdata *conn,
+                                bool dead_connection)
+{
+  struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN);
 
-      /* add any remainder to buf */
-      if(start < nread) {
-        result = Curl_bufq_cwrite(&ctx->buf, buf + start, nread - start, &n);
-        if(result)
-          return result;
-      }
-    }
-  }
+  (void)data;
+  if(!smtpc)
+    return CURLE_FAILED_INIT;
 
-  *peos = FALSE;
+  /* We cannot send quit unconditionally. If this connection is stale or
+     bad in any way, sending quit and waiting around here will make the
+     disconnect wait in vain and cause more problems than we need to. */
 
-  if(ctx->read_eos && !ctx->processed_eos) {
-    /* if we last matched a CRLF or if the data was empty, add ".\r\n"
-     * to end the body. If we sent something and it did not end with "\r\n",
-     * add "\r\n.\r\n" to end the body */
-    const char *eob = SMTP_EOB;
-    CURL_TRC_SMTP(data, "auto-ending mail body with '\\r\\n.\\r\\n'");
-    switch(ctx->n_eob) {
-    case 2:
-      /* seen a CRLF at the end, just add the remainder */
-      eob = &SMTP_EOB[2];
-      break;
-    case 3:
-      /* ended with '\r\n.', we should escape the last '.' */
-      eob = "." SMTP_EOB;
-      break;
-    default:
-      break;
-    }
-    result = Curl_bufq_cwrite(&ctx->buf, eob, strlen(eob), &n);
-    if(result)
-      return result;
-    ctx->processed_eos = TRUE;
+  if(!dead_connection && conn->bits.protoconnstart &&
+     !Curl_pp_needs_flush(data, &smtpc->pp)) {
+    if(!smtp_perform_quit(data, smtpc))
+      (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */
   }
 
-  if(!Curl_bufq_is_empty(&ctx->buf)) {
-    result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread);
-  }
-  else
-    *pnread = 0;
+  CURL_TRC_SMTP(data, "smtp_disconnect(), finished");
+  return CURLE_OK;
+}
 
-  if(ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) {
-    /* no more data, read all, done. */
-    CURL_TRC_SMTP(data, "mail body complete, returning EOS");
-    ctx->eos = TRUE;
+/* Called from multi.c while DOing */
+static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done)
+{
+  struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY);
+  CURLcode result;
+
+  if(!smtp)
+    return CURLE_FAILED_INIT;
+  result = smtp_multi_statemach(data, dophase_done);
+  if(result)
+    DEBUGF(infof(data, "DO phase failed"));
+  else if(*dophase_done) {
+    result = smtp_dophase_done(data, smtp, FALSE /* not connected */);
+
+    DEBUGF(infof(data, "DO phase is complete"));
   }
-  *peos = ctx->eos;
-  DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d",
-         blen, result, *pnread, *peos));
+
+  CURL_TRC_SMTP(data, "smtp_doing() -> %d, done=%d", result, *dophase_done);
   return result;
 }
 
-static curl_off_t cr_eob_total_length(struct Curl_easy *data,
-                                      struct Curl_creader *reader)
+static void smtp_easy_dtor(void *key, size_t klen, void *entry)
 {
-  /* this reader changes length depending on input */
-  (void)data;
-  (void)reader;
-  return -1;
+  struct SMTP *smtp = entry;
+  (void)key;
+  (void)klen;
+  curlx_free(smtp);
 }
 
-static const struct Curl_crtype cr_eob = {
-  "cr-smtp-eob",
-  cr_eob_init,
-  cr_eob_read,
-  cr_eob_close,
-  Curl_creader_def_needs_rewind,
-  cr_eob_total_length,
-  Curl_creader_def_resume_from,
-  Curl_creader_def_cntrl,
-  Curl_creader_def_is_paused,
-  Curl_creader_def_done,
-  sizeof(struct cr_eob_ctx)
-};
+static void smtp_conn_dtor(void *key, size_t klen, void *entry)
+{
+  struct smtp_conn *smtpc = entry;
+  (void)key;
+  (void)klen;
+  Curl_pp_disconnect(&smtpc->pp);
+  Curl_safefree(smtpc->domain);
+  curlx_free(smtpc);
+}
 
-static CURLcode cr_eob_add(struct Curl_easy *data)
+static CURLcode smtp_setup_connection(struct Curl_easy *data,
+                                      struct connectdata *conn)
 {
-  struct Curl_creader *reader = NULL;
-  CURLcode result;
+  struct smtp_conn *smtpc;
+  struct SMTP *smtp;
+  CURLcode result = CURLE_OK;
 
-  result = Curl_creader_create(&reader, data, &cr_eob, CURL_CR_CONTENT_ENCODE);
-  if(!result)
-    result = Curl_creader_add(data, reader);
+  smtpc = curlx_calloc(1, sizeof(*smtpc));
+  if(!smtpc ||
+     Curl_conn_meta_set(conn, CURL_META_SMTP_CONN, smtpc, smtp_conn_dtor)) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
 
-  if(result && reader)
-    Curl_creader_free(data, reader);
+  smtp = curlx_calloc(1, sizeof(*smtp));
+  if(!smtp ||
+     Curl_meta_set(data, CURL_META_SMTP_EASY, smtp, smtp_easy_dtor))
+    result = CURLE_OUT_OF_MEMORY;
+
+out:
+  CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", result);
   return result;
 }
 
+/*
+ * SMTP protocol handler.
+ */
+const struct Curl_handler Curl_handler_smtp = {
+  "smtp",                           /* scheme */
+  smtp_setup_connection,            /* setup_connection */
+  smtp_do,                          /* do_it */
+  smtp_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  smtp_connect,                     /* connect_it */
+  smtp_multi_statemach,             /* connecting */
+  smtp_doing,                       /* doing */
+  smtp_pollset,                     /* proto_pollset */
+  smtp_pollset,                     /* doing_pollset */
+  ZERO_NULL,                        /* domore_pollset */
+  ZERO_NULL,                        /* perform_pollset */
+  smtp_disconnect,                  /* disconnect */
+  ZERO_NULL,                        /* write_resp */
+  ZERO_NULL,                        /* write_resp_hd */
+  ZERO_NULL,                        /* connection_check */
+  ZERO_NULL,                        /* attach connection */
+  ZERO_NULL,                        /* follow */
+  PORT_SMTP,                        /* defport */
+  CURLPROTO_SMTP,                   /* protocol */
+  CURLPROTO_SMTP,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */
+    PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE
+};
+
+#ifdef USE_SSL
+/*
+ * SMTPS protocol handler.
+ */
+const struct Curl_handler Curl_handler_smtps = {
+  "smtps",                          /* scheme */
+  smtp_setup_connection,            /* setup_connection */
+  smtp_do,                          /* do_it */
+  smtp_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  smtp_connect,                     /* connect_it */
+  smtp_multi_statemach,             /* connecting */
+  smtp_doing,                       /* doing */
+  smtp_pollset,                     /* proto_pollset */
+  smtp_pollset,                     /* doing_pollset */
+  ZERO_NULL,                        /* domore_pollset */
+  ZERO_NULL,                        /* perform_pollset */
+  smtp_disconnect,                  /* disconnect */
+  ZERO_NULL,                        /* write_resp */
+  ZERO_NULL,                        /* write_resp_hd */
+  ZERO_NULL,                        /* connection_check */
+  ZERO_NULL,                        /* attach connection */
+  ZERO_NULL,                        /* follow */
+  PORT_SMTPS,                       /* defport */
+  CURLPROTO_SMTPS,                  /* protocol */
+  CURLPROTO_SMTP,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
+    PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE
+};
+#endif
+
 #endif /* CURL_DISABLE_SMTP */