Webserver+3d print

Dependents:   Nucleo

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers smtp_client.c Source File

smtp_client.c

Go to the documentation of this file.
00001 /**
00002  * @file smtp_client.c
00003  * @brief SMTP client (Simple Mail Transfer Protocol)
00004  *
00005  * @section License
00006  *
00007  * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved.
00008  *
00009  * This file is part of CycloneTCP Open.
00010  *
00011  * This program is free software; you can redistribute it and/or
00012  * modify it under the terms of the GNU General Public License
00013  * as published by the Free Software Foundation; either version 2
00014  * of the License, or (at your option) any later version.
00015  *
00016  * This program is distributed in the hope that it will be useful,
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  * GNU General Public License for more details.
00020  *
00021  * You should have received a copy of the GNU General Public License
00022  * along with this program; if not, write to the Free Software Foundation,
00023  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00024  *
00025  * @section Description
00026  *
00027  * SMTP is designed as a mail transport and delivery protocol. Refer to
00028  * the following RFCs for complete details:
00029  * - RFC 5321: Simple Mail Transfer Protocol
00030  * - RFC 4954: SMTP Service Extension for Authentication
00031  * - RFC 3207: SMTP Service Extension for Secure SMTP over TLS
00032  *
00033  * @author Oryx Embedded SARL (www.oryx-embedded.com)
00034  * @version 1.7.6
00035  **/
00036 
00037 //Switch to the appropriate trace level
00038 #define TRACE_LEVEL SMTP_TRACE_LEVEL
00039 
00040 //Dependencies
00041 #include <string.h>
00042 #include <ctype.h>
00043 #include <stdlib.h>
00044 #include "core/net.h"
00045 #include "smtp/smtp_client.h"
00046 #include "core/socket.h"
00047 #include "str.h"
00048 #include "debug.h"
00049 
00050 //Check TCP/IP stack configuration
00051 #if (SMTP_CLIENT_SUPPORT == ENABLED)
00052 
00053 
00054 /**
00055  * @brief Send a mail to the specified recipients
00056  * @param[in] authInfo Authentication information
00057  * @param[in] mail Mail contents
00058  * @return Error code
00059  **/
00060 
00061 error_t smtpSendMail(const SmtpAuthInfo *authInfo, const SmtpMail *mail)
00062 {
00063    error_t error;
00064    uint_t i;
00065    uint_t replyCode;
00066    IpAddr serverIpAddr;
00067    SmtpClientContext *context;
00068 
00069    //Check parameters
00070    if(authInfo == NULL || mail == NULL)
00071       return ERROR_INVALID_PARAMETER;
00072    //Make sure the server name is valid
00073    if(authInfo->serverName == NULL)
00074       return ERROR_INVALID_PARAMETER;
00075 
00076    //Debug message
00077    TRACE_INFO("Sending a mail to %s port %" PRIu16 "...\r\n",
00078       authInfo->serverName, authInfo->serverPort);
00079 
00080    //The specified SMTP server can be either an IP or a host name
00081    error = getHostByName(authInfo->interface,
00082       authInfo->serverName, &serverIpAddr, 0);
00083    //Unable to resolve server name?
00084    if(error)
00085       return ERROR_NAME_RESOLUTION_FAILED;
00086 
00087    //Allocate a memory buffer to hold the SMTP client context
00088    context = osAllocMem(sizeof(SmtpClientContext));
00089    //Failed to allocate memory?
00090    if(context == NULL)
00091       return ERROR_OUT_OF_MEMORY;
00092 
00093    //Open a TCP socket
00094    context->socket = socketOpen(SOCKET_TYPE_STREAM, SOCKET_IP_PROTO_TCP);
00095    //Failed to open socket?
00096    if(!context->socket)
00097    {
00098       //Free previously allocated resources
00099       osFreeMem(context);
00100       //Report an error
00101       return ERROR_OPEN_FAILED;
00102    }
00103 
00104 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
00105    //Do not use SSL/TLS for the moment
00106    context->tlsContext = NULL;
00107 #endif
00108 
00109    //Start of exception handling block
00110    do
00111    {
00112       //Bind the socket to a particular network interface?
00113       if(authInfo->interface)
00114       {
00115          //Associate the socket with the relevant interface
00116          error = socketBindToInterface(context->socket, authInfo->interface);
00117          //Any error to report?
00118          if(error)
00119             break;
00120       }
00121 
00122       //Set timeout for blocking operations
00123       error = socketSetTimeout(context->socket, SMTP_CLIENT_DEFAULT_TIMEOUT);
00124       //Any error to report?
00125       if(error)
00126          break;
00127 
00128       //Connect to the SMTP server
00129       error = socketConnect(context->socket, &serverIpAddr, authInfo->serverPort);
00130       //Connection to server failed?
00131       if(error)
00132          break;
00133 
00134 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
00135       //Open a secure SSL/TLS session?
00136       if(authInfo->useTls)
00137       {
00138          //Initialize TLS context
00139          context->tlsContext = tlsInit();
00140          //Initialization failed?
00141          if(context->tlsContext == NULL)
00142          {
00143             //Unable to allocate memory
00144             error = ERROR_OUT_OF_MEMORY;
00145             //Stop immediately
00146             break;
00147          }
00148 
00149          //Bind TLS to the relevant socket
00150          error = tlsSetSocket(context->tlsContext, context->socket);
00151          //Any error to report?
00152          if(error)
00153             break;
00154 
00155          //Select client operation mode
00156          error = tlsSetConnectionEnd(context->tlsContext, TLS_CONNECTION_END_CLIENT);
00157          //Any error to report?
00158          if(error)
00159             break;
00160 
00161          //Set the PRNG algorithm to be used
00162          error = tlsSetPrng(context->tlsContext, authInfo->prngAlgo, authInfo->prngContext);
00163          //Any error to report?
00164          if(error)
00165             break;
00166 
00167          //Perform TLS handshake
00168          error = tlsConnect(context->tlsContext);
00169          //Failed to established a TLS session?
00170          if(error)
00171             break;
00172       }
00173 #endif
00174 
00175       //Wait for the connection greeting reply
00176       error = smtpSendCommand(context, NULL, &replyCode, NULL);
00177       //Any communication error to report?
00178       if(error)
00179          break;
00180 
00181       //Check whether the greeting message was properly received
00182       if(!SMTP_REPLY_CODE_2YZ(replyCode))
00183       {
00184          //An unexpected response was received...
00185          error = ERROR_UNEXPECTED_RESPONSE;
00186          //Stop immediately
00187          break;
00188       }
00189 
00190       //Clear security features
00191       context->authLoginSupported = FALSE;
00192       context->authPlainSupported = FALSE;
00193       context->authCramMd5Supported = FALSE;
00194       context->startTlsSupported = FALSE;
00195 
00196       //Send EHLO command and parse server response
00197       error = smtpSendCommand(context, "EHLO [127.0.0.1]\r\n",
00198          &replyCode, smtpEhloReplyCallback);
00199       //Any communication error to report?
00200       if(error)
00201          break;
00202 
00203       //Check SMTP response code
00204       if(!SMTP_REPLY_CODE_2YZ(replyCode))
00205       {
00206          //An unexpected response was received...
00207          error = ERROR_UNEXPECTED_RESPONSE;
00208          //Stop immediately
00209          break;
00210       }
00211 
00212 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
00213       //Check whether the STARTTLS command is supported
00214       if(context->startTlsSupported && !context->tlsContext)
00215       {
00216          //Send STARTTLS command
00217          error = smtpSendCommand(context, "STARTTLS\r\n", &replyCode, NULL);
00218          //Any communication error to report?
00219          if(error)
00220             break;
00221 
00222          //Check SMTP response code
00223          if(!SMTP_REPLY_CODE_2YZ(replyCode))
00224          {
00225             //An unexpected response was received...
00226             error = ERROR_UNEXPECTED_RESPONSE;
00227             //Stop immediately
00228             break;
00229          }
00230 
00231          //Initialize TLS context
00232          context->tlsContext = tlsInit();
00233          //Initialization failed?
00234          if(context->tlsContext == NULL)
00235          {
00236             //Unable to allocate memory
00237             error = ERROR_OUT_OF_MEMORY;
00238             //Stop immediately
00239             break;
00240          }
00241 
00242          //Bind TLS to the relevant socket
00243          error = tlsSetSocket(context->tlsContext, context->socket);
00244          //Any error to report?
00245          if(error)
00246             break;
00247 
00248          //Select client operation mode
00249          error = tlsSetConnectionEnd(context->tlsContext, TLS_CONNECTION_END_CLIENT);
00250          //Any error to report?
00251          if(error)
00252             break;
00253 
00254          //Set the PRNG algorithm to be used
00255          error = tlsSetPrng(context->tlsContext, authInfo->prngAlgo, authInfo->prngContext);
00256          //Any error to report?
00257          if(error)
00258             break;
00259 
00260          //Perform TLS handshake
00261          error = tlsConnect(context->tlsContext);
00262          //Failed to established a TLS session?
00263          if(error)
00264             break;
00265 
00266          //Clear security features
00267          context->authLoginSupported = FALSE;
00268          context->authPlainSupported = FALSE;
00269          context->authCramMd5Supported = FALSE;
00270 
00271          //Send EHLO command and parse server response
00272          error = smtpSendCommand(context, "EHLO [127.0.0.1]\r\n",
00273             &replyCode, smtpEhloReplyCallback);
00274          //Any communication error to report?
00275          if(error)
00276             break;
00277 
00278          //Check SMTP response code
00279          if(!SMTP_REPLY_CODE_2YZ(replyCode))
00280          {
00281             //An unexpected response was received...
00282             error = ERROR_UNEXPECTED_RESPONSE;
00283             //Stop immediately
00284             break;
00285          }
00286       }
00287 #endif
00288 
00289       //Authentication requires a valid user name and password
00290       if(authInfo->userName && authInfo->password)
00291       {
00292 #if (SMTP_CLIENT_LOGIN_AUTH_SUPPORT == ENABLED)
00293          //LOGIN authentication mechanism supported?
00294          if(context->authLoginSupported)
00295          {
00296             //Perform LOGIN authentication
00297             error = smtpSendAuthLogin(context, authInfo);
00298             //Authentication failed?
00299             if(error)
00300                break;
00301          }
00302          else
00303 #endif
00304 #if (SMTP_CLIENT_PLAIN_AUTH_SUPPORT == ENABLED)
00305          //PLAIN authentication mechanism supported?
00306          if(context->authPlainSupported)
00307          {
00308             //Perform PLAIN authentication
00309             error = smtpSendAuthPlain(context, authInfo);
00310             //Authentication failed?
00311             if(error)
00312                break;
00313          }
00314          else
00315 #endif
00316 #if (SMTP_CLIENT_CRAM_MD5_AUTH_SUPPORT == ENABLED)
00317          //CRAM-MD5 authentication mechanism supported?
00318          if(context->authCramMd5Supported)
00319          {
00320             //Perform CRAM-MD5 authentication
00321             error = smtpSendAuthCramMd5(context, authInfo);
00322             //Authentication failed?
00323             if(error)
00324                break;
00325          }
00326          else
00327 #endif
00328          //No authentication mechanism supported?
00329          {
00330             //Skip authentication step
00331          }
00332       }
00333 
00334       //Format the MAIL FROM command (a null return path must be accepted)
00335       if(mail->from.addr)
00336          sprintf(context->buffer, "MAIL FROM:<%s>\r\n", mail->from.addr);
00337       else
00338          strcpy(context->buffer, "MAIL FROM:<>\r\n");
00339 
00340       //Send the command to the server
00341       error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
00342       //Any communication error to report?
00343       if(error)
00344          break;
00345 
00346       //Check SMTP response code
00347       if(!SMTP_REPLY_CODE_2YZ(replyCode))
00348       {
00349          //An unexpected response was received...
00350          error = ERROR_UNEXPECTED_RESPONSE;
00351          //Stop immediately
00352          break;
00353       }
00354 
00355       //Format the RCPT TO command
00356       for(i = 0; i < mail->recipientCount; i++)
00357       {
00358          //Skip recipient addresses that are not valid
00359          if(!mail->recipients[i].addr)
00360             continue;
00361 
00362          //Format the RCPT TO command
00363          sprintf(context->buffer, "RCPT TO:<%s>\r\n", mail->recipients[i].addr);
00364          //Send the command to the server
00365          error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
00366          //Any communication error to report?
00367          if(error)
00368             break;
00369 
00370          //Check SMTP response code
00371          if(!SMTP_REPLY_CODE_2YZ(replyCode))
00372          {
00373             //An unexpected response was received...
00374             error = ERROR_UNEXPECTED_RESPONSE;
00375             //Stop immediately
00376             break;
00377          }
00378       }
00379 
00380       //Propagate exception if necessary
00381       if(error)
00382          break;
00383 
00384       //Send message body
00385       error = smtpSendData(context, mail);
00386       //Any error to report?
00387       if(error)
00388          break;
00389 
00390       //End of exception handling block
00391    } while(0);
00392 
00393    //Check status code
00394    if(error == NO_ERROR ||
00395       error == ERROR_UNEXPECTED_RESPONSE ||
00396       error == ERROR_AUTHENTICATION_FAILED)
00397    {
00398       //Properly disconnect from the SMTP server
00399       smtpSendCommand(context, "QUIT\r\n", &replyCode, NULL);
00400    }
00401 
00402 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
00403    if(context->tlsContext != NULL)
00404    {
00405       //Gracefully close SSL/TLS session
00406       tlsShutdown(context->tlsContext);
00407       //Release SSL/TLS context
00408       tlsFree(context->tlsContext);
00409    }
00410 #endif
00411 
00412    //Close socket
00413    socketClose(context->socket);
00414    //Clean up previously allocated resources
00415    osFreeMem(context);
00416 
00417    //Return status code
00418    return error;
00419 }
00420 
00421 
00422 /**
00423  * @brief Callback function to parse EHLO response
00424  * @param[in] context SMTP client context
00425  * @param[in] replyLine Response line
00426  * @param[in] replyCode Response code
00427  * @return Error code
00428  **/
00429 
00430 error_t smtpEhloReplyCallback(SmtpClientContext *context,
00431    char_t *replyLine, uint_t replyCode)
00432 {
00433    char_t *p;
00434    char_t *token;
00435 
00436    //The line must be at least 4 characters long
00437    if(strlen(replyLine) < 4)
00438       return NO_ERROR;
00439 
00440    //Skip the response code and the separator
00441    replyLine += 4;
00442 
00443    //Get the first keyword
00444    token = strtok_r(replyLine, " ", &p);
00445    //Check whether the response line is empty
00446    if(token == NULL)
00447       return ERROR_INVALID_SYNTAX;
00448 
00449    //The AUTH keyword contains a space-separated list of
00450    //names of available authentication mechanisms
00451    if(!strcasecmp(token, "AUTH"))
00452    {
00453       //Process the rest of the line
00454       while(1)
00455       {
00456          //Get the next keyword
00457          token = strtok_r(NULL, " ", &p);
00458          //Unable to find the next token?
00459          if(token == NULL)
00460             break;
00461 
00462          //LOGIN authentication mechanism supported?
00463          if(!strcasecmp(token, "LOGIN"))
00464             context->authLoginSupported = TRUE;
00465          //PLAIN authentication mechanism supported?
00466          else if(!strcasecmp(token, "PLAIN"))
00467             context->authPlainSupported = TRUE;
00468          //CRAM-MD5 authentication mechanism supported?
00469          else if(!strcasecmp(token, "CRAM-MD5"))
00470             context->authCramMd5Supported = TRUE;
00471       }
00472    }
00473    //The STARTTLS keyword is used to tell the SMTP client
00474    //that the SMTP server allows use of TLS
00475    else if(!strcasecmp(token, "STARTTLS"))
00476    {
00477       //STARTTLS use is allowed
00478       context->startTlsSupported = TRUE;
00479    }
00480 
00481    //Successful processing
00482    return NO_ERROR;
00483 }
00484 
00485 
00486 /**
00487  * @brief Authentication using LOGIN mechanism
00488  * @param[in] context SMTP client context
00489  * @param[in] authInfo Authentication information
00490  * @return Error code
00491  **/
00492 
00493 error_t smtpSendAuthLogin(SmtpClientContext *context, const SmtpAuthInfo *authInfo)
00494 {
00495 #if (SMTP_CLIENT_LOGIN_AUTH_SUPPORT == ENABLED)
00496    error_t error;
00497    uint_t replyCode;
00498 
00499    //Send AUTH LOGIN command
00500    error = smtpSendCommand(context, "AUTH LOGIN\r\n", &replyCode, NULL);
00501 
00502    //Any communication error to report?
00503    if(error)
00504       return error;
00505    //Check SMTP reply code
00506    if(!SMTP_REPLY_CODE_3YZ(replyCode))
00507       return ERROR_AUTHENTICATION_FAILED;
00508 
00509    //Encode the user name with Base64 algorithm
00510    base64Encode(authInfo->userName, strlen(authInfo->userName), context->buffer, NULL);
00511    //Add a line feed
00512    strcat(context->buffer, "\r\n");
00513 
00514    //Send the resulting string
00515    error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
00516    //Any communication error to report?
00517    if(error)
00518       return error;
00519 
00520    //Check SMTP reply code
00521    if(!SMTP_REPLY_CODE_3YZ(replyCode))
00522       return ERROR_AUTHENTICATION_FAILED;
00523 
00524    //Encode the password with Base64 algorithm
00525    base64Encode(authInfo->password, strlen(authInfo->password), context->buffer, NULL);
00526    //Add a line feed
00527    strcat(context->buffer, "\r\n");
00528 
00529    //Send the resulting string
00530    error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
00531    //Any communication error to report?
00532    if(error)
00533       return error;
00534 
00535    //Check SMTP reply code
00536    if(!SMTP_REPLY_CODE_2YZ(replyCode))
00537       return ERROR_AUTHENTICATION_FAILED;
00538 
00539    //Successful authentication
00540    return NO_ERROR;
00541 #else
00542    //LOGIN authentication is not supported
00543    return ERROR_AUTHENTICATION_FAILED;
00544 #endif
00545 }
00546 
00547 
00548 /**
00549  * @brief Authentication using PLAIN mechanism
00550  * @param[in] context SMTP client context
00551  * @param[in] authInfo Authentication information
00552  * @return Error code
00553  **/
00554 
00555 error_t smtpSendAuthPlain(SmtpClientContext *context, const SmtpAuthInfo *authInfo)
00556 {
00557 #if (SMTP_CLIENT_PLAIN_AUTH_SUPPORT == ENABLED)
00558    error_t error;
00559    uint_t n;
00560    uint_t replyCode;
00561 
00562    //Authorization identity
00563    strcpy(context->buffer, authInfo->userName);
00564    n = strlen(authInfo->userName) + 1;
00565    //Authentication identity
00566    strcpy(context->buffer + n, authInfo->userName);
00567    n += strlen(authInfo->userName) + 1;
00568    //Password
00569    strcpy(context->buffer + n, authInfo->password);
00570    n += strlen(authInfo->password);
00571 
00572    //Base64 encoding
00573    base64Encode(context->buffer, n, context->buffer2, NULL);
00574    //Format the AUTH PLAIN command
00575    sprintf(context->buffer, "AUTH PLAIN %s\r\n", context->buffer2);
00576 
00577    //Send the command to the server
00578    error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
00579    //Any communication error to report?
00580    if(error)
00581       return error;
00582 
00583    //Check SMTP reply code
00584    if(!SMTP_REPLY_CODE_2YZ(replyCode))
00585       return ERROR_AUTHENTICATION_FAILED;
00586 
00587    //Successful authentication
00588    return NO_ERROR;
00589 #else
00590    //PLAIN authentication is not supported
00591    return ERROR_AUTHENTICATION_FAILED;
00592 #endif
00593 }
00594 
00595 
00596 /**
00597  * @brief Authentication using CRAM-MD5 mechanism
00598  * @param[in] context SMTP client context
00599  * @param[in] authInfo Authentication information
00600  * @return Error code
00601  **/
00602 
00603 error_t smtpSendAuthCramMd5(SmtpClientContext *context, const SmtpAuthInfo *authInfo)
00604 {
00605 #if (SMTP_CLIENT_CRAM_MD5_AUTH_SUPPORT == ENABLED)
00606    //Hex conversion table
00607    static const char_t hexDigit[] =
00608    {
00609       '0', '1', '2', '3', '4', '5', '6', '7',
00610       '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
00611    };
00612 
00613    //Local variables
00614    error_t error;
00615    uint_t n;
00616    uint_t replyCode;
00617 
00618    //Alias pointers
00619    uint8_t *challenge = (uint8_t *) context->buffer2;
00620    uint8_t *digest = (uint8_t *) context->buffer;
00621    char_t *textDigest = (char_t *) context->buffer2;
00622 
00623    //Send AUTH CRAM-MD5 command
00624    error = smtpSendCommand(context, "AUTH CRAM-MD5\r\n", &replyCode, NULL);
00625    //Any communication error to report?
00626    if(error)
00627       return error;
00628 
00629    //Check SMTP reply code
00630    if(!SMTP_REPLY_CODE_3YZ(replyCode))
00631       return ERROR_AUTHENTICATION_FAILED;
00632 
00633    //Compute the length of the response
00634    n = strlen(context->buffer);
00635    //Unexpected response from the SMTP server?
00636    if(n <= 4)
00637       return ERROR_INVALID_SYNTAX;
00638 
00639    //Decrypt the Base64 encoded challenge
00640    error = base64Decode(context->buffer + 4, n - 4, challenge, &n);
00641    //Decoding failed?
00642    if(error)
00643       return error;
00644 
00645    //Compute HMAC using MD5
00646    error = hmacCompute(MD5_HASH_ALGO, authInfo->password,
00647       strlen(authInfo->password), challenge, n, digest);
00648    //HMAC computation failed?
00649    if(error)
00650       return error;
00651 
00652    //Convert the digest to text
00653    for(n = 0; n < MD5_DIGEST_SIZE; n++)
00654    {
00655       //Convert upper nibble
00656       textDigest[n * 2] = hexDigit[(digest[n] >> 4) & 0x0F];
00657       //Then convert lower nibble
00658       textDigest[n * 2 + 1] = hexDigit[digest[n] & 0x0F];
00659    }
00660 
00661    //Properly terminate the string
00662    textDigest[MD5_DIGEST_SIZE * 2] = '\0';
00663    //Concatenate the user name and the text representation of the digest
00664    sprintf(context->buffer, "%s %s", authInfo->userName, textDigest);
00665    //Encode the resulting string with Base64 algorithm
00666    base64Encode(context->buffer, strlen(context->buffer), context->buffer2, NULL);
00667    //Add a line feed
00668    strcat(context->buffer2, "\r\n");
00669 
00670    //Transmit the Base64 encoded string
00671    error = smtpSendCommand(context, context->buffer2, &replyCode, NULL);
00672    //Any communication error to report?
00673    if(error)
00674       return error;
00675 
00676    //Check SMTP reply code
00677    if(!SMTP_REPLY_CODE_2YZ(replyCode))
00678       return ERROR_AUTHENTICATION_FAILED;
00679 
00680    //Successful authentication
00681    return NO_ERROR;
00682 #else
00683    //CRAM-MD5 authentication is not supported
00684    return ERROR_AUTHENTICATION_FAILED;
00685 #endif
00686 }
00687 
00688 
00689 /**
00690  * @brief Send message body
00691  * @param[in] context SMTP client context
00692  * @param[in] mail Mail contents
00693  * @return Error code
00694  **/
00695 
00696 error_t smtpSendData(SmtpClientContext *context, const SmtpMail *mail)
00697 {
00698    error_t error;
00699    bool_t first;
00700    uint_t i;
00701    uint_t replyCode;
00702    char_t *p;
00703 
00704    //Send DATA command
00705    error = smtpSendCommand(context, "DATA\r\n", &replyCode, NULL);
00706    //Any communication error to report?
00707    if(error)
00708       return error;
00709 
00710    //Check SMTP reply code
00711    if(!SMTP_REPLY_CODE_3YZ(replyCode))
00712       return ERROR_UNEXPECTED_RESPONSE;
00713 
00714    //Point to the beginning of the buffer
00715    p = context->buffer;
00716 
00717    //Current date and time
00718    if(mail->dateTime && mail->dateTime[0] != '\0')
00719       p += sprintf(p, "Date: %s\r\n", mail->dateTime);
00720 
00721    //Sender address
00722    if(mail->from.addr)
00723    {
00724       //A friendly name may be associated with the sender address
00725       if(mail->from.name && mail->from.name[0] != '\0')
00726          p += sprintf(p, "From: \"%s\" <%s>\r\n", mail->from.name, mail->from.addr);
00727       else
00728          p += sprintf(p, "From: %s\r\n", mail->from.addr);
00729    }
00730 
00731    //Recipients
00732    for(i = 0, first = TRUE; i < mail->recipientCount; i++)
00733    {
00734       //Skip recipient addresses that are not valid
00735       if(!mail->recipients[i].addr)
00736          continue;
00737 
00738       //Check recipient type
00739       if(mail->recipients[i].type & SMTP_RCPT_TYPE_TO)
00740       {
00741          //The first item of the list requires special processing
00742          p += sprintf(p, first ? "To: " : ", ");
00743 
00744          //A friendly name may be associated with the address
00745          if(mail->recipients[i].name && mail->recipients[i].name[0] != '\0')
00746             p += sprintf(p, "\"%s\" <%s>", mail->recipients[i].name, mail->recipients[i].addr);
00747          else
00748             p += sprintf(p, "%s", mail->recipients[i].addr);
00749 
00750          //Prepare to add a new item to the list
00751          first = FALSE;
00752       }
00753    }
00754 
00755    //Properly terminate the line with CRLF
00756    if(!first)
00757       p += sprintf(p, "\r\n");
00758 
00759    //Carbon copy
00760    for(i = 0, first = TRUE; i < mail->recipientCount; i++)
00761    {
00762       //Skip recipient addresses that are not valid
00763       if(!mail->recipients[i].addr)
00764          continue;
00765 
00766       //Check recipient type
00767       if(mail->recipients[i].type & SMTP_RCPT_TYPE_CC)
00768       {
00769          //The first item of the list requires special processing
00770          p += sprintf(p, first ? "Cc: " : ", ");
00771 
00772          //A friendly name may be associated with the address
00773          if(mail->recipients[i].name && mail->recipients[i].name[0] != '\0')
00774             p += sprintf(p, "\"%s\" <%s>", mail->recipients[i].name, mail->recipients[i].addr);
00775          else
00776             p += sprintf(p, "%s", mail->recipients[i].addr);
00777 
00778          //Prepare to add a new item to the list
00779          first = FALSE;
00780       }
00781    }
00782 
00783    //Properly terminate the line with CRLF
00784    if(!first)
00785       p += sprintf(p, "\r\n");
00786 
00787    //Subject
00788    if(mail->subject)
00789       p += sprintf(p, "Subject: %s\r\n", mail->subject);
00790 
00791    //The header and the body are separated by an empty line
00792    sprintf(p, "\r\n");
00793 
00794    //Debug message
00795    TRACE_DEBUG(context->buffer);
00796    TRACE_DEBUG(mail->body);
00797    TRACE_DEBUG("\r\n.\r\n");
00798 
00799    //Send message header
00800    error = smtpWrite(context, context->buffer, strlen(context->buffer), 0);
00801    //Any communication error to report?
00802    if(error)
00803       return error;
00804 
00805    //Send message body
00806    error = smtpWrite(context, mail->body, strlen(mail->body), 0);
00807    //Any communication error to report?
00808    if(error)
00809       return error;
00810 
00811    //Indicate the end of the mail data by sending a line containing only a "."
00812    error = smtpSendCommand(context, "\r\n.\r\n", &replyCode, NULL);
00813    //Any communication error to report?
00814    if(error)
00815       return error;
00816 
00817    //Check SMTP reply code
00818    if(!SMTP_REPLY_CODE_2YZ(replyCode))
00819       return ERROR_UNEXPECTED_RESPONSE;
00820 
00821    //Successful operation
00822    return NO_ERROR;
00823 }
00824 
00825 
00826 /**
00827  * @brief Send SMTP command and wait for a reply
00828  * @param[in] context SMTP client context
00829  * @param[in] command Command line
00830  * @param[out] replyCode SMTP server reply code
00831  * @param[in] callback Optional callback to parse each line of the reply
00832  * @return Error code
00833  **/
00834 
00835 error_t smtpSendCommand(SmtpClientContext *context, const char_t *command,
00836    uint_t *replyCode, SmtpReplyCallback callback)
00837 {
00838    error_t error;
00839    size_t length;
00840    char_t *line;
00841 
00842    //Any command line to send?
00843    if(command)
00844    {
00845       //Debug message
00846       TRACE_DEBUG("SMTP client: %s", command);
00847 
00848       //Send the command to the SMTP server
00849       error = smtpWrite(context, command, strlen(command), SOCKET_FLAG_WAIT_ACK);
00850       //Failed to send command?
00851       if(error)
00852          return error;
00853    }
00854 
00855    //Multiline replies are allowed for any command
00856    while(1)
00857    {
00858       //Wait for a response from the server
00859       error = smtpRead(context, context->buffer,
00860          SMTP_CLIENT_MAX_LINE_LENGTH - 1, &length, SOCKET_FLAG_BREAK_CRLF);
00861 
00862       //The remote server did not respond as expected?
00863       if(error)
00864          return error;
00865 
00866       //Properly terminate the string with a NULL character
00867       context->buffer[length] = '\0';
00868       //Remove all leading and trailing whitespace from the response
00869       line = strTrimWhitespace(context->buffer);
00870 
00871       //Debug message
00872       TRACE_DEBUG("SMTP server: %s\r\n", line);
00873 
00874       //Check the length of the response
00875       if(strlen(line) < 3)
00876          return ERROR_INVALID_SYNTAX;
00877       //All replies begin with a three digit numeric code
00878       if(!isdigit((uint8_t) line[0]) || !isdigit((uint8_t) line[1]) || !isdigit((uint8_t) line[2]))
00879          return ERROR_INVALID_SYNTAX;
00880       //A hyphen or a space character must follow the response code
00881       if(line[3] != '-' && line[3] != ' ' && line[3] != '\0')
00882          return ERROR_INVALID_SYNTAX;
00883 
00884       //Get the server response code
00885       *replyCode = strtoul(line, NULL, 10);
00886 
00887       //Any callback function to call?
00888       if(callback)
00889       {
00890          //Invoke callback function to parse the response line
00891          error = callback(context, line, *replyCode);
00892          //Check status code
00893          if(error)
00894             return error;
00895       }
00896 
00897       //A hyphen follows the response code for all but the last line
00898       if(line[3] != '-')
00899          break;
00900    }
00901 
00902    //Successful processing
00903    return NO_ERROR;
00904 }
00905 
00906 
00907 /**
00908  * @brief Send data to the SMTP server
00909  * @param[in] context SMTP client context
00910  * @param[in] data Pointer to a buffer containing the data to be transmitted
00911  * @param[in] length Number of bytes to be transmitted
00912  * @param[in] flags Set of flags that influences the behavior of this function
00913  **/
00914 
00915 error_t smtpWrite(SmtpClientContext *context, const void *data, size_t length, uint_t flags)
00916 {
00917 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
00918    //Check whether a secure connection is being used
00919    if(context->tlsContext != NULL)
00920    {
00921       //Use SSL/TLS to transmit data to the SMTP server
00922       return tlsWrite(context->tlsContext, data, length, NULL, flags);
00923    }
00924    else
00925 #endif
00926    {
00927       //Transmit data to the SMTP server
00928       return socketSend(context->socket, data, length, NULL, flags);
00929    }
00930 }
00931 
00932 
00933 /**
00934  * @brief Receive data from the SMTP server
00935  * @param[in] context SMTP client context
00936  * @param[out] data Buffer into which received data will be placed
00937  * @param[in] size Maximum number of bytes that can be received
00938  * @param[out] received Actual number of bytes that have been received
00939  * @param[in] flags Set of flags that influences the behavior of this function
00940  * @return Error code
00941  **/
00942 
00943 error_t smtpRead(SmtpClientContext *context, void *data, size_t size, size_t *received, uint_t flags)
00944 {
00945 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
00946    //Check whether a secure connection is being used
00947    if(context->tlsContext != NULL)
00948    {
00949       //Use SSL/TLS to receive data from the SMTP server
00950       return tlsRead(context->tlsContext, data, size, received, flags);
00951    }
00952    else
00953 #endif
00954    {
00955       //Receive data from the SMTP server
00956       return socketReceive(context->socket, data, size, received, flags);
00957    }
00958 }
00959 
00960 #endif
00961