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-2024 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.4.0
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  *written = n;
777 
778  //Return status code
779  return error;
780 }
781 
782 
783 /**
784  * @brief Write multipart header
785  * @param[in] context Pointer to the SMTP client context
786  * @param[in] filename NULL-terminated string that holds the file name
787  * (optional parameter)
788  * @param[in] contentType NULL-terminated string that holds the content type
789  * (optional parameter)
790  * @param[in] contentTransferEncoding NULL-terminated string that holds the
791  * content transfer encoding (optional parameter)
792  * @param[in] last This flag indicates whether the multipart header is the
793  * final one
794  * @return Error code
795  **/
796 
798  const char_t *filename, const char_t *contentType,
799  const char_t *contentTransferEncoding, bool_t last)
800 {
801 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
802  error_t error;
803  size_t n;
804 
805  //Make sure the SMTP client context is valid
806  if(context == NULL)
808 
809  //Initialize status code
810  error = NO_ERROR;
811 
812  //Format and send multipart header
813  while(!error)
814  {
815  //Check current state
816  if(context->state == SMTP_CLIENT_STATE_MAIL_BODY ||
817  context->state == SMTP_CLIENT_STATE_MULTIPART_BODY)
818  {
819  //Any data residue?
820  if(context->bufferLen > 0 && context->bufferLen < 4)
821  {
822  //Encode the final quantum
823  base64Encode(context->buffer, context->bufferLen,
824  context->buffer, &n);
825 
826  //Save the length of the Base64-encoded string
827  context->bufferLen = n;
828  context->bufferPos = 0;
829  }
830  else if(context->bufferPos < context->bufferLen)
831  {
832  //Send more data
833  error = smtpClientSendData(context,
834  context->buffer + context->bufferPos,
835  context->bufferLen - context->bufferPos, &n, 0);
836 
837  //Check status code
838  if(error == NO_ERROR || error == ERROR_TIMEOUT)
839  {
840  //Advance data pointer
841  context->bufferPos += n;
842  }
843  }
844  else
845  {
846  //Rewind to the beginning of the buffer
847  context->bufferPos = 0;
848  context->bufferLen = 0;
849 
850  //Format multipart header
851  error = smtpClientFormatMultipartHeader(context, filename,
852  contentType, contentTransferEncoding, last);
853 
854  //Check status code
855  if(!error)
856  {
857  //Send multipart header
859  }
860  }
861  }
862  else if(context->state == SMTP_CLIENT_STATE_MULTIPART_HEADER)
863  {
864  //Send multipart header
865  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  //Last multipart header?
886  if(last)
887  {
888  //The last multipart header has been successfully transmitted
890  }
891  else
892  {
893  //Send multipart body
895  }
896 
897  //The email header has been successfully written
898  break;
899  }
900  }
901  else
902  {
903  //Invalid state
904  error = ERROR_WRONG_STATE;
905  }
906  }
907 
908  //Check status code
909  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
910  {
911  //Check whether the timeout has elapsed
912  error = smtpClientCheckTimeout(context);
913  }
914 
915  //Return status code
916  return error;
917 #else
918  //MIME extension is not implemented
919  return ERROR_NOT_IMPLEMENTED;
920 #endif
921 }
922 
923 
924 /**
925  * @brief Write data to the multipart body
926  * @param[in] context Pointer to the SMTP client context
927  * @param[in] data Pointer to the buffer containing the data to be transmitted
928  * @param[in] length Number of data bytes to send
929  * @param[out] written Actual number of bytes written (optional parameter)
930  * @param[in] flags Set of flags that influences the behavior of this function
931  * @return Error code
932  **/
933 
935  const void *data, size_t length, size_t *written, uint_t flags)
936 {
937 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
938  error_t error;
939  size_t n;
940  size_t totalLength;
941 
942  //Make sure the SMTP client context is valid
943  if(context == NULL)
945 
946  //Check parameters
947  if(data == NULL && length != 0)
949 
950  //Initialize status code
951  error = NO_ERROR;
952 
953  //Actual number of bytes written
954  totalLength = 0;
955 
956  //Check current state
957  if(context->state == SMTP_CLIENT_STATE_MULTIPART_BODY)
958  {
959  //Base64 encoding?
960  if(context->base64Encoding)
961  {
962  //Send as much data as possible
963  while(totalLength < length && !error)
964  {
965  //Any data pending in the transmit buffer?
966  if(context->bufferLen < 4)
967  {
968  //Base64 maps a 3-byte block to 4 printable characters
969  n = (SMTP_CLIENT_BUFFER_SIZE * 3) / 4;
970 
971  //Calculate the number of bytes to copy at a time
972  n = MIN(n - context->bufferLen, length - totalLength);
973 
974  //The raw data must be an integral multiple of 24 bits
975  if((context->bufferLen + n) > 3)
976  {
977  n -= (context->bufferLen + n) % 3;
978  }
979 
980  //Copy the raw data to the transmit buffer
981  osMemcpy(context->buffer + context->bufferLen, data, n);
982 
983  //Advance data pointer
984  data = (uint8_t *) data + n;
985  //Update the length of the buffer
986  context->bufferLen += n;
987  //Actual number of bytes written
988  totalLength += n;
989 
990  //The raw data is processed block by block
991  if(context->bufferLen >= 3)
992  {
993  //Encode the data with Base64 algorithm
994  base64Encode(context->buffer, context->bufferLen,
995  context->buffer, &n);
996 
997  //Save the length of the Base64-encoded string
998  context->bufferLen = n;
999  context->bufferPos = 0;
1000  }
1001  }
1002  else if(context->bufferPos < context->bufferLen)
1003  {
1004  //Send more data
1005  error = smtpClientSendData(context,
1006  context->buffer + context->bufferPos,
1007  context->bufferLen - context->bufferPos, &n, 0);
1008 
1009  //Check status code
1010  if(error == NO_ERROR || error == ERROR_TIMEOUT)
1011  {
1012  //Any data transmitted?
1013  if(n > 0)
1014  {
1015  //Advance data pointer
1016  context->bufferPos += n;
1017  //Save current time
1018  context->timestamp = osGetSystemTime();
1019  }
1020  }
1021  }
1022  else
1023  {
1024  //Rewind to the beginning of the buffer
1025  context->bufferPos = 0;
1026  context->bufferLen = 0;
1027  }
1028  }
1029  }
1030  else
1031  {
1032  //Send raw data
1033  error = smtpClientSendData(context, data, length, &n, flags);
1034 
1035  //Check status code
1036  if(error == NO_ERROR || error == ERROR_TIMEOUT)
1037  {
1038  //Any data transmitted?
1039  if(n > 0)
1040  {
1041  //Actual number of bytes written
1042  totalLength += n;
1043  //Save current time
1044  context->timestamp = osGetSystemTime();
1045  }
1046  }
1047  }
1048  }
1049  else
1050  {
1051  //Invalid state
1052  error = ERROR_WRONG_STATE;
1053  }
1054 
1055  //Check status code
1056  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1057  {
1058  //Check whether the timeout has elapsed
1059  error = smtpClientCheckTimeout(context);
1060  }
1061 
1062  //Total number of data that have been written
1063  if(written != NULL)
1064  *written = totalLength;
1065 
1066  //Return status code
1067  return error;
1068 #else
1069  //MIME extension is not implemented
1070  return ERROR_NOT_IMPLEMENTED;
1071 #endif
1072 }
1073 
1074 
1075 /**
1076  * @brief Complete email sending process and wait for server's status
1077  * @param[in] context Pointer to the SMTP client context
1078  * @return Error code
1079  **/
1080 
1082 {
1083  error_t error;
1084 
1085  //Make sure the SMTP client context is valid
1086  if(context == NULL)
1087  return ERROR_INVALID_PARAMETER;
1088 
1089  //Initialize status code
1090  error = NO_ERROR;
1091 
1092  //Execute SMTP command sequence
1093  while(!error)
1094  {
1095  //Check current state
1096  if(context->state == SMTP_CLIENT_STATE_MAIL_BODY)
1097  {
1098  //SMTP indicates the end of the mail data by sending a line containing
1099  //only a "." (refer to RFC 5321, section 3.3)
1100  error = smtpClientFormatCommand(context, ".", NULL);
1101 
1102  //Check status code
1103  if(!error)
1104  {
1105  //Wait for the server's response
1107  }
1108  }
1109  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
1110  {
1111  //Wait for the server's response
1112  error = smtpClientSendCommand(context, NULL);
1113 
1114  //Check status code
1115  if(!error)
1116  {
1117  //Check SMTP response code
1118  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
1119  {
1120  //Update SMTP client state
1122  //The email has been accepted by the server
1123  break;
1124  }
1125  else
1126  {
1127  //Update SMTP client state
1129  //Report an error
1130  error = ERROR_UNEXPECTED_RESPONSE;
1131  }
1132  }
1133  }
1134  else
1135  {
1136  //Invalid state
1137  error = ERROR_WRONG_STATE;
1138  }
1139  }
1140 
1141  //Check status code
1142  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1143  {
1144  //Check whether the timeout has elapsed
1145  error = smtpClientCheckTimeout(context);
1146  }
1147 
1148  //Return status code
1149  return error;
1150 }
1151 
1152 
1153 /**
1154  * @brief Retrieve server's reply code
1155  * @param[in] context Pointer to the SMTP client context
1156  * @return SMTP reply code
1157  **/
1158 
1160 {
1161  uint_t replyCode;
1162 
1163  //Make sure the SMTP client context is valid
1164  if(context != NULL)
1165  {
1166  //Get server's reply code
1167  replyCode = context->replyCode;
1168  }
1169  else
1170  {
1171  //The SMTP client context is not valid
1172  replyCode = 0;
1173  }
1174 
1175  //Return SMTP reply code
1176  return replyCode;
1177 }
1178 
1179 
1180 /**
1181  * @brief Gracefully disconnect from the SMTP server
1182  * @param[in] context Pointer to the SMTP client context
1183  * @return Error code
1184  **/
1185 
1187 {
1188  error_t error;
1189 
1190  //Make sure the SMTP client context is valid
1191  if(context == NULL)
1192  return ERROR_INVALID_PARAMETER;
1193 
1194  //Initialize status code
1195  error = NO_ERROR;
1196 
1197  //Execute SMTP command sequence
1198  while(!error)
1199  {
1200  //Check current state
1201  if(context->state == SMTP_CLIENT_STATE_CONNECTED)
1202  {
1203  //Format QUIT command
1204  error = smtpClientFormatCommand(context, "QUIT", NULL);
1205 
1206  //Check status code
1207  if(!error)
1208  {
1209  //Send QUIT command and wait for the server's response
1211  }
1212  }
1213  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
1214  {
1215  //Send QUIT command and wait for the server's response
1216  error = smtpClientSendCommand(context, NULL);
1217 
1218  //Check status code
1219  if(!error)
1220  {
1221  //Update SMTP client state
1223  }
1224  }
1225  else if(context->state == SMTP_CLIENT_STATE_DISCONNECTING)
1226  {
1227  //Shutdown connection
1228  error = smtpClientShutdownConnection(context);
1229 
1230  //Check status code
1231  if(!error)
1232  {
1233  //Close connection
1234  smtpClientCloseConnection(context);
1235  //Update SMTP client state
1237  }
1238  }
1239  else if(context->state == SMTP_CLIENT_STATE_DISCONNECTED)
1240  {
1241  //We are done
1242  break;
1243  }
1244  else
1245  {
1246  //Invalid state
1247  error = ERROR_WRONG_STATE;
1248  }
1249  }
1250 
1251  //Check status code
1252  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1253  {
1254  //Check whether the timeout has elapsed
1255  error = smtpClientCheckTimeout(context);
1256  }
1257 
1258  //Failed to gracefully disconnect from the SMTP server?
1259  if(error != NO_ERROR && error != ERROR_WOULD_BLOCK)
1260  {
1261  //Close connection
1262  smtpClientCloseConnection(context);
1263  //Update SMTP client state
1265  }
1266 
1267  //Return status code
1268  return error;
1269 }
1270 
1271 
1272 /**
1273  * @brief Close the connection with the SMTP server
1274  * @param[in] context Pointer to the SMTP client context
1275  * @return Error code
1276  **/
1277 
1279 {
1280  //Make sure the SMTP client context is valid
1281  if(context == NULL)
1282  return ERROR_INVALID_PARAMETER;
1283 
1284  //Close connection
1285  smtpClientCloseConnection(context);
1286  //Update SMTP client state
1288 
1289  //Successful processing
1290  return NO_ERROR;
1291 }
1292 
1293 
1294 /**
1295  * @brief Release SMTP client context
1296  * @param[in] context Pointer to the SMTP client context
1297  **/
1298 
1300 {
1301  //Make sure the SMTP client context is valid
1302  if(context != NULL)
1303  {
1304  //Close connection
1305  smtpClientCloseConnection(context);
1306 
1307 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
1308  //Release TLS session state
1309  tlsFreeSessionState(&context->tlsSession);
1310 #endif
1311 
1312  //Clear SMTP client context
1313  osMemset(context, 0, sizeof(SmtpClientContext));
1314  }
1315 }
1316 
1317 #endif
void base64Encode(const void *input, size_t inputLen, char_t *output, size_t *outputLen)
Base64 encoding algorithm.
Definition: base64.c:142
unsigned int uint_t
Definition: compiler_port.h:50
char char_t
Definition: compiler_port.h:48
int bool_t
Definition: compiler_port.h:53
Debugging facilities.
uint8_t n
error_t
Error codes.
Definition: error.h:43
@ ERROR_WOULD_BLOCK
Definition: error.h:96
@ ERROR_TIMEOUT
Definition: error.h:95
@ ERROR_AUTHENTICATION_FAILED
Definition: error.h:69
@ ERROR_UNEXPECTED_RESPONSE
Definition: error.h:70
@ ERROR_NOT_IMPLEMENTED
Definition: error.h:66
@ NO_ERROR
Success.
Definition: error.h:44
@ ERROR_WRONG_STATE
Definition: error.h:209
@ ERROR_INVALID_LENGTH
Definition: error.h:111
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
uint8_t data[]
Definition: ethernet.h:222
uint16_t totalLength
Definition: ipv4.h:292
uint16_t last
Definition: ipv4_frag.h:105
TCP/IP stack core.
#define NetInterface
Definition: net.h:36
#define osMemset(p, value, length)
Definition: os_port.h:135
#define osMemcpy(dest, src, length)
Definition: os_port.h:141
#define MIN(a, b)
Definition: os_port.h:63
#define osStrlen(s)
Definition: os_port.h:165
#define FALSE
Definition: os_port.h:46
#define osStrcpy(s1, s2)
Definition: os_port.h:207
systime_t osGetSystemTime(void)
Retrieve system time.
uint32_t systime_t
System time.
error_t smtpClientCloseMailBody(SmtpClientContext *context)
Complete email sending process and wait for server's status.
Definition: smtp_client.c:1081
uint_t smtpClientGetReplyCode(SmtpClientContext *context)
Retrieve server's reply code.
Definition: smtp_client.c:1159
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
error_t smtpClientClose(SmtpClientContext *context)
Close the connection with the SMTP server.
Definition: smtp_client.c:1278
error_t smtpClientSetContentType(SmtpClientContext *context, const char_t *contentType)
Set the content type to be used.
Definition: smtp_client.c:484
error_t smtpClientRegisterTlsInitCallback(SmtpClientContext *context, SmtpClientTlsInitCallback callback)
Register TLS initialization callback function.
Definition: smtp_client.c:102
error_t smtpClientDisconnect(SmtpClientContext *context)
Gracefully disconnect from the SMTP server.
Definition: smtp_client.c:1186
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 smtpClientConnect(SmtpClientContext *context, const IpAddr *serverIpAddr, uint16_t serverPort, SmtpConnectionMode mode)
Establish a connection with the specified SMTP server.
Definition: smtp_client.c:171
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 smtpClientSetTimeout(SmtpClientContext *context, systime_t timeout)
Set communication timeout.
Definition: smtp_client.c:126
error_t smtpClientInit(SmtpClientContext *context)
Initialize SMTP client context.
Definition: smtp_client.c:61
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:797
void smtpClientDeinit(SmtpClientContext *context)
Release SMTP client context.
Definition: smtp_client.c:1299
error_t smtpClientBindToInterface(SmtpClientContext *context, NetInterface *interface)
Bind the SMTP client to a particular network interface.
Definition: smtp_client.c:147
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:934
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
SMTP client (Simple Mail Transfer Protocol)
@ SMTP_CLIENT_STATE_MAIL_HEADER
Definition: smtp_client.h:207
@ SMTP_CLIENT_STATE_CONNECTING_TLS
Definition: smtp_client.h:202
@ SMTP_CLIENT_STATE_MULTIPART_BODY
Definition: smtp_client.h:210
@ SMTP_CLIENT_STATE_SUB_COMMAND_1
Definition: smtp_client.h:204
@ SMTP_CLIENT_STATE_CONNECTING_TCP
Definition: smtp_client.h:201
@ SMTP_CLIENT_STATE_CONNECTED
Definition: smtp_client.h:203
@ SMTP_CLIENT_STATE_MAIL_BODY
Definition: smtp_client.h:208
@ SMTP_CLIENT_STATE_DISCONNECTED
Definition: smtp_client.h:200
@ SMTP_CLIENT_STATE_DISCONNECTING
Definition: smtp_client.h:211
@ SMTP_CLIENT_STATE_SUB_COMMAND_3
Definition: smtp_client.h:206
@ SMTP_CLIENT_STATE_MULTIPART_HEADER
Definition: smtp_client.h:209
@ SMTP_CLIENT_STATE_SUB_COMMAND_2
Definition: smtp_client.h:205
#define SMTP_CLIENT_DEFAULT_TIMEOUT
Definition: smtp_client.h:81
#define SMTP_REPLY_CODE_3YZ(code)
Definition: smtp_client.h:155
#define SmtpClientContext
Definition: smtp_client.h:161
#define SMTP_CLIENT_BUFFER_SIZE
Definition: smtp_client.h:88
#define SMTP_CLIENT_CONTENT_TYPE_MAX_LEN
Definition: smtp_client.h:109
SmtpConnectionMode
SMTP connection modes.
Definition: smtp_client.h:174
@ SMTP_MODE_IMPLICIT_TLS
Definition: smtp_client.h:176
@ SMTP_MODE_PLAINTEXT
Definition: smtp_client.h:175
@ SMTP_MODE_EXPLICIT_TLS
Definition: smtp_client.h:177
#define SMTP_REPLY_CODE_2YZ(code)
Definition: smtp_client.h:154
error_t(* SmtpClientTlsInitCallback)(SmtpClientContext *context, TlsContext *tlsContext)
TLS initialization callback function.
Definition: smtp_client.h:230
#define SMTP_CLIENT_BOUNDARY_MAX_LEN
Definition: smtp_client.h:116
error_t smtpClientCramMd5Auth(SmtpClientContext *context, const char_t *username, const char_t *password)
Perform CRAM-MD5 authentication.
error_t smtpClientLoginAuth(SmtpClientContext *context, const char_t *username, const char_t *password)
Perform LOGIN authentication.
error_t smtpClientPlainAuth(SmtpClientContext *context, const char_t *username, const char_t *password)
Perform PLAIN authentication.
SMTP authentication mechanism.
void smtpClientChangeState(SmtpClientContext *context, SmtpClientState newState)
Update SMTP client state.
error_t smtpClientFormatMailHeader(SmtpClientContext *context, const SmtpMailAddr *from, const SmtpMailAddr *recipients, uint_t numRecipients, const char_t *subject)
Format email header.
error_t smtpClientParseEhloReply(SmtpClientContext *context, char_t *replyLine)
Parse EHLO response.
error_t smtpClientSendCommand(SmtpClientContext *context, SmtpClientReplyCallback callback)
Send SMTP command and wait for a reply.
error_t smtpClientFormatCommand(SmtpClientContext *context, const char_t *command, const char_t *argument)
Format SMTP command.
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 smtpClientCheckTimeout(SmtpClientContext *context)
Determine whether a timeout error has occurred.
Helper functions for SMTP client.
error_t smtpClientOpenConnection(SmtpClientContext *context)
Open network connection.
void smtpClientCloseConnection(SmtpClientContext *context)
Close network connection.
error_t smtpClientOpenSecureConnection(SmtpClientContext *context)
Open secure connection.
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 smtpClientEstablishSecureConnection(SmtpClientContext *context)
Establish secure connection.
error_t smtpClientEstablishConnection(SmtpClientContext *context, const IpAddr *serverIpAddr, uint16_t serverPort)
Establish network connection.
error_t smtpClientShutdownConnection(SmtpClientContext *context)
Shutdown network connection.
Transport protocol abstraction layer.
String manipulation helper functions.
IP network address.
Definition: ip.h:79
Email address.
Definition: smtp_client.h:241
char_t * addr
Definition: smtp_client.h:243
uint8_t length
Definition: tcp.h:368
uint8_t flags
Definition: tcp.h:351
char_t filename[]
Definition: tftp_common.h:93
void tlsFreeSessionState(TlsSessionState *session)
Properly dispose a session state.
Definition: tls.c:2743
error_t tlsInitSessionState(TlsSessionState *session)
Initialize session state.
Definition: tls.c:2600