smtp_client.c
Go to the documentation of this file.
1 /**
2  * @file smtp_client.c
3  * @brief SMTP client (Simple Mail Transfer Protocol)
4  *
5  * @section License
6  *
7  * Copyright (C) 2010-2018 Oryx Embedded SARL. All rights reserved.
8  *
9  * This file is part of CycloneTCP Open.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  *
25  * @section Description
26  *
27  * SMTP is designed as a mail transport and delivery protocol. Refer to
28  * the following RFCs for complete details:
29  * - RFC 5321: Simple Mail Transfer Protocol
30  * - RFC 4954: SMTP Service Extension for Authentication
31  * - RFC 3207: SMTP Service Extension for Secure SMTP over TLS
32  *
33  * @author Oryx Embedded SARL (www.oryx-embedded.com)
34  * @version 1.9.0
35  **/
36 
37 //Switch to the appropriate trace level
38 #define TRACE_LEVEL SMTP_TRACE_LEVEL
39 
40 //Dependencies
41 #include <string.h>
42 #include <ctype.h>
43 #include <stdlib.h>
44 #include "core/net.h"
45 #include "smtp/smtp_client.h"
46 #include "core/socket.h"
47 #include "str.h"
48 #include "debug.h"
49 
50 //Check TCP/IP stack configuration
51 #if (SMTP_CLIENT_SUPPORT == ENABLED)
52 
53 
54 /**
55  * @brief Send a mail to the specified recipients
56  * @param[in] authInfo Authentication information
57  * @param[in] mail Mail contents
58  * @return Error code
59  **/
60 
62 {
63  error_t error;
64  uint_t i;
65  uint_t replyCode;
66  IpAddr serverIpAddr;
67  SmtpClientContext *context;
68 
69  //Check parameters
70  if(authInfo == NULL || mail == NULL)
72  //Make sure the server name is valid
73  if(authInfo->serverName == NULL)
75 
76  //Debug message
77  TRACE_INFO("Sending a mail to %s port %" PRIu16 "...\r\n",
78  authInfo->serverName, authInfo->serverPort);
79 
80  //The specified SMTP server can be either an IP or a host name
81  error = getHostByName(authInfo->interface,
82  authInfo->serverName, &serverIpAddr, 0);
83  //Unable to resolve server name?
84  if(error)
86 
87  //Allocate a memory buffer to hold the SMTP client context
88  context = osAllocMem(sizeof(SmtpClientContext));
89  //Failed to allocate memory?
90  if(context == NULL)
91  return ERROR_OUT_OF_MEMORY;
92 
93  //Open a TCP socket
95  //Failed to open socket?
96  if(context->socket == NULL)
97  {
98  //Free previously allocated resources
99  osFreeMem(context);
100  //Report an error
101  return ERROR_OPEN_FAILED;
102  }
103 
104 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
105  //Do not use TLS for the moment
106  context->tlsContext = NULL;
107 #endif
108 
109  //Start of exception handling block
110  do
111  {
112  //Bind the socket to a particular network interface?
113  if(authInfo->interface)
114  {
115  //Associate the socket with the relevant interface
116  error = socketBindToInterface(context->socket, authInfo->interface);
117  //Any error to report?
118  if(error)
119  break;
120  }
121 
122  //Set timeout for blocking operations
124  //Any error to report?
125  if(error)
126  break;
127 
128  //Connect to the SMTP server
129  error = socketConnect(context->socket, &serverIpAddr, authInfo->serverPort);
130  //Connection to server failed?
131  if(error)
132  break;
133 
134 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
135  //Open a secure TLS session?
136  if(authInfo->useTls)
137  {
138  //Initialize TLS context
139  context->tlsContext = tlsInit();
140  //Initialization failed?
141  if(context->tlsContext == NULL)
142  {
143  //Unable to allocate memory
144  error = ERROR_OUT_OF_MEMORY;
145  //Stop immediately
146  break;
147  }
148 
149  //Bind TLS to the relevant socket
150  error = tlsSetSocket(context->tlsContext, context->socket);
151  //Any error to report?
152  if(error)
153  break;
154 
155  //Select client operation mode
157  //Any error to report?
158  if(error)
159  break;
160 
161  //Set the PRNG algorithm to be used
162  error = tlsSetPrng(context->tlsContext, authInfo->prngAlgo, authInfo->prngContext);
163  //Any error to report?
164  if(error)
165  break;
166 
167  //Perform TLS handshake
168  error = tlsConnect(context->tlsContext);
169  //Failed to established a TLS session?
170  if(error)
171  break;
172  }
173 #endif
174 
175  //Wait for the connection greeting reply
176  error = smtpSendCommand(context, NULL, &replyCode, NULL);
177  //Any communication error to report?
178  if(error)
179  break;
180 
181  //Check whether the greeting message was properly received
182  if(!SMTP_REPLY_CODE_2YZ(replyCode))
183  {
184  //An unexpected response was received...
186  //Stop immediately
187  break;
188  }
189 
190  //Clear security features
191  context->authLoginSupported = FALSE;
192  context->authPlainSupported = FALSE;
193  context->authCramMd5Supported = FALSE;
194  context->startTlsSupported = FALSE;
195 
196  //Send EHLO command and parse server response
197  error = smtpSendCommand(context, "EHLO [127.0.0.1]\r\n",
198  &replyCode, smtpEhloReplyCallback);
199  //Any communication error to report?
200  if(error)
201  break;
202 
203  //Check SMTP response code
204  if(!SMTP_REPLY_CODE_2YZ(replyCode))
205  {
206  //An unexpected response was received...
208  //Stop immediately
209  break;
210  }
211 
212 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
213  //Check whether the STARTTLS command is supported
214  if(context->startTlsSupported && !context->tlsContext)
215  {
216  //Send STARTTLS command
217  error = smtpSendCommand(context, "STARTTLS\r\n", &replyCode, NULL);
218  //Any communication error to report?
219  if(error)
220  break;
221 
222  //Check SMTP response code
223  if(!SMTP_REPLY_CODE_2YZ(replyCode))
224  {
225  //An unexpected response was received...
227  //Stop immediately
228  break;
229  }
230 
231  //Initialize TLS context
232  context->tlsContext = tlsInit();
233  //Initialization failed?
234  if(context->tlsContext == NULL)
235  {
236  //Unable to allocate memory
237  error = ERROR_OUT_OF_MEMORY;
238  //Stop immediately
239  break;
240  }
241 
242  //Bind TLS to the relevant socket
243  error = tlsSetSocket(context->tlsContext, context->socket);
244  //Any error to report?
245  if(error)
246  break;
247 
248  //Select client operation mode
250  //Any error to report?
251  if(error)
252  break;
253 
254  //Set the PRNG algorithm to be used
255  error = tlsSetPrng(context->tlsContext, authInfo->prngAlgo, authInfo->prngContext);
256  //Any error to report?
257  if(error)
258  break;
259 
260  //Perform TLS handshake
261  error = tlsConnect(context->tlsContext);
262  //Failed to established a TLS session?
263  if(error)
264  break;
265 
266  //Clear security features
267  context->authLoginSupported = FALSE;
268  context->authPlainSupported = FALSE;
269  context->authCramMd5Supported = FALSE;
270 
271  //Send EHLO command and parse server response
272  error = smtpSendCommand(context, "EHLO [127.0.0.1]\r\n",
273  &replyCode, smtpEhloReplyCallback);
274  //Any communication error to report?
275  if(error)
276  break;
277 
278  //Check SMTP response code
279  if(!SMTP_REPLY_CODE_2YZ(replyCode))
280  {
281  //An unexpected response was received...
283  //Stop immediately
284  break;
285  }
286  }
287 #endif
288 
289  //Authentication requires a valid user name and password
290  if(authInfo->userName && authInfo->password)
291  {
292 #if (SMTP_CLIENT_LOGIN_AUTH_SUPPORT == ENABLED)
293  //LOGIN authentication mechanism supported?
294  if(context->authLoginSupported)
295  {
296  //Perform LOGIN authentication
297  error = smtpSendAuthLogin(context, authInfo);
298  //Authentication failed?
299  if(error)
300  break;
301  }
302  else
303 #endif
304 #if (SMTP_CLIENT_PLAIN_AUTH_SUPPORT == ENABLED)
305  //PLAIN authentication mechanism supported?
306  if(context->authPlainSupported)
307  {
308  //Perform PLAIN authentication
309  error = smtpSendAuthPlain(context, authInfo);
310  //Authentication failed?
311  if(error)
312  break;
313  }
314  else
315 #endif
316 #if (SMTP_CLIENT_CRAM_MD5_AUTH_SUPPORT == ENABLED)
317  //CRAM-MD5 authentication mechanism supported?
318  if(context->authCramMd5Supported)
319  {
320  //Perform CRAM-MD5 authentication
321  error = smtpSendAuthCramMd5(context, authInfo);
322  //Authentication failed?
323  if(error)
324  break;
325  }
326  else
327 #endif
328  //No authentication mechanism supported?
329  {
330  //Skip authentication step
331  }
332  }
333 
334  //Format the MAIL FROM command (a null return path must be accepted)
335  if(mail->from.addr)
336  sprintf(context->buffer, "MAIL FROM:<%s>\r\n", mail->from.addr);
337  else
338  strcpy(context->buffer, "MAIL FROM:<>\r\n");
339 
340  //Send the command to the server
341  error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
342  //Any communication error to report?
343  if(error)
344  break;
345 
346  //Check SMTP response code
347  if(!SMTP_REPLY_CODE_2YZ(replyCode))
348  {
349  //An unexpected response was received...
351  //Stop immediately
352  break;
353  }
354 
355  //Format the RCPT TO command
356  for(i = 0; i < mail->recipientCount; i++)
357  {
358  //Skip recipient addresses that are not valid
359  if(!mail->recipients[i].addr)
360  continue;
361 
362  //Format the RCPT TO command
363  sprintf(context->buffer, "RCPT TO:<%s>\r\n", mail->recipients[i].addr);
364  //Send the command to the server
365  error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
366  //Any communication error to report?
367  if(error)
368  break;
369 
370  //Check SMTP response code
371  if(!SMTP_REPLY_CODE_2YZ(replyCode))
372  {
373  //An unexpected response was received...
375  //Stop immediately
376  break;
377  }
378  }
379 
380  //Propagate exception if necessary
381  if(error)
382  break;
383 
384  //Send message body
385  error = smtpSendData(context, mail);
386  //Any error to report?
387  if(error)
388  break;
389 
390  //End of exception handling block
391  } while(0);
392 
393  //Check status code
394  if(error == NO_ERROR ||
395  error == ERROR_UNEXPECTED_RESPONSE ||
397  {
398  //Properly disconnect from the SMTP server
399  smtpSendCommand(context, "QUIT\r\n", &replyCode, NULL);
400  }
401 
402 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
403  if(context->tlsContext != NULL)
404  {
405  //Gracefully close TLS session
406  tlsShutdown(context->tlsContext);
407  //Release TLS context
408  tlsFree(context->tlsContext);
409  }
410 #endif
411 
412  //Close socket
413  socketClose(context->socket);
414  //Clean up previously allocated resources
415  osFreeMem(context);
416 
417  //Return status code
418  return error;
419 }
420 
421 
422 /**
423  * @brief Callback function to parse EHLO response
424  * @param[in] context SMTP client context
425  * @param[in] replyLine Response line
426  * @param[in] replyCode Response code
427  * @return Error code
428  **/
429 
431  char_t *replyLine, uint_t replyCode)
432 {
433  char_t *p;
434  char_t *token;
435 
436  //The line must be at least 4 characters long
437  if(strlen(replyLine) < 4)
438  return NO_ERROR;
439 
440  //Skip the response code and the separator
441  replyLine += 4;
442 
443  //Get the first keyword
444  token = strtok_r(replyLine, " ", &p);
445  //Check whether the response line is empty
446  if(token == NULL)
447  return ERROR_INVALID_SYNTAX;
448 
449  //The AUTH keyword contains a space-separated list of
450  //names of available authentication mechanisms
451  if(!strcasecmp(token, "AUTH"))
452  {
453  //Process the rest of the line
454  while(1)
455  {
456  //Get the next keyword
457  token = strtok_r(NULL, " ", &p);
458  //Unable to find the next token?
459  if(token == NULL)
460  break;
461 
462  //LOGIN authentication mechanism supported?
463  if(!strcasecmp(token, "LOGIN"))
464  context->authLoginSupported = TRUE;
465  //PLAIN authentication mechanism supported?
466  else if(!strcasecmp(token, "PLAIN"))
467  context->authPlainSupported = TRUE;
468  //CRAM-MD5 authentication mechanism supported?
469  else if(!strcasecmp(token, "CRAM-MD5"))
470  context->authCramMd5Supported = TRUE;
471  }
472  }
473  //The STARTTLS keyword is used to tell the SMTP client
474  //that the SMTP server allows use of TLS
475  else if(!strcasecmp(token, "STARTTLS"))
476  {
477  //STARTTLS use is allowed
478  context->startTlsSupported = TRUE;
479  }
480 
481  //Successful processing
482  return NO_ERROR;
483 }
484 
485 
486 /**
487  * @brief Authentication using LOGIN mechanism
488  * @param[in] context SMTP client context
489  * @param[in] authInfo Authentication information
490  * @return Error code
491  **/
492 
494 {
495 #if (SMTP_CLIENT_LOGIN_AUTH_SUPPORT == ENABLED)
496  error_t error;
497  uint_t replyCode;
498 
499  //Send AUTH LOGIN command
500  error = smtpSendCommand(context, "AUTH LOGIN\r\n", &replyCode, NULL);
501 
502  //Any communication error to report?
503  if(error)
504  return error;
505  //Check SMTP reply code
506  if(!SMTP_REPLY_CODE_3YZ(replyCode))
508 
509  //Encode the user name with Base64 algorithm
510  base64Encode(authInfo->userName, strlen(authInfo->userName), context->buffer, NULL);
511  //Add a line feed
512  strcat(context->buffer, "\r\n");
513 
514  //Send the resulting string
515  error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
516  //Any communication error to report?
517  if(error)
518  return error;
519 
520  //Check SMTP reply code
521  if(!SMTP_REPLY_CODE_3YZ(replyCode))
523 
524  //Encode the password with Base64 algorithm
525  base64Encode(authInfo->password, strlen(authInfo->password), context->buffer, NULL);
526  //Add a line feed
527  strcat(context->buffer, "\r\n");
528 
529  //Send the resulting string
530  error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
531  //Any communication error to report?
532  if(error)
533  return error;
534 
535  //Check SMTP reply code
536  if(!SMTP_REPLY_CODE_2YZ(replyCode))
538 
539  //Successful authentication
540  return NO_ERROR;
541 #else
542  //LOGIN authentication is not supported
544 #endif
545 }
546 
547 
548 /**
549  * @brief Authentication using PLAIN mechanism
550  * @param[in] context SMTP client context
551  * @param[in] authInfo Authentication information
552  * @return Error code
553  **/
554 
556 {
557 #if (SMTP_CLIENT_PLAIN_AUTH_SUPPORT == ENABLED)
558  error_t error;
559  uint_t n;
560  uint_t replyCode;
561 
562  //Authorization identity
563  strcpy(context->buffer, authInfo->userName);
564  n = strlen(authInfo->userName) + 1;
565  //Authentication identity
566  strcpy(context->buffer + n, authInfo->userName);
567  n += strlen(authInfo->userName) + 1;
568  //Password
569  strcpy(context->buffer + n, authInfo->password);
570  n += strlen(authInfo->password);
571 
572  //Base64 encoding
573  base64Encode(context->buffer, n, context->buffer2, NULL);
574  //Format the AUTH PLAIN command
575  sprintf(context->buffer, "AUTH PLAIN %s\r\n", context->buffer2);
576 
577  //Send the command to the server
578  error = smtpSendCommand(context, context->buffer, &replyCode, NULL);
579  //Any communication error to report?
580  if(error)
581  return error;
582 
583  //Check SMTP reply code
584  if(!SMTP_REPLY_CODE_2YZ(replyCode))
586 
587  //Successful authentication
588  return NO_ERROR;
589 #else
590  //PLAIN authentication is not supported
592 #endif
593 }
594 
595 
596 /**
597  * @brief Authentication using CRAM-MD5 mechanism
598  * @param[in] context SMTP client context
599  * @param[in] authInfo Authentication information
600  * @return Error code
601  **/
602 
604 {
605 #if (SMTP_CLIENT_CRAM_MD5_AUTH_SUPPORT == ENABLED)
606  //Hex conversion table
607  static const char_t hexDigit[] =
608  {
609  '0', '1', '2', '3', '4', '5', '6', '7',
610  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
611  };
612 
613  //Local variables
614  error_t error;
615  uint_t n;
616  uint_t replyCode;
617 
618  //Alias pointers
619  uint8_t *challenge = (uint8_t *) context->buffer2;
620  uint8_t *digest = (uint8_t *) context->buffer;
621  char_t *textDigest = (char_t *) context->buffer2;
622 
623  //Send AUTH CRAM-MD5 command
624  error = smtpSendCommand(context, "AUTH CRAM-MD5\r\n", &replyCode, NULL);
625  //Any communication error to report?
626  if(error)
627  return error;
628 
629  //Check SMTP reply code
630  if(!SMTP_REPLY_CODE_3YZ(replyCode))
632 
633  //Compute the length of the response
634  n = strlen(context->buffer);
635  //Unexpected response from the SMTP server?
636  if(n <= 4)
637  return ERROR_INVALID_SYNTAX;
638 
639  //Decrypt the Base64 encoded challenge
640  error = base64Decode(context->buffer + 4, n - 4, challenge, &n);
641  //Decoding failed?
642  if(error)
643  return error;
644 
645  //Compute HMAC using MD5
646  error = hmacCompute(MD5_HASH_ALGO, authInfo->password,
647  strlen(authInfo->password), challenge, n, digest);
648  //HMAC computation failed?
649  if(error)
650  return error;
651 
652  //Convert the digest to text
653  for(n = 0; n < MD5_DIGEST_SIZE; n++)
654  {
655  //Convert upper nibble
656  textDigest[n * 2] = hexDigit[(digest[n] >> 4) & 0x0F];
657  //Then convert lower nibble
658  textDigest[n * 2 + 1] = hexDigit[digest[n] & 0x0F];
659  }
660 
661  //Properly terminate the string
662  textDigest[MD5_DIGEST_SIZE * 2] = '\0';
663  //Concatenate the user name and the text representation of the digest
664  sprintf(context->buffer, "%s %s", authInfo->userName, textDigest);
665  //Encode the resulting string with Base64 algorithm
666  base64Encode(context->buffer, strlen(context->buffer), context->buffer2, NULL);
667  //Add a line feed
668  strcat(context->buffer2, "\r\n");
669 
670  //Transmit the Base64 encoded string
671  error = smtpSendCommand(context, context->buffer2, &replyCode, NULL);
672  //Any communication error to report?
673  if(error)
674  return error;
675 
676  //Check SMTP reply code
677  if(!SMTP_REPLY_CODE_2YZ(replyCode))
679 
680  //Successful authentication
681  return NO_ERROR;
682 #else
683  //CRAM-MD5 authentication is not supported
685 #endif
686 }
687 
688 
689 /**
690  * @brief Send message body
691  * @param[in] context SMTP client context
692  * @param[in] mail Mail contents
693  * @return Error code
694  **/
695 
697 {
698  error_t error;
699  bool_t first;
700  uint_t i;
701  uint_t replyCode;
702  char_t *p;
703 
704  //Send DATA command
705  error = smtpSendCommand(context, "DATA\r\n", &replyCode, NULL);
706  //Any communication error to report?
707  if(error)
708  return error;
709 
710  //Check SMTP reply code
711  if(!SMTP_REPLY_CODE_3YZ(replyCode))
713 
714  //Point to the beginning of the buffer
715  p = context->buffer;
716 
717  //Current date and time
718  if(mail->dateTime && mail->dateTime[0] != '\0')
719  p += sprintf(p, "Date: %s\r\n", mail->dateTime);
720 
721  //Sender address
722  if(mail->from.addr)
723  {
724  //A friendly name may be associated with the sender address
725  if(mail->from.name && mail->from.name[0] != '\0')
726  p += sprintf(p, "From: \"%s\" <%s>\r\n", mail->from.name, mail->from.addr);
727  else
728  p += sprintf(p, "From: %s\r\n", mail->from.addr);
729  }
730 
731  //Recipients
732  for(i = 0, first = TRUE; i < mail->recipientCount; i++)
733  {
734  //Skip recipient addresses that are not valid
735  if(!mail->recipients[i].addr)
736  continue;
737 
738  //Check recipient type
739  if(mail->recipients[i].type & SMTP_RCPT_TYPE_TO)
740  {
741  //The first item of the list requires special processing
742  p += sprintf(p, first ? "To: " : ", ");
743 
744  //A friendly name may be associated with the address
745  if(mail->recipients[i].name && mail->recipients[i].name[0] != '\0')
746  p += sprintf(p, "\"%s\" <%s>", mail->recipients[i].name, mail->recipients[i].addr);
747  else
748  p += sprintf(p, "%s", mail->recipients[i].addr);
749 
750  //Prepare to add a new item to the list
751  first = FALSE;
752  }
753  }
754 
755  //Properly terminate the line with CRLF
756  if(!first)
757  p += sprintf(p, "\r\n");
758 
759  //Carbon copy
760  for(i = 0, first = TRUE; i < mail->recipientCount; i++)
761  {
762  //Skip recipient addresses that are not valid
763  if(!mail->recipients[i].addr)
764  continue;
765 
766  //Check recipient type
767  if(mail->recipients[i].type & SMTP_RCPT_TYPE_CC)
768  {
769  //The first item of the list requires special processing
770  p += sprintf(p, first ? "Cc: " : ", ");
771 
772  //A friendly name may be associated with the address
773  if(mail->recipients[i].name && mail->recipients[i].name[0] != '\0')
774  p += sprintf(p, "\"%s\" <%s>", mail->recipients[i].name, mail->recipients[i].addr);
775  else
776  p += sprintf(p, "%s", mail->recipients[i].addr);
777 
778  //Prepare to add a new item to the list
779  first = FALSE;
780  }
781  }
782 
783  //Properly terminate the line with CRLF
784  if(!first)
785  p += sprintf(p, "\r\n");
786 
787  //Subject
788  if(mail->subject)
789  p += sprintf(p, "Subject: %s\r\n", mail->subject);
790 
791  //The header and the body are separated by an empty line
792  sprintf(p, "\r\n");
793 
794  //Debug message
795  TRACE_DEBUG(context->buffer);
796  TRACE_DEBUG(mail->body);
797  TRACE_DEBUG("\r\n.\r\n");
798 
799  //Send message header
800  error = smtpWrite(context, context->buffer, strlen(context->buffer), 0);
801  //Any communication error to report?
802  if(error)
803  return error;
804 
805  //Send message body
806  error = smtpWrite(context, mail->body, strlen(mail->body), 0);
807  //Any communication error to report?
808  if(error)
809  return error;
810 
811  //Indicate the end of the mail data by sending a line containing only a "."
812  error = smtpSendCommand(context, "\r\n.\r\n", &replyCode, NULL);
813  //Any communication error to report?
814  if(error)
815  return error;
816 
817  //Check SMTP reply code
818  if(!SMTP_REPLY_CODE_2YZ(replyCode))
820 
821  //Successful operation
822  return NO_ERROR;
823 }
824 
825 
826 /**
827  * @brief Send SMTP command and wait for a reply
828  * @param[in] context SMTP client context
829  * @param[in] command Command line
830  * @param[out] replyCode SMTP server reply code
831  * @param[in] callback Optional callback to parse each line of the reply
832  * @return Error code
833  **/
834 
836  uint_t *replyCode, SmtpReplyCallback callback)
837 {
838  error_t error;
839  size_t length;
840  char_t *line;
841 
842  //Any command line to send?
843  if(command)
844  {
845  //Debug message
846  TRACE_DEBUG("SMTP client: %s", command);
847 
848  //Send the command to the SMTP server
849  error = smtpWrite(context, command, strlen(command), SOCKET_FLAG_WAIT_ACK);
850  //Failed to send command?
851  if(error)
852  return error;
853  }
854 
855  //Multiline replies are allowed for any command
856  while(1)
857  {
858  //Wait for a response from the server
859  error = smtpRead(context, context->buffer,
861 
862  //The remote server did not respond as expected?
863  if(error)
864  return error;
865 
866  //Properly terminate the string with a NULL character
867  context->buffer[length] = '\0';
868  //Remove all leading and trailing whitespace from the response
869  line = strTrimWhitespace(context->buffer);
870 
871  //Debug message
872  TRACE_DEBUG("SMTP server: %s\r\n", line);
873 
874  //Check the length of the response
875  if(strlen(line) < 3)
876  return ERROR_INVALID_SYNTAX;
877  //All replies begin with a three digit numeric code
878  if(!isdigit((uint8_t) line[0]) || !isdigit((uint8_t) line[1]) || !isdigit((uint8_t) line[2]))
879  return ERROR_INVALID_SYNTAX;
880  //A hyphen or a space character must follow the response code
881  if(line[3] != '-' && line[3] != ' ' && line[3] != '\0')
882  return ERROR_INVALID_SYNTAX;
883 
884  //Get the server response code
885  *replyCode = strtoul(line, NULL, 10);
886 
887  //Any callback function to call?
888  if(callback)
889  {
890  //Invoke callback function to parse the response line
891  error = callback(context, line, *replyCode);
892  //Check status code
893  if(error)
894  return error;
895  }
896 
897  //A hyphen follows the response code for all but the last line
898  if(line[3] != '-')
899  break;
900  }
901 
902  //Successful processing
903  return NO_ERROR;
904 }
905 
906 
907 /**
908  * @brief Send data to the SMTP server
909  * @param[in] context SMTP client context
910  * @param[in] data Pointer to a buffer containing the data to be transmitted
911  * @param[in] length Number of bytes to be transmitted
912  * @param[in] flags Set of flags that influences the behavior of this function
913  **/
914 
915 error_t smtpWrite(SmtpClientContext *context, const void *data, size_t length, uint_t flags)
916 {
917 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
918  //Check whether a secure connection is being used
919  if(context->tlsContext != NULL)
920  {
921  //Use TLS to transmit data to the SMTP server
922  return tlsWrite(context->tlsContext, data, length, NULL, flags);
923  }
924  else
925 #endif
926  {
927  //Transmit data to the SMTP server
928  return socketSend(context->socket, data, length, NULL, flags);
929  }
930 }
931 
932 
933 /**
934  * @brief Receive data from the SMTP server
935  * @param[in] context SMTP client context
936  * @param[out] data Buffer into which received data will be placed
937  * @param[in] size Maximum number of bytes that can be received
938  * @param[out] received Actual number of bytes that have been received
939  * @param[in] flags Set of flags that influences the behavior of this function
940  * @return Error code
941  **/
942 
943 error_t smtpRead(SmtpClientContext *context, void *data, size_t size, size_t *received, uint_t flags)
944 {
945 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
946  //Check whether a secure connection is being used
947  if(context->tlsContext != NULL)
948  {
949  //Use TLS to receive data from the SMTP server
950  return tlsRead(context->tlsContext, data, size, received, flags);
951  }
952  else
953 #endif
954  {
955  //Receive data from the SMTP server
956  return socketReceive(context->socket, data, size, received, flags);
957  }
958 }
959 
960 #endif
const char_t * subject
Definition: smtp_client.h:176
error_t(* SmtpReplyCallback)(SmtpClientContext *context, char_t *replyLine, uint_t replyCode)
Definition: smtp_client.h:201
error_t tlsWrite(TlsContext *context, const void *data, size_t length, size_t *written, uint_t flags)
Send application data to the remote host using TLS.
Definition: tls.c:1622
Socket * socket
Underlying socket.
Definition: smtp_client.h:187
error_t socketReceive(Socket *socket, void *data, size_t size, size_t *received, uint_t flags)
Receive data from a connected socket.
Definition: socket.c:584
char char_t
Definition: compiler_port.h:41
uint8_t flags
Definition: tcp.h:312
error_t smtpWrite(SmtpClientContext *context, const void *data, size_t length, uint_t flags)
Send data to the SMTP server.
Definition: smtp_client.c:915
SMTP client context.
Definition: smtp_client.h:185
void osFreeMem(void *p)
Release a previously allocated memory block.
TCP/IP stack core.
Debugging facilities.
error_t smtpSendAuthPlain(SmtpClientContext *context, const SmtpAuthInfo *authInfo)
Authentication using PLAIN mechanism.
Definition: smtp_client.c:555
error_t getHostByName(NetInterface *interface, const char_t *name, IpAddr *ipAddr, uint_t flags)
Resolve a host name into an IP address.
Definition: socket.c:1049
uint8_t p
Definition: ndp.h:295
Authentication information.
Definition: smtp_client.h:139
bool_t authLoginSupported
LOGIN authentication mechanism supported.
Definition: smtp_client.h:188
char_t * name
Definition: smtp_client.h:160
error_t tlsConnect(TlsContext *context)
Initiate the TLS handshake.
Definition: tls.c:1531
Invalid parameter.
Definition: error.h:45
error_t tlsSetConnectionEnd(TlsContext *context, TlsConnectionEnd entity)
Set operation mode (client or server)
Definition: tls.c:310
error_t base64Decode(const char_t *input, size_t inputLen, void *output, size_t *outputLen)
Base64 decoding algorithm.
Definition: base64.c:186
IP network address.
Definition: ip.h:57
error_t tlsRead(TlsContext *context, void *data, size_t size, size_t *received, uint_t flags)
Receive application data from a the remote host using TLS.
Definition: tls.c:1740
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:797
#define SMTP_CLIENT_MAX_LINE_LENGTH
Definition: smtp_client.h:51
String manipulation helper functions.
#define strtok_r(str, delim, p)
void base64Encode(const void *input, size_t inputLen, char_t *output, size_t *outputLen)
Base64 encoding algorithm.
Definition: base64.c:76
char_t buffer2[SMTP_CLIENT_MAX_LINE_LENGTH/2]
Definition: smtp_client.h:193
bool_t startTlsSupported
STARTTLS command supported.
Definition: smtp_client.h:191
#define TRUE
Definition: os_port.h:48
uint16_t first
Definition: ipv4_frag.h:93
#define strcasecmp
error_t smtpSendAuthCramMd5(SmtpClientContext *context, const SmtpAuthInfo *authInfo)
Authentication using CRAM-MD5 mechanism.
Definition: smtp_client.c:603
char_t * dateTime
Definition: smtp_client.h:175
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:216
#define tlsSetSocket(context, socket)
Definition: tls.h:821
uint8_t authInfo[]
char_t * addr
Definition: smtp_client.h:161
#define SMTP_CLIENT_DEFAULT_TIMEOUT
Definition: smtp_client.h:44
const char_t * body
Definition: smtp_client.h:177
SmtpMailAddr from
Definition: smtp_client.h:172
TlsContext * tlsContext
TLS context.
Definition: smtp_client.h:195
const SmtpMailAddr * recipients
Definition: smtp_client.h:173
bool_t authCramMd5Supported
CRAM-MD5 authentication mechanism supported.
Definition: smtp_client.h:190
error_t tlsShutdown(TlsContext *context)
Gracefully close TLS session.
Definition: tls.c:2018
void tlsFree(TlsContext *context)
Release TLS context.
Definition: tls.c:2178
SMTP client (Simple Mail Transfer Protocol)
error_t smtpRead(SmtpClientContext *context, void *data, size_t size, size_t *received, uint_t flags)
Receive data from the SMTP server.
Definition: smtp_client.c:943
#define TRACE_INFO(...)
Definition: debug.h:86
error_t smtpSendAuthLogin(SmtpClientContext *context, const SmtpAuthInfo *authInfo)
Authentication using LOGIN mechanism.
Definition: smtp_client.c:493
error_t tlsSetPrng(TlsContext *context, const PrngAlgo *prngAlgo, void *prngContext)
Set the pseudo-random number generator to be used.
Definition: tls.c:336
Success.
Definition: error.h:42
error_t socketConnect(Socket *socket, const IpAddr *remoteIpAddr, uint16_t remotePort)
Establish a connection to a specified socket.
Definition: socket.c:357
void * osAllocMem(size_t size)
Allocate a memory block.
error_t
Error codes.
Definition: error.h:40
bool_t authPlainSupported
PLAIN authentication mechanism supported.
Definition: smtp_client.h:189
unsigned int uint_t
Definition: compiler_port.h:43
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:92
uint8_t token[]
Definition: coap_common.h:181
error_t smtpSendCommand(SmtpClientContext *context, const char_t *command, uint_t *replyCode, SmtpReplyCallback callback)
Send SMTP command and wait for a reply.
Definition: smtp_client.c:835
uint8_t data[]
Definition: dtls_misc.h:167
error_t hmacCompute(const HashAlgo *hash, const void *key, size_t keyLen, const void *data, size_t dataLen, uint8_t *digest)
Compute HMAC using the specified hash function.
Definition: hmac.c:83
char_t * strTrimWhitespace(char_t *s)
Removes all leading and trailing whitespace from a string.
Definition: str.c:68
#define MD5_DIGEST_SIZE
Definition: md5.h:38
error_t smtpSendData(SmtpClientContext *context, const SmtpMail *mail)
Send message body.
Definition: smtp_client.c:696
TlsContext * tlsInit(void)
TLS context initialization.
Definition: tls.c:63
char_t buffer[SMTP_CLIENT_MAX_LINE_LENGTH/2]
Memory buffer for input/output operations.
Definition: smtp_client.h:192
error_t smtpEhloReplyCallback(SmtpClientContext *context, char_t *replyLine, uint_t replyCode)
Callback function to parse EHLO response.
Definition: smtp_client.c:430
uint_t recipientCount
Definition: smtp_client.h:174
Socket API.
#define SMTP_REPLY_CODE_2YZ(code)
Definition: smtp_client.h:112
uint8_t length
Definition: dtls_misc.h:140
uint8_t n
error_t smtpSendMail(const SmtpAuthInfo *authInfo, const SmtpMail *mail)
Send a mail to the specified recipients.
Definition: smtp_client.c:61
#define FALSE
Definition: os_port.h:44
#define SMTP_REPLY_CODE_3YZ(code)
Definition: smtp_client.h:113
error_t socketSend(Socket *socket, const void *data, size_t length, size_t *written, uint_t flags)
Send data to a connected socket.
Definition: socket.c:490
int bool_t
Definition: compiler_port.h:47
#define MD5_HASH_ALGO
Definition: md5.h:42
error_t socketBindToInterface(Socket *socket, NetInterface *interface)
Bind a socket to a particular network interface.
Definition: socket.c:309
Mail contents.
Definition: smtp_client.h:170
#define TRACE_DEBUG(...)
Definition: debug.h:98