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  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2026 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @section Description
28  *
29  * SMTP is designed as a mail transport and delivery protocol. Refer to
30  * the following RFCs for complete details:
31  * - RFC 5321: Simple Mail Transfer Protocol
32  * - RFC 4954: SMTP Service Extension for Authentication
33  * - RFC 3207: SMTP Service Extension for Secure SMTP over TLS
34  *
35  * @author Oryx Embedded SARL (www.oryx-embedded.com)
36  * @version 2.6.4
37  **/
38 
39 //Switch to the appropriate trace level
40 #define TRACE_LEVEL SMTP_TRACE_LEVEL
41 
42 //Dependencies
43 #include "core/net.h"
44 #include "smtp/smtp_client.h"
45 #include "smtp/smtp_client_auth.h"
47 #include "smtp/smtp_client_misc.h"
48 #include "str.h"
49 #include "debug.h"
50 
51 //Check TCP/IP stack configuration
52 #if (SMTP_CLIENT_SUPPORT == ENABLED)
53 
54 
55 /**
56  * @brief Initialize SMTP client context
57  * @param[in] context Pointer to the SMTP client context
58  * @return Error code
59  **/
60 
62 {
63 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
64  error_t error;
65 #endif
66 
67  //Make sure the SMTP client context is valid
68  if(context == NULL)
70 
71  //Clear SMTP client context
72  osMemset(context, 0, sizeof(SmtpClientContext));
73 
74  //Attach TCP/IP stack context
75  context->netContext = netGetDefaultContext();
76 
77 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
78  //Initialize TLS session state
79  error = tlsInitSessionState(&context->tlsSession);
80  //Any error to report?
81  if(error)
82  return error;
83 #endif
84 
85  //Initialize SMTP client state
86  context->state = SMTP_CLIENT_STATE_DISCONNECTED;
87 
88  //Default timeout
89  context->timeout = SMTP_CLIENT_DEFAULT_TIMEOUT;
90 
91  //Successful initialization
92  return NO_ERROR;
93 }
94 
95 
96 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
97 
98 /**
99  * @brief Register TLS initialization callback function
100  * @param[in] context Pointer to the SMTP client context
101  * @param[in] callback TLS initialization callback function
102  * @return Error code
103  **/
104 
106  SmtpClientTlsInitCallback callback)
107 {
108  //Check parameters
109  if(context == NULL || callback == NULL)
111 
112  //Save callback function
113  context->tlsInitCallback = callback;
114 
115  //Successful processing
116  return NO_ERROR;
117 }
118 
119 #endif
120 
121 
122 /**
123  * @brief Set communication timeout
124  * @param[in] context Pointer to the SMTP client context
125  * @param[in] timeout Timeout value, in milliseconds
126  * @return Error code
127  **/
128 
130 {
131  //Make sure the SMTP client context is valid
132  if(context == NULL)
134 
135  //Save timeout value
136  context->timeout = timeout;
137 
138  //Successful processing
139  return NO_ERROR;
140 }
141 
142 
143 /**
144  * @brief Bind the SMTP client to a particular network interface
145  * @param[in] context Pointer to the SMTP client context
146  * @param[in] interface Network interface to be used
147  * @return Error code
148  **/
149 
151  NetInterface *interface)
152 {
153  //Make sure the SMTP client context is valid
154  if(context == NULL)
156 
157  //Explicitly associate the SMTP client with the specified interface
158  context->interface = interface;
159 
160  //Successful processing
161  return NO_ERROR;
162 }
163 
164 
165 /**
166  * @brief Establish a connection with the specified SMTP server
167  * @param[in] context Pointer to the SMTP client context
168  * @param[in] serverIpAddr IP address of the SMTP server
169  * @param[in] serverPort Port number
170  * @param[in] mode SMTP connection mode
171  * @return Error code
172  **/
173 
175  const IpAddr *serverIpAddr, uint16_t serverPort, SmtpConnectionMode mode)
176 {
177  error_t error;
178 
179  //Check parameters
180  if(context == NULL || serverIpAddr == NULL)
182 
183 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
184  //Check connection mode
185  if(mode != SMTP_MODE_PLAINTEXT &&
186  mode != SMTP_MODE_IMPLICIT_TLS &&
187  mode != SMTP_MODE_EXPLICIT_TLS)
188  {
189  //The connection mode is not valid
191  }
192 #else
193  //Check connection mode
194  if(mode != SMTP_MODE_PLAINTEXT)
195  {
196  //The connection mode is not valid
198  }
199 #endif
200 
201  //Initialize status code
202  error = NO_ERROR;
203 
204  //Establish connection with the SMTP server
205  while(!error)
206  {
207  //Check current state
208  if(context->state == SMTP_CLIENT_STATE_DISCONNECTED)
209  {
210  //Reset parameters
211  context->startTlsSupported = FALSE;
212  context->authLoginSupported = FALSE;
213  context->authPlainSupported = FALSE;
214  context->authCramMd5Supported = FALSE;
215 
216 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
217  //Reset MIME-specific parameters
218  osStrcpy(context->contentType, "");
219  osStrcpy(context->boundary, "this-is-a-boundary");
220 #endif
221  //Open TCP socket
222  error = smtpClientOpenConnection(context);
223 
224  //Check status code
225  if(!error)
226  {
227  //Establish TCP connection
229  }
230  }
231  else if(context->state == SMTP_CLIENT_STATE_CONNECTING_TCP)
232  {
233  //Establish TCP connection
234  error = smtpClientEstablishConnection(context, serverIpAddr,
235  serverPort);
236 
237  //Check status code
238  if(!error)
239  {
240  //Implicit TLS?
241  if(mode == SMTP_MODE_IMPLICIT_TLS)
242  {
243  //TLS initialization
244  error = smtpClientOpenSecureConnection(context);
245 
246  //Check status code
247  if(!error)
248  {
249  //Perform TLS handshake
251  }
252  }
253  else
254  {
255  //Flush buffer
256  context->bufferPos = 0;
257  context->commandLen = 0;
258  context->replyLen = 0;
259 
260  //Wait for the connection greeting reply
262  }
263  }
264  }
265  else if(context->state == SMTP_CLIENT_STATE_CONNECTING_TLS)
266  {
267  //Perform TLS handshake
268  error = smtpClientEstablishSecureConnection(context);
269 
270  //Check status code
271  if(!error)
272  {
273  //Implicit TLS?
274  if(mode == SMTP_MODE_IMPLICIT_TLS)
275  {
276  //Flush buffer
277  context->bufferPos = 0;
278  context->commandLen = 0;
279  context->replyLen = 0;
280 
281  //Wait for the connection greeting reply
283  }
284  else
285  {
286  //The client should send an EHLO command as the first command
287  //after a successful TLS negotiation (refer to RFC 3207,
288  //section 4.2)
289  error = smtpClientFormatCommand(context, "EHLO [127.0.0.1]", NULL);
290 
291  //Check status code
292  if(!error)
293  {
294  //Send EHLO command and wait for the server's response
296  }
297  }
298  }
299  }
300  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
301  {
302  //Wait for the connection greeting reply
303  error = smtpClientSendCommand(context, NULL);
304 
305  //Check status code
306  if(!error)
307  {
308  //Check SMTP response code
309  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
310  {
311  //Format EHLO command
312  error = smtpClientFormatCommand(context, "EHLO [127.0.0.1]", NULL);
313 
314  //Check status code
315  if(!error)
316  {
317  //Send EHLO command and wait for the server's response
319  }
320  }
321  else
322  {
323  //Report an error
325  }
326  }
327  }
328  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_2)
329  {
330  //Send EHLO command and wait for the server's response
332 
333  //Check status code
334  if(!error)
335  {
336  //Check SMTP response code
337  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
338  {
339 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
340  //Implicit or explicit TLS?
341  if(mode == SMTP_MODE_IMPLICIT_TLS)
342  {
343  //Save TLS session
344  error = smtpClientSaveSession(context);
345 
346  //Check status code
347  if(!error)
348  {
349  //The SMTP client is connected
351  }
352  }
353  else if(mode == SMTP_MODE_EXPLICIT_TLS)
354  {
355  //Valid TLS context?
356  if(context->tlsContext != NULL)
357  {
358  //Save TLS session
359  error = smtpClientSaveSession(context);
360 
361  //Check status code
362  if(!error)
363  {
364  //The SMTP client is connected
366  }
367  }
368  else
369  {
370  //Format STARTTLS command
371  error = smtpClientFormatCommand(context, "STARTTLS", NULL);
372 
373  //Check status code
374  if(!error)
375  {
376  //Send STARTTLS command and wait for the server's response
378  }
379  }
380  }
381  else
382 #endif
383  {
384  //The SMTP client is connected
386  }
387  }
388  else
389  {
390  //Report an error
392  }
393  }
394  }
395  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_3)
396  {
397  //Send STARTTLS command and wait for the server's response
398  error = smtpClientSendCommand(context, NULL);
399 
400  //Check status code
401  if(!error)
402  {
403  //Check SMTP response code
404  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
405  {
406  //TLS initialization
407  error = smtpClientOpenSecureConnection(context);
408 
409  //Check status code
410  if(!error)
411  {
412  //Perform TLS handshake
414  }
415  }
416  else
417  {
418  //Report an error
420  }
421  }
422  }
423  else if(context->state == SMTP_CLIENT_STATE_CONNECTED)
424  {
425  //The SMTP client is connected
426  break;
427  }
428  else
429  {
430  //Invalid state
431  error = ERROR_WRONG_STATE;
432  }
433  }
434 
435  //Check status code
436  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
437  {
438  //Check whether the timeout has elapsed
439  error = smtpClientCheckTimeout(context);
440  }
441 
442  //Failed to establish connection with the SMTP server?
443  if(error != NO_ERROR && error != ERROR_WOULD_BLOCK)
444  {
445  //Clean up side effects
446  smtpClientCloseConnection(context);
447  //Update SMTP client state
449  }
450 
451  //Return status code
452  return error;
453 }
454 
455 
456 /**
457  * @brief Login to the SMTP server using the provided user name and password
458  * @param[in] context Pointer to the SMTP client context
459  * @param[in] username NULL-terminated string containing the user name
460  * @param[in] password NULL-terminated string containing the user's password
461  * @return Error code
462  **/
463 
465  const char_t *password)
466 {
467  error_t error;
468 
469  //Check parameters
470  if(context == NULL || username == NULL || password == NULL)
472 
473 #if (SMTP_CLIENT_CRAM_MD5_AUTH_SUPPORT == ENABLED)
474  //CRAM-MD5 authentication mechanism supported?
475  if(context->authCramMd5Supported)
476  {
477  //Perform CRAM-MD5 authentication
478  error = smtpClientCramMd5Auth(context, username, password);
479  }
480  else
481 #endif
482 #if (SMTP_CLIENT_LOGIN_AUTH_SUPPORT == ENABLED)
483  //LOGIN authentication mechanism supported?
484  if(context->authLoginSupported)
485  {
486  //Perform LOGIN authentication
487  error = smtpClientLoginAuth(context, username, password);
488  }
489  else
490 #endif
491 #if (SMTP_CLIENT_PLAIN_AUTH_SUPPORT == ENABLED)
492  //PLAIN authentication mechanism supported?
493  if(context->authPlainSupported)
494  {
495  //Perform PLAIN authentication
496  error = smtpClientPlainAuth(context, username, password);
497  }
498  else
499 #endif
500  {
501  //Report an error
503  }
504 
505  //Return status code
506  return error;
507 }
508 
509 
510 /**
511  * @brief Set the content type to be used
512  * @param[in] context Pointer to the SMTP client context
513  * @param[in] contentType NULL-terminated string that holds the content type
514  * @return Error code
515  **/
516 
518  const char_t *contentType)
519 {
520 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
521  size_t n;
522 
523  //Check parameters
524  if(context == NULL || contentType == NULL)
526 
527  //Retrieve the length of the boundary string
528  n = osStrlen(contentType);
529 
530  //Check the length of the string
531  if(n < 1 || n > SMTP_CLIENT_CONTENT_TYPE_MAX_LEN)
532  return ERROR_INVALID_LENGTH;
533 
534  //Save content type
535  osStrcpy(context->contentType, contentType);
536 
537  //Successful processing
538  return NO_ERROR;
539 #else
540  //MIME extension is not implemented
541  return ERROR_NOT_IMPLEMENTED;
542 #endif
543 }
544 
545 
546 /**
547  * @brief Define the boundary string to be used (multipart encoding)
548  * @param[in] context Pointer to the SMTP client context
549  * @param[in] boundary NULL-terminated string that holds the boundary string
550  * @return Error code
551  **/
552 
554  const char_t *boundary)
555 {
556 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
557  size_t n;
558 
559  //Check parameters
560  if(context == NULL || boundary == NULL)
562 
563  //Retrieve the length of the boundary string
564  n = osStrlen(boundary);
565 
566  //The boundary parameter consists of 1 to 70 characters
567  if(n < 1 || n > SMTP_CLIENT_BOUNDARY_MAX_LEN)
568  return ERROR_INVALID_LENGTH;
569 
570  //Save boundary string
571  osStrcpy(context->boundary, boundary);
572 
573  //Successful processing
574  return NO_ERROR;
575 #else
576  //MIME extension is not implemented
577  return ERROR_NOT_IMPLEMENTED;
578 #endif
579 }
580 
581 
582 /**
583  * @brief Write email header
584  * @param[in] context Pointer to the SMTP client context
585  * @param[in] from Email address of the sender
586  * @param[in] recipients Email addresses of the recipients
587  * @param[in] numRecipients Number of email addresses in the list
588  * @param[in] subject NULL-terminated string containing the email subject
589  * @return Error code
590  **/
591 
593  const SmtpMailAddr *from, const SmtpMailAddr *recipients,
594  uint_t numRecipients, const char_t *subject)
595 {
596  error_t error;
597  size_t n;
598 
599  //Check parameters
600  if(context == NULL || from == NULL || recipients == NULL || subject == NULL)
602 
603  //Initialize status code
604  error = NO_ERROR;
605 
606  //Execute SMTP command sequence
607  while(!error)
608  {
609  //Check current state
610  if(context->state == SMTP_CLIENT_STATE_CONNECTED)
611  {
612  //Format MAIL FROM command
613  error = smtpClientFormatCommand(context, "MAIL FROM", from->addr);
614 
615  //Check status code
616  if(!error)
617  {
618  //Point to the first recipient of the list
619  context->recipientIndex = 0;
620  //Send MAIL FROM command and wait for the server's response
622  }
623  }
624  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
625  {
626  //Wait for the server's response
627  error = smtpClientSendCommand(context, NULL);
628 
629  //Check status code
630  if(!error)
631  {
632  //Check SMTP response code
633  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
634  {
635  //Process each recipients of the list
636  if(context->recipientIndex < numRecipients)
637  {
638  //Format RCPT TO command
639  error = smtpClientFormatCommand(context, "RCPT TO",
640  recipients[context->recipientIndex].addr);
641 
642  //Check status code
643  if(!error)
644  {
645  //Point to the next recipient
646  context->recipientIndex++;
647  //Send RCPT TO command and wait for the server's response
649  }
650  }
651  else
652  {
653  //Format DATA command
654  error = smtpClientFormatCommand(context, "DATA", NULL);
655 
656  //Check status code
657  if(!error)
658  {
659  //Send DATA command and wait for the server's response
661  }
662  }
663  }
664  else
665  {
666  //Update SMTP client state
668  //Report an error
670  }
671  }
672  }
673  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_2)
674  {
675  //Send DATA command and wait for the server's response
676  error = smtpClientSendCommand(context, NULL);
677 
678  //Check status code
679  if(!error)
680  {
681  //Check SMTP response code
682  if(SMTP_REPLY_CODE_3YZ(context->replyCode))
683  {
684  //Format email header
685  error = smtpClientFormatMailHeader(context, from, recipients,
686  numRecipients, subject);
687 
688  //Check status code
689  if(!error)
690  {
691  //Send email header
693  }
694  }
695  else
696  {
697  //Report an error
699  }
700  }
701  }
702  else if(context->state == SMTP_CLIENT_STATE_MAIL_HEADER)
703  {
704  //Send email header
705  if(context->bufferPos < context->bufferLen)
706  {
707  //Send more data
708  error = smtpClientSendData(context,
709  context->buffer + context->bufferPos,
710  context->bufferLen - context->bufferPos, &n, 0);
711 
712  //Check status code
713  if(error == NO_ERROR || error == ERROR_TIMEOUT)
714  {
715  //Advance data pointer
716  context->bufferPos += n;
717  }
718  }
719  else
720  {
721  //Flush transmit buffer
722  context->bufferPos = 0;
723  context->bufferLen = 0;
724 
725  //Update SMTP client state
727  //The email header has been successfully written
728  break;
729  }
730  }
731  else
732  {
733  //Invalid state
734  error = ERROR_WRONG_STATE;
735  }
736  }
737 
738  //Check status code
739  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
740  {
741  //Check whether the timeout has elapsed
742  error = smtpClientCheckTimeout(context);
743  }
744 
745  //Return status code
746  return error;
747 }
748 
749 
750 /**
751  * @brief Write email body
752  * @param[in] context Pointer to the SMTP client context
753  * @param[in] data Pointer to a buffer containing the data to be written
754  * @param[in] length Number of data bytes to write
755  * @param[in] written Number of bytes that have been written (optional parameter)
756  * @param[in] flags Set of flags that influences the behavior of this function
757  * @return Error code
758  **/
759 
761  const void *data, size_t length, size_t *written, uint_t flags)
762 {
763  error_t error;
764  size_t n;
765 
766  //Make sure the SMTP client context is valid
767  if(context == NULL)
769 
770  //Check parameters
771  if(data == NULL && length != 0)
773 
774  //Actual number of bytes written
775  n = 0;
776 
777  //Check current state
778  if(context->state == SMTP_CLIENT_STATE_MAIL_BODY)
779  {
780  //Transmit the contents of the body
781  error = smtpClientSendData(context, data, length, &n, flags);
782 
783  //Check status code
784  if(error == NO_ERROR || error == ERROR_TIMEOUT)
785  {
786  //Any data transmitted?
787  if(n > 0)
788  {
789  //Save current time
790  context->timestamp = osGetSystemTime();
791  }
792  }
793  }
794  else
795  {
796  //Invalid state
797  error = ERROR_WRONG_STATE;
798  }
799 
800  //Check status code
801  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
802  {
803  //Check whether the timeout has elapsed
804  error = smtpClientCheckTimeout(context);
805  }
806 
807  //Total number of data that have been written
808  if(written != NULL)
809  {
810  *written = n;
811  }
812 
813  //Return status code
814  return error;
815 }
816 
817 
818 /**
819  * @brief Write multipart header
820  * @param[in] context Pointer to the SMTP client context
821  * @param[in] filename NULL-terminated string that holds the file name
822  * (optional parameter)
823  * @param[in] contentType NULL-terminated string that holds the content type
824  * (optional parameter)
825  * @param[in] contentTransferEncoding NULL-terminated string that holds the
826  * content transfer encoding (optional parameter)
827  * @param[in] last This flag indicates whether the multipart header is the
828  * final one
829  * @return Error code
830  **/
831 
833  const char_t *filename, const char_t *contentType,
834  const char_t *contentTransferEncoding, bool_t last)
835 {
836 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
837  error_t error;
838  size_t n;
839 
840  //Make sure the SMTP client context is valid
841  if(context == NULL)
843 
844  //Initialize status code
845  error = NO_ERROR;
846 
847  //Format and send multipart header
848  while(!error)
849  {
850  //Check current state
851  if(context->state == SMTP_CLIENT_STATE_MAIL_BODY ||
852  context->state == SMTP_CLIENT_STATE_MULTIPART_BODY)
853  {
854  //Any data residue?
855  if(context->bufferLen > 0 && context->bufferLen < 4)
856  {
857  //Encode the final quantum
858  base64Encode(context->buffer, context->bufferLen,
859  context->buffer, &n);
860 
861  //Save the length of the Base64-encoded string
862  context->bufferLen = n;
863  context->bufferPos = 0;
864  }
865  else if(context->bufferPos < context->bufferLen)
866  {
867  //Send more data
868  error = smtpClientSendData(context,
869  context->buffer + context->bufferPos,
870  context->bufferLen - context->bufferPos, &n, 0);
871 
872  //Check status code
873  if(error == NO_ERROR || error == ERROR_TIMEOUT)
874  {
875  //Advance data pointer
876  context->bufferPos += n;
877  }
878  }
879  else
880  {
881  //Rewind to the beginning of the buffer
882  context->bufferPos = 0;
883  context->bufferLen = 0;
884 
885  //Format multipart header
886  error = smtpClientFormatMultipartHeader(context, filename,
887  contentType, contentTransferEncoding, last);
888 
889  //Check status code
890  if(!error)
891  {
892  //Send multipart header
894  }
895  }
896  }
897  else if(context->state == SMTP_CLIENT_STATE_MULTIPART_HEADER)
898  {
899  //Send multipart header
900  if(context->bufferPos < context->bufferLen)
901  {
902  //Send more data
903  error = smtpClientSendData(context,
904  context->buffer + context->bufferPos,
905  context->bufferLen - context->bufferPos, &n, 0);
906 
907  //Check status code
908  if(error == NO_ERROR || error == ERROR_TIMEOUT)
909  {
910  //Advance data pointer
911  context->bufferPos += n;
912  }
913  }
914  else
915  {
916  //Rewind to the beginning of the buffer
917  context->bufferPos = 0;
918  context->bufferLen = 0;
919 
920  //Last multipart header?
921  if(last)
922  {
923  //The last multipart header has been successfully transmitted
925  }
926  else
927  {
928  //Send multipart body
930  }
931 
932  //The email header has been successfully written
933  break;
934  }
935  }
936  else
937  {
938  //Invalid state
939  error = ERROR_WRONG_STATE;
940  }
941  }
942 
943  //Check status code
944  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
945  {
946  //Check whether the timeout has elapsed
947  error = smtpClientCheckTimeout(context);
948  }
949 
950  //Return status code
951  return error;
952 #else
953  //MIME extension is not implemented
954  return ERROR_NOT_IMPLEMENTED;
955 #endif
956 }
957 
958 
959 /**
960  * @brief Write data to the multipart body
961  * @param[in] context Pointer to the SMTP client context
962  * @param[in] data Pointer to the buffer containing the data to be transmitted
963  * @param[in] length Number of data bytes to send
964  * @param[out] written Actual number of bytes written (optional parameter)
965  * @param[in] flags Set of flags that influences the behavior of this function
966  * @return Error code
967  **/
968 
970  const void *data, size_t length, size_t *written, uint_t flags)
971 {
972 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
973  error_t error;
974  size_t n;
975  size_t totalLength;
976 
977  //Make sure the SMTP client context is valid
978  if(context == NULL)
980 
981  //Check parameters
982  if(data == NULL && length != 0)
984 
985  //Initialize status code
986  error = NO_ERROR;
987 
988  //Actual number of bytes written
989  totalLength = 0;
990 
991  //Check current state
992  if(context->state == SMTP_CLIENT_STATE_MULTIPART_BODY)
993  {
994  //Base64 encoding?
995  if(context->base64Encoding)
996  {
997  //Send as much data as possible
998  while(totalLength < length && !error)
999  {
1000  //Any data pending in the transmit buffer?
1001  if(context->bufferLen < 4)
1002  {
1003  //Base64 maps a 3-byte block to 4 printable characters
1004  n = (SMTP_CLIENT_BUFFER_SIZE * 3) / 4;
1005 
1006  //Calculate the number of bytes to copy at a time
1007  n = MIN(n - context->bufferLen, length - totalLength);
1008 
1009  //The raw data must be an integral multiple of 24 bits
1010  if((context->bufferLen + n) > 3)
1011  {
1012  n -= (context->bufferLen + n) % 3;
1013  }
1014 
1015  //Copy the raw data to the transmit buffer
1016  osMemcpy(context->buffer + context->bufferLen, data, n);
1017 
1018  //Advance data pointer
1019  data = (uint8_t *) data + n;
1020  //Update the length of the buffer
1021  context->bufferLen += n;
1022  //Actual number of bytes written
1023  totalLength += n;
1024 
1025  //The raw data is processed block by block
1026  if(context->bufferLen >= 3)
1027  {
1028  //Encode the data with Base64 algorithm
1029  base64Encode(context->buffer, context->bufferLen,
1030  context->buffer, &n);
1031 
1032  //Save the length of the Base64-encoded string
1033  context->bufferLen = n;
1034  context->bufferPos = 0;
1035  }
1036  }
1037  else if(context->bufferPos < context->bufferLen)
1038  {
1039  //Send more data
1040  error = smtpClientSendData(context,
1041  context->buffer + context->bufferPos,
1042  context->bufferLen - context->bufferPos, &n, 0);
1043 
1044  //Check status code
1045  if(error == NO_ERROR || error == ERROR_TIMEOUT)
1046  {
1047  //Any data transmitted?
1048  if(n > 0)
1049  {
1050  //Advance data pointer
1051  context->bufferPos += n;
1052  //Save current time
1053  context->timestamp = osGetSystemTime();
1054  }
1055  }
1056  }
1057  else
1058  {
1059  //Rewind to the beginning of the buffer
1060  context->bufferPos = 0;
1061  context->bufferLen = 0;
1062  }
1063  }
1064  }
1065  else
1066  {
1067  //Send raw data
1068  error = smtpClientSendData(context, data, length, &n, flags);
1069 
1070  //Check status code
1071  if(error == NO_ERROR || error == ERROR_TIMEOUT)
1072  {
1073  //Any data transmitted?
1074  if(n > 0)
1075  {
1076  //Actual number of bytes written
1077  totalLength += n;
1078  //Save current time
1079  context->timestamp = osGetSystemTime();
1080  }
1081  }
1082  }
1083  }
1084  else
1085  {
1086  //Invalid state
1087  error = ERROR_WRONG_STATE;
1088  }
1089 
1090  //Check status code
1091  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1092  {
1093  //Check whether the timeout has elapsed
1094  error = smtpClientCheckTimeout(context);
1095  }
1096 
1097  //Total number of data that have been written
1098  if(written != NULL)
1099  {
1100  *written = totalLength;
1101  }
1102 
1103  //Return status code
1104  return error;
1105 #else
1106  //MIME extension is not implemented
1107  return ERROR_NOT_IMPLEMENTED;
1108 #endif
1109 }
1110 
1111 
1112 /**
1113  * @brief Complete email sending process and wait for server's status
1114  * @param[in] context Pointer to the SMTP client context
1115  * @return Error code
1116  **/
1117 
1119 {
1120  error_t error;
1121 
1122  //Make sure the SMTP client context is valid
1123  if(context == NULL)
1124  return ERROR_INVALID_PARAMETER;
1125 
1126  //Initialize status code
1127  error = NO_ERROR;
1128 
1129  //Execute SMTP command sequence
1130  while(!error)
1131  {
1132  //Check current state
1133  if(context->state == SMTP_CLIENT_STATE_MAIL_BODY)
1134  {
1135  //SMTP indicates the end of the mail data by sending a line containing
1136  //only a "." (refer to RFC 5321, section 3.3)
1137  error = smtpClientFormatCommand(context, ".", NULL);
1138 
1139  //Check status code
1140  if(!error)
1141  {
1142  //Wait for the server's response
1144  }
1145  }
1146  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
1147  {
1148  //Wait for the server's response
1149  error = smtpClientSendCommand(context, NULL);
1150 
1151  //Check status code
1152  if(!error)
1153  {
1154  //Check SMTP response code
1155  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
1156  {
1157  //Update SMTP client state
1159  //The email has been accepted by the server
1160  break;
1161  }
1162  else
1163  {
1164  //Update SMTP client state
1166  //Report an error
1167  error = ERROR_UNEXPECTED_RESPONSE;
1168  }
1169  }
1170  }
1171  else
1172  {
1173  //Invalid state
1174  error = ERROR_WRONG_STATE;
1175  }
1176  }
1177 
1178  //Check status code
1179  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1180  {
1181  //Check whether the timeout has elapsed
1182  error = smtpClientCheckTimeout(context);
1183  }
1184 
1185  //Return status code
1186  return error;
1187 }
1188 
1189 
1190 /**
1191  * @brief Retrieve server's reply code
1192  * @param[in] context Pointer to the SMTP client context
1193  * @return SMTP reply code
1194  **/
1195 
1197 {
1198  uint_t replyCode;
1199 
1200  //Make sure the SMTP client context is valid
1201  if(context != NULL)
1202  {
1203  //Get server's reply code
1204  replyCode = context->replyCode;
1205  }
1206  else
1207  {
1208  //The SMTP client context is not valid
1209  replyCode = 0;
1210  }
1211 
1212  //Return SMTP reply code
1213  return replyCode;
1214 }
1215 
1216 
1217 /**
1218  * @brief Gracefully disconnect from the SMTP server
1219  * @param[in] context Pointer to the SMTP client context
1220  * @return Error code
1221  **/
1222 
1224 {
1225  error_t error;
1226 
1227  //Make sure the SMTP client context is valid
1228  if(context == NULL)
1229  return ERROR_INVALID_PARAMETER;
1230 
1231  //Initialize status code
1232  error = NO_ERROR;
1233 
1234  //Execute SMTP command sequence
1235  while(!error)
1236  {
1237  //Check current state
1238  if(context->state == SMTP_CLIENT_STATE_CONNECTED)
1239  {
1240  //Format QUIT command
1241  error = smtpClientFormatCommand(context, "QUIT", NULL);
1242 
1243  //Check status code
1244  if(!error)
1245  {
1246  //Send QUIT command and wait for the server's response
1248  }
1249  }
1250  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
1251  {
1252  //Send QUIT command and wait for the server's response
1253  error = smtpClientSendCommand(context, NULL);
1254 
1255  //Check status code
1256  if(!error)
1257  {
1258  //Update SMTP client state
1260  }
1261  }
1262  else if(context->state == SMTP_CLIENT_STATE_DISCONNECTING)
1263  {
1264  //Shutdown connection
1265  error = smtpClientShutdownConnection(context);
1266 
1267  //Check status code
1268  if(!error)
1269  {
1270  //Close connection
1271  smtpClientCloseConnection(context);
1272  //Update SMTP client state
1274  }
1275  }
1276  else if(context->state == SMTP_CLIENT_STATE_DISCONNECTED)
1277  {
1278  //We are done
1279  break;
1280  }
1281  else
1282  {
1283  //Invalid state
1284  error = ERROR_WRONG_STATE;
1285  }
1286  }
1287 
1288  //Check status code
1289  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1290  {
1291  //Check whether the timeout has elapsed
1292  error = smtpClientCheckTimeout(context);
1293  }
1294 
1295  //Failed to gracefully disconnect from the SMTP server?
1296  if(error != NO_ERROR && error != ERROR_WOULD_BLOCK)
1297  {
1298  //Close connection
1299  smtpClientCloseConnection(context);
1300  //Update SMTP client state
1302  }
1303 
1304  //Return status code
1305  return error;
1306 }
1307 
1308 
1309 /**
1310  * @brief Close the connection with the SMTP server
1311  * @param[in] context Pointer to the SMTP client context
1312  * @return Error code
1313  **/
1314 
1316 {
1317  //Make sure the SMTP client context is valid
1318  if(context == NULL)
1319  return ERROR_INVALID_PARAMETER;
1320 
1321  //Close connection
1322  smtpClientCloseConnection(context);
1323  //Update SMTP client state
1325 
1326  //Successful processing
1327  return NO_ERROR;
1328 }
1329 
1330 
1331 /**
1332  * @brief Release SMTP client context
1333  * @param[in] context Pointer to the SMTP client context
1334  **/
1335 
1337 {
1338  //Make sure the SMTP client context is valid
1339  if(context != NULL)
1340  {
1341  //Close connection
1342  smtpClientCloseConnection(context);
1343 
1344 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
1345  //Release TLS session state
1346  tlsFreeSessionState(&context->tlsSession);
1347 #endif
1348 
1349  //Clear SMTP client context
1350  osMemset(context, 0, sizeof(SmtpClientContext));
1351  }
1352 }
1353 
1354 #endif
@ SMTP_CLIENT_STATE_MAIL_BODY
Definition: smtp_client.h:208
error_t smtpClientSetTimeout(SmtpClientContext *context, systime_t timeout)
Set communication timeout.
Definition: smtp_client.c:129
String manipulation helper functions.
int bool_t
Definition: compiler_port.h:63
#define SMTP_CLIENT_BUFFER_SIZE
Definition: smtp_client.h:88
@ ERROR_WOULD_BLOCK
Definition: error.h:96
IP network address.
Definition: ip.h:90
@ ERROR_NOT_IMPLEMENTED
Definition: error.h:66
Transport protocol abstraction layer.
uint8_t data[]
Definition: ethernet.h:224
Email address.
Definition: smtp_client.h:241
error_t smtpClientDisconnect(SmtpClientContext *context)
Gracefully disconnect from the SMTP server.
Definition: smtp_client.c:1223
void base64Encode(const void *input, size_t inputLen, char_t *output, size_t *outputLen)
Base64 encoding algorithm.
Definition: base64.c:142
uint16_t last
Definition: ipv4_frag.h:105
error_t smtpClientConnect(SmtpClientContext *context, const IpAddr *serverIpAddr, uint16_t serverPort, SmtpConnectionMode mode)
Establish a connection with the specified SMTP server.
Definition: smtp_client.c:174
@ SMTP_CLIENT_STATE_SUB_COMMAND_1
Definition: smtp_client.h:204
@ SMTP_CLIENT_STATE_MAIL_HEADER
Definition: smtp_client.h:207
error_t smtpClientClose(SmtpClientContext *context)
Close the connection with the SMTP server.
Definition: smtp_client.c:1315
uint16_t totalLength
Definition: ipv4.h:348
#define osStrlen(s)
Definition: os_port.h:171
void tlsFreeSessionState(TlsSessionState *session)
Properly dispose a session state.
Definition: tls.c:3126
@ SMTP_CLIENT_STATE_DISCONNECTING
Definition: smtp_client.h:211
error_t smtpClientOpenSecureConnection(SmtpClientContext *context)
Open secure connection.
@ ERROR_WRONG_STATE
Definition: error.h:210
error_t smtpClientFormatMultipartHeader(SmtpClientContext *context, const char_t *filename, const char_t *contentType, const char_t *contentTransferEncoding, bool_t last)
Format multipart header.
error_t smtpClientWriteMultipartHeader(SmtpClientContext *context, const char_t *filename, const char_t *contentType, const char_t *contentTransferEncoding, bool_t last)
Write multipart header.
Definition: smtp_client.c:832
void smtpClientChangeState(SmtpClientContext *context, SmtpClientState newState)
Update SMTP client state.
#define FALSE
Definition: os_port.h:46
@ SMTP_MODE_PLAINTEXT
Definition: smtp_client.h:175
Helper functions for SMTP client.
@ SMTP_MODE_IMPLICIT_TLS
Definition: smtp_client.h:176
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
#define osMemcpy(dest, src, length)
Definition: os_port.h:147
SMTP client (Simple Mail Transfer Protocol)
error_t smtpClientRegisterTlsInitCallback(SmtpClientContext *context, SmtpClientTlsInitCallback callback)
Register TLS initialization callback function.
Definition: smtp_client.c:105
#define SmtpClientContext
Definition: smtp_client.h:161
#define SMTP_CLIENT_CONTENT_TYPE_MAX_LEN
Definition: smtp_client.h:109
error_t
Error codes.
Definition: error.h:43
error_t smtpClientWriteMailBody(SmtpClientContext *context, const void *data, size_t length, size_t *written, uint_t flags)
Write email body.
Definition: smtp_client.c:760
error_t smtpClientParseEhloReply(SmtpClientContext *context, char_t *replyLine)
Parse EHLO response.
error_t smtpClientSetContentType(SmtpClientContext *context, const char_t *contentType)
Set the content type to be used.
Definition: smtp_client.c:517
error_t smtpClientLoginAuth(SmtpClientContext *context, const char_t *username, const char_t *password)
Perform LOGIN authentication.
error_t smtpClientSetMultipartBoundary(SmtpClientContext *context, const char_t *boundary)
Define the boundary string to be used (multipart encoding)
Definition: smtp_client.c:553
error_t(* SmtpClientTlsInitCallback)(SmtpClientContext *context, TlsContext *tlsContext)
TLS initialization callback function.
Definition: smtp_client.h:230
#define NetInterface
Definition: net.h:40
#define SMTP_CLIENT_BOUNDARY_MAX_LEN
Definition: smtp_client.h:116
error_t smtpClientShutdownConnection(SmtpClientContext *context)
Shutdown network connection.
@ ERROR_INVALID_LENGTH
Definition: error.h:111
void smtpClientCloseConnection(SmtpClientContext *context)
Close network connection.
NetContext * netGetDefaultContext(void)
Get default TCP/IP stack context.
Definition: net.c:527
char_t filename[]
Definition: tftp_common.h:93
@ SMTP_CLIENT_STATE_SUB_COMMAND_2
Definition: smtp_client.h:205
@ SMTP_CLIENT_STATE_CONNECTED
Definition: smtp_client.h:203
@ ERROR_UNEXPECTED_RESPONSE
Definition: error.h:70
uint8_t length
Definition: tcp.h:375
#define SMTP_REPLY_CODE_2YZ(code)
Definition: smtp_client.h:154
#define MIN(a, b)
Definition: os_port.h:63
#define SMTP_REPLY_CODE_3YZ(code)
Definition: smtp_client.h:155
void smtpClientDeinit(SmtpClientContext *context)
Release SMTP client context.
Definition: smtp_client.c:1336
error_t smtpClientCloseMailBody(SmtpClientContext *context)
Complete email sending process and wait for server's status.
Definition: smtp_client.c:1118
error_t smtpClientEstablishConnection(SmtpClientContext *context, const IpAddr *serverIpAddr, uint16_t serverPort)
Establish network connection.
uint_t smtpClientGetReplyCode(SmtpClientContext *context)
Retrieve server's reply code.
Definition: smtp_client.c:1196
uint32_t systime_t
System time.
error_t smtpClientInit(SmtpClientContext *context)
Initialize SMTP client context.
Definition: smtp_client.c:61
error_t smtpClientBindToInterface(SmtpClientContext *context, NetInterface *interface)
Bind the SMTP client to a particular network interface.
Definition: smtp_client.c:150
@ SMTP_CLIENT_STATE_DISCONNECTED
Definition: smtp_client.h:200
@ ERROR_TIMEOUT
Definition: error.h:95
char char_t
Definition: compiler_port.h:55
error_t smtpClientWriteMultipartBody(SmtpClientContext *context, const void *data, size_t length, size_t *written, uint_t flags)
Write data to the multipart body.
Definition: smtp_client.c:969
error_t smtpClientOpenConnection(SmtpClientContext *context)
Open network connection.
@ SMTP_CLIENT_STATE_SUB_COMMAND_3
Definition: smtp_client.h:206
error_t smtpClientPlainAuth(SmtpClientContext *context, const char_t *username, const char_t *password)
Perform PLAIN authentication.
error_t smtpClientWriteMailHeader(SmtpClientContext *context, const SmtpMailAddr *from, const SmtpMailAddr *recipients, uint_t numRecipients, const char_t *subject)
Write email header.
Definition: smtp_client.c:592
error_t smtpClientLogin(SmtpClientContext *context, const char_t *username, const char_t *password)
Login to the SMTP server using the provided user name and password.
Definition: smtp_client.c:464
SmtpConnectionMode
SMTP connection modes.
Definition: smtp_client.h:174
uint8_t n
@ ERROR_AUTHENTICATION_FAILED
Definition: error.h:69
error_t smtpClientEstablishSecureConnection(SmtpClientContext *context)
Establish secure connection.
error_t smtpClientFormatMailHeader(SmtpClientContext *context, const SmtpMailAddr *from, const SmtpMailAddr *recipients, uint_t numRecipients, const char_t *subject)
Format email header.
error_t smtpClientSaveSession(SmtpClientContext *context)
Save TLS session.
error_t smtpClientFormatCommand(SmtpClientContext *context, const char_t *command, const char_t *argument)
Format SMTP command.
@ SMTP_CLIENT_STATE_CONNECTING_TLS
Definition: smtp_client.h:202
char_t * addr
Definition: smtp_client.h:243
SMTP authentication mechanism.
uint8_t flags
Definition: tcp.h:358
unsigned int uint_t
Definition: compiler_port.h:57
error_t smtpClientCramMd5Auth(SmtpClientContext *context, const char_t *username, const char_t *password)
Perform CRAM-MD5 authentication.
@ SMTP_MODE_EXPLICIT_TLS
Definition: smtp_client.h:177
#define osMemset(p, value, length)
Definition: os_port.h:141
TCP/IP stack core.
error_t smtpClientSendData(SmtpClientContext *context, const void *data, size_t length, size_t *written, uint_t flags)
Send data using the relevant transport protocol.
error_t tlsInitSessionState(TlsSessionState *session)
Initialize session state.
Definition: tls.c:2983
#define osStrcpy(s1, s2)
Definition: os_port.h:213
#define SMTP_CLIENT_DEFAULT_TIMEOUT
Definition: smtp_client.h:81
error_t smtpClientSendCommand(SmtpClientContext *context, SmtpClientReplyCallback callback)
Send SMTP command and wait for a reply.
error_t smtpClientCheckTimeout(SmtpClientContext *context)
Determine whether a timeout error has occurred.
@ SMTP_CLIENT_STATE_MULTIPART_BODY
Definition: smtp_client.h:210
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
@ SMTP_CLIENT_STATE_CONNECTING_TCP
Definition: smtp_client.h:201
@ SMTP_CLIENT_STATE_MULTIPART_HEADER
Definition: smtp_client.h:209
systime_t osGetSystemTime(void)
Retrieve system time.