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);
+}
/***********************************************************************
*
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()
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;
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)
{
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 */
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;
{ "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 '.'.
*/
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;
}
}
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()
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()
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)
{
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;
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 */
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;
/***********************************************************************
*
- * 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 */