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