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.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  //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  //Format EHLO command
287  error = smtpClientFormatCommand(context, "EHLO [127.0.0.1]", NULL);
288 
289  //Check status code
290  if(!error)
291  {
292  //Send EHLO command and wait for the server's response
294  }
295  }
296  }
297  }
298  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
299  {
300  //Wait for the connection greeting reply
301  error = smtpClientSendCommand(context, NULL);
302 
303  //Check status code
304  if(!error)
305  {
306  //Check SMTP response code
307  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
308  {
309  //Format EHLO command
310  error = smtpClientFormatCommand(context, "EHLO [127.0.0.1]", NULL);
311 
312  //Check status code
313  if(!error)
314  {
315  //Send EHLO command and wait for the server's response
317  }
318  }
319  else
320  {
321  //Report an error
323  }
324  }
325  }
326  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_2)
327  {
328  //Send EHLO command and wait for the server's response
330 
331  //Check status code
332  if(!error)
333  {
334  //Check SMTP response code
335  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
336  {
337 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
338  //Explicit TLS?
339  if(mode == SMTP_MODE_EXPLICIT_TLS && context->tlsContext == NULL)
340  {
341  //Format STARTTLS command
342  error = smtpClientFormatCommand(context, "STARTTLS", NULL);
343 
344  //Check status code
345  if(!error)
346  {
347  //Send STARTTLS command and wait for the server's response
349  }
350  }
351  else
352 #endif
353  {
354  //The SMTP client is connected
356  }
357  }
358  else
359  {
360  //Report an error
362  }
363  }
364  }
365  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_3)
366  {
367  //Send STARTTLS command and wait for the server's response
368  error = smtpClientSendCommand(context, NULL);
369 
370  //Check status code
371  if(!error)
372  {
373  //Check SMTP response code
374  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
375  {
376  //TLS initialization
377  error = smtpClientOpenSecureConnection(context);
378 
379  //Check status code
380  if(!error)
381  {
382  //Perform TLS handshake
384  }
385  }
386  else
387  {
388  //Report an error
390  }
391  }
392  }
393  else if(context->state == SMTP_CLIENT_STATE_CONNECTED)
394  {
395  //The SMTP client is connected
396  break;
397  }
398  else
399  {
400  //Invalid state
401  error = ERROR_WRONG_STATE;
402  }
403  }
404 
405  //Check status code
406  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
407  {
408  //Check whether the timeout has elapsed
409  error = smtpClientCheckTimeout(context);
410  }
411 
412  //Failed to establish connection with the SMTP server?
413  if(error != NO_ERROR && error != ERROR_WOULD_BLOCK)
414  {
415  //Clean up side effects
416  smtpClientCloseConnection(context);
417  //Update SMTP client state
419  }
420 
421  //Return status code
422  return error;
423 }
424 
425 
426 /**
427  * @brief Login to the SMTP server using the provided user name and password
428  * @param[in] context Pointer to the SMTP client context
429  * @param[in] username NULL-terminated string containing the user name
430  * @param[in] password NULL-terminated string containing the user's password
431  * @return Error code
432  **/
433 
435  const char_t *password)
436 {
437  error_t error;
438 
439  //Check parameters
440  if(context == NULL || username == NULL || password == NULL)
442 
443 #if (SMTP_CLIENT_CRAM_MD5_AUTH_SUPPORT == ENABLED)
444  //CRAM-MD5 authentication mechanism supported?
445  if(context->authCramMd5Supported)
446  {
447  //Perform CRAM-MD5 authentication
448  error = smtpClientCramMd5Auth(context, username, password);
449  }
450  else
451 #endif
452 #if (SMTP_CLIENT_LOGIN_AUTH_SUPPORT == ENABLED)
453  //LOGIN authentication mechanism supported?
454  if(context->authLoginSupported)
455  {
456  //Perform LOGIN authentication
457  error = smtpClientLoginAuth(context, username, password);
458  }
459  else
460 #endif
461 #if (SMTP_CLIENT_PLAIN_AUTH_SUPPORT == ENABLED)
462  //PLAIN authentication mechanism supported?
463  if(context->authPlainSupported)
464  {
465  //Perform PLAIN authentication
466  error = smtpClientPlainAuth(context, username, password);
467  }
468  else
469 #endif
470  {
471  //Report an error
473  }
474 
475  //Return status code
476  return error;
477 }
478 
479 
480 /**
481  * @brief Set the content type to be used
482  * @param[in] context Pointer to the SMTP client context
483  * @param[in] contentType NULL-terminated string that holds the content type
484  * @return Error code
485  **/
486 
488  const char_t *contentType)
489 {
490 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
491  size_t n;
492 
493  //Check parameters
494  if(context == NULL || contentType == NULL)
496 
497  //Retrieve the length of the boundary string
498  n = osStrlen(contentType);
499 
500  //Check the length of the string
501  if(n < 1 || n > SMTP_CLIENT_CONTENT_TYPE_MAX_LEN)
502  return ERROR_INVALID_LENGTH;
503 
504  //Save content type
505  osStrcpy(context->contentType, contentType);
506 
507  //Successful processing
508  return NO_ERROR;
509 #else
510  //MIME extension is not implemented
511  return ERROR_NOT_IMPLEMENTED;
512 #endif
513 }
514 
515 
516 /**
517  * @brief Define the boundary string to be used (multipart encoding)
518  * @param[in] context Pointer to the SMTP client context
519  * @param[in] boundary NULL-terminated string that holds the boundary string
520  * @return Error code
521  **/
522 
524  const char_t *boundary)
525 {
526 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
527  size_t n;
528 
529  //Check parameters
530  if(context == NULL || boundary == NULL)
532 
533  //Retrieve the length of the boundary string
534  n = osStrlen(boundary);
535 
536  //The boundary parameter consists of 1 to 70 characters
537  if(n < 1 || n > SMTP_CLIENT_BOUNDARY_MAX_LEN)
538  return ERROR_INVALID_LENGTH;
539 
540  //Save boundary string
541  osStrcpy(context->boundary, boundary);
542 
543  //Successful processing
544  return NO_ERROR;
545 #else
546  //MIME extension is not implemented
547  return ERROR_NOT_IMPLEMENTED;
548 #endif
549 }
550 
551 
552 /**
553  * @brief Write email header
554  * @param[in] context Pointer to the SMTP client context
555  * @param[in] from Email address of the sender
556  * @param[in] recipients Email addresses of the recipients
557  * @param[in] numRecipients Number of email addresses in the list
558  * @param[in] subject NULL-terminated string containing the email subject
559  * @return Error code
560  **/
561 
563  const SmtpMailAddr *from, const SmtpMailAddr *recipients,
564  uint_t numRecipients, const char_t *subject)
565 {
566  error_t error;
567  size_t n;
568 
569  //Check parameters
570  if(context == NULL || from == NULL || recipients == NULL || subject == NULL)
572 
573  //Initialize status code
574  error = NO_ERROR;
575 
576  //Execute SMTP command sequence
577  while(!error)
578  {
579  //Check current state
580  if(context->state == SMTP_CLIENT_STATE_CONNECTED)
581  {
582  //Format MAIL FROM command
583  error = smtpClientFormatCommand(context, "MAIL FROM", from->addr);
584 
585  //Check status code
586  if(!error)
587  {
588  //Point to the first recipient of the list
589  context->recipientIndex = 0;
590  //Send MAIL FROM command and wait for the server's response
592  }
593  }
594  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
595  {
596  //Wait for the server's response
597  error = smtpClientSendCommand(context, NULL);
598 
599  //Check status code
600  if(!error)
601  {
602  //Check SMTP response code
603  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
604  {
605  //Process each recipients of the list
606  if(context->recipientIndex < numRecipients)
607  {
608  //Format RCPT TO command
609  error = smtpClientFormatCommand(context, "RCPT TO",
610  recipients[context->recipientIndex].addr);
611 
612  //Check status code
613  if(!error)
614  {
615  //Point to the next recipient
616  context->recipientIndex++;
617  //Send RCPT TO command and wait for the server's response
619  }
620  }
621  else
622  {
623  //Format DATA command
624  error = smtpClientFormatCommand(context, "DATA", NULL);
625 
626  //Check status code
627  if(!error)
628  {
629  //Send DATA command and wait for the server's response
631  }
632  }
633  }
634  else
635  {
636  //Update SMTP client state
638  //Report an error
640  }
641  }
642  }
643  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_2)
644  {
645  //Send DATA command and wait for the server's response
646  error = smtpClientSendCommand(context, NULL);
647 
648  //Check status code
649  if(!error)
650  {
651  //Check SMTP response code
652  if(SMTP_REPLY_CODE_3YZ(context->replyCode))
653  {
654  //Format email header
655  error = smtpClientFormatMailHeader(context, from, recipients,
656  numRecipients, subject);
657 
658  //Check status code
659  if(!error)
660  {
661  //Send email header
663  }
664  }
665  else
666  {
667  //Report an error
669  }
670  }
671  }
672  else if(context->state == SMTP_CLIENT_STATE_MAIL_HEADER)
673  {
674  //Send email header
675  if(context->bufferPos < context->bufferLen)
676  {
677  //Send more data
678  error = smtpClientSendData(context,
679  context->buffer + context->bufferPos,
680  context->bufferLen - context->bufferPos, &n, 0);
681 
682  //Check status code
683  if(error == NO_ERROR || error == ERROR_TIMEOUT)
684  {
685  //Advance data pointer
686  context->bufferPos += n;
687  }
688  }
689  else
690  {
691  //Flush transmit buffer
692  context->bufferPos = 0;
693  context->bufferLen = 0;
694 
695  //Update SMTP client state
697  //The email header has been successfully written
698  break;
699  }
700  }
701  else
702  {
703  //Invalid state
704  error = ERROR_WRONG_STATE;
705  }
706  }
707 
708  //Check status code
709  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
710  {
711  //Check whether the timeout has elapsed
712  error = smtpClientCheckTimeout(context);
713  }
714 
715  //Return status code
716  return error;
717 }
718 
719 
720 /**
721  * @brief Write email body
722  * @param[in] context Pointer to the SMTP client context
723  * @param[in] data Pointer to a buffer containing the data to be written
724  * @param[in] length Number of data bytes to write
725  * @param[in] written Number of bytes that have been written (optional parameter)
726  * @param[in] flags Set of flags that influences the behavior of this function
727  * @return Error code
728  **/
729 
731  const void *data, size_t length, size_t *written, uint_t flags)
732 {
733  error_t error;
734  size_t n;
735 
736  //Make sure the SMTP client context is valid
737  if(context == NULL)
739 
740  //Check parameters
741  if(data == NULL && length != 0)
743 
744  //Actual number of bytes written
745  n = 0;
746 
747  //Check current state
748  if(context->state == SMTP_CLIENT_STATE_MAIL_BODY)
749  {
750  //Transmit the contents of the body
751  error = smtpClientSendData(context, data, length, &n, flags);
752 
753  //Check status code
754  if(error == NO_ERROR || error == ERROR_TIMEOUT)
755  {
756  //Any data transmitted?
757  if(n > 0)
758  {
759  //Save current time
760  context->timestamp = osGetSystemTime();
761  }
762  }
763  }
764  else
765  {
766  //Invalid state
767  error = ERROR_WRONG_STATE;
768  }
769 
770  //Check status code
771  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
772  {
773  //Check whether the timeout has elapsed
774  error = smtpClientCheckTimeout(context);
775  }
776 
777  //Total number of data that have been written
778  if(written != NULL)
779  {
780  *written = n;
781  }
782 
783  //Return status code
784  return error;
785 }
786 
787 
788 /**
789  * @brief Write multipart header
790  * @param[in] context Pointer to the SMTP client context
791  * @param[in] filename NULL-terminated string that holds the file name
792  * (optional parameter)
793  * @param[in] contentType NULL-terminated string that holds the content type
794  * (optional parameter)
795  * @param[in] contentTransferEncoding NULL-terminated string that holds the
796  * content transfer encoding (optional parameter)
797  * @param[in] last This flag indicates whether the multipart header is the
798  * final one
799  * @return Error code
800  **/
801 
803  const char_t *filename, const char_t *contentType,
804  const char_t *contentTransferEncoding, bool_t last)
805 {
806 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
807  error_t error;
808  size_t n;
809 
810  //Make sure the SMTP client context is valid
811  if(context == NULL)
813 
814  //Initialize status code
815  error = NO_ERROR;
816 
817  //Format and send multipart header
818  while(!error)
819  {
820  //Check current state
821  if(context->state == SMTP_CLIENT_STATE_MAIL_BODY ||
822  context->state == SMTP_CLIENT_STATE_MULTIPART_BODY)
823  {
824  //Any data residue?
825  if(context->bufferLen > 0 && context->bufferLen < 4)
826  {
827  //Encode the final quantum
828  base64Encode(context->buffer, context->bufferLen,
829  context->buffer, &n);
830 
831  //Save the length of the Base64-encoded string
832  context->bufferLen = n;
833  context->bufferPos = 0;
834  }
835  else if(context->bufferPos < context->bufferLen)
836  {
837  //Send more data
838  error = smtpClientSendData(context,
839  context->buffer + context->bufferPos,
840  context->bufferLen - context->bufferPos, &n, 0);
841 
842  //Check status code
843  if(error == NO_ERROR || error == ERROR_TIMEOUT)
844  {
845  //Advance data pointer
846  context->bufferPos += n;
847  }
848  }
849  else
850  {
851  //Rewind to the beginning of the buffer
852  context->bufferPos = 0;
853  context->bufferLen = 0;
854 
855  //Format multipart header
856  error = smtpClientFormatMultipartHeader(context, filename,
857  contentType, contentTransferEncoding, last);
858 
859  //Check status code
860  if(!error)
861  {
862  //Send multipart header
864  }
865  }
866  }
867  else if(context->state == SMTP_CLIENT_STATE_MULTIPART_HEADER)
868  {
869  //Send multipart header
870  if(context->bufferPos < context->bufferLen)
871  {
872  //Send more data
873  error = smtpClientSendData(context,
874  context->buffer + context->bufferPos,
875  context->bufferLen - context->bufferPos, &n, 0);
876 
877  //Check status code
878  if(error == NO_ERROR || error == ERROR_TIMEOUT)
879  {
880  //Advance data pointer
881  context->bufferPos += n;
882  }
883  }
884  else
885  {
886  //Rewind to the beginning of the buffer
887  context->bufferPos = 0;
888  context->bufferLen = 0;
889 
890  //Last multipart header?
891  if(last)
892  {
893  //The last multipart header has been successfully transmitted
895  }
896  else
897  {
898  //Send multipart body
900  }
901 
902  //The email header has been successfully written
903  break;
904  }
905  }
906  else
907  {
908  //Invalid state
909  error = ERROR_WRONG_STATE;
910  }
911  }
912 
913  //Check status code
914  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
915  {
916  //Check whether the timeout has elapsed
917  error = smtpClientCheckTimeout(context);
918  }
919 
920  //Return status code
921  return error;
922 #else
923  //MIME extension is not implemented
924  return ERROR_NOT_IMPLEMENTED;
925 #endif
926 }
927 
928 
929 /**
930  * @brief Write data to the multipart body
931  * @param[in] context Pointer to the SMTP client context
932  * @param[in] data Pointer to the buffer containing the data to be transmitted
933  * @param[in] length Number of data bytes to send
934  * @param[out] written Actual number of bytes written (optional parameter)
935  * @param[in] flags Set of flags that influences the behavior of this function
936  * @return Error code
937  **/
938 
940  const void *data, size_t length, size_t *written, uint_t flags)
941 {
942 #if (SMTP_CLIENT_MIME_SUPPORT == ENABLED)
943  error_t error;
944  size_t n;
945  size_t totalLength;
946 
947  //Make sure the SMTP client context is valid
948  if(context == NULL)
950 
951  //Check parameters
952  if(data == NULL && length != 0)
954 
955  //Initialize status code
956  error = NO_ERROR;
957 
958  //Actual number of bytes written
959  totalLength = 0;
960 
961  //Check current state
962  if(context->state == SMTP_CLIENT_STATE_MULTIPART_BODY)
963  {
964  //Base64 encoding?
965  if(context->base64Encoding)
966  {
967  //Send as much data as possible
968  while(totalLength < length && !error)
969  {
970  //Any data pending in the transmit buffer?
971  if(context->bufferLen < 4)
972  {
973  //Base64 maps a 3-byte block to 4 printable characters
974  n = (SMTP_CLIENT_BUFFER_SIZE * 3) / 4;
975 
976  //Calculate the number of bytes to copy at a time
977  n = MIN(n - context->bufferLen, length - totalLength);
978 
979  //The raw data must be an integral multiple of 24 bits
980  if((context->bufferLen + n) > 3)
981  {
982  n -= (context->bufferLen + n) % 3;
983  }
984 
985  //Copy the raw data to the transmit buffer
986  osMemcpy(context->buffer + context->bufferLen, data, n);
987 
988  //Advance data pointer
989  data = (uint8_t *) data + n;
990  //Update the length of the buffer
991  context->bufferLen += n;
992  //Actual number of bytes written
993  totalLength += n;
994 
995  //The raw data is processed block by block
996  if(context->bufferLen >= 3)
997  {
998  //Encode the data with Base64 algorithm
999  base64Encode(context->buffer, context->bufferLen,
1000  context->buffer, &n);
1001 
1002  //Save the length of the Base64-encoded string
1003  context->bufferLen = n;
1004  context->bufferPos = 0;
1005  }
1006  }
1007  else if(context->bufferPos < context->bufferLen)
1008  {
1009  //Send more data
1010  error = smtpClientSendData(context,
1011  context->buffer + context->bufferPos,
1012  context->bufferLen - context->bufferPos, &n, 0);
1013 
1014  //Check status code
1015  if(error == NO_ERROR || error == ERROR_TIMEOUT)
1016  {
1017  //Any data transmitted?
1018  if(n > 0)
1019  {
1020  //Advance data pointer
1021  context->bufferPos += n;
1022  //Save current time
1023  context->timestamp = osGetSystemTime();
1024  }
1025  }
1026  }
1027  else
1028  {
1029  //Rewind to the beginning of the buffer
1030  context->bufferPos = 0;
1031  context->bufferLen = 0;
1032  }
1033  }
1034  }
1035  else
1036  {
1037  //Send raw data
1038  error = smtpClientSendData(context, data, length, &n, flags);
1039 
1040  //Check status code
1041  if(error == NO_ERROR || error == ERROR_TIMEOUT)
1042  {
1043  //Any data transmitted?
1044  if(n > 0)
1045  {
1046  //Actual number of bytes written
1047  totalLength += n;
1048  //Save current time
1049  context->timestamp = osGetSystemTime();
1050  }
1051  }
1052  }
1053  }
1054  else
1055  {
1056  //Invalid state
1057  error = ERROR_WRONG_STATE;
1058  }
1059 
1060  //Check status code
1061  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1062  {
1063  //Check whether the timeout has elapsed
1064  error = smtpClientCheckTimeout(context);
1065  }
1066 
1067  //Total number of data that have been written
1068  if(written != NULL)
1069  {
1070  *written = totalLength;
1071  }
1072 
1073  //Return status code
1074  return error;
1075 #else
1076  //MIME extension is not implemented
1077  return ERROR_NOT_IMPLEMENTED;
1078 #endif
1079 }
1080 
1081 
1082 /**
1083  * @brief Complete email sending process and wait for server's status
1084  * @param[in] context Pointer to the SMTP client context
1085  * @return Error code
1086  **/
1087 
1089 {
1090  error_t error;
1091 
1092  //Make sure the SMTP client context is valid
1093  if(context == NULL)
1094  return ERROR_INVALID_PARAMETER;
1095 
1096  //Initialize status code
1097  error = NO_ERROR;
1098 
1099  //Execute SMTP command sequence
1100  while(!error)
1101  {
1102  //Check current state
1103  if(context->state == SMTP_CLIENT_STATE_MAIL_BODY)
1104  {
1105  //SMTP indicates the end of the mail data by sending a line containing
1106  //only a "." (refer to RFC 5321, section 3.3)
1107  error = smtpClientFormatCommand(context, ".", NULL);
1108 
1109  //Check status code
1110  if(!error)
1111  {
1112  //Wait for the server's response
1114  }
1115  }
1116  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
1117  {
1118  //Wait for the server's response
1119  error = smtpClientSendCommand(context, NULL);
1120 
1121  //Check status code
1122  if(!error)
1123  {
1124  //Check SMTP response code
1125  if(SMTP_REPLY_CODE_2YZ(context->replyCode))
1126  {
1127  //Update SMTP client state
1129  //The email has been accepted by the server
1130  break;
1131  }
1132  else
1133  {
1134  //Update SMTP client state
1136  //Report an error
1137  error = ERROR_UNEXPECTED_RESPONSE;
1138  }
1139  }
1140  }
1141  else
1142  {
1143  //Invalid state
1144  error = ERROR_WRONG_STATE;
1145  }
1146  }
1147 
1148  //Check status code
1149  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1150  {
1151  //Check whether the timeout has elapsed
1152  error = smtpClientCheckTimeout(context);
1153  }
1154 
1155  //Return status code
1156  return error;
1157 }
1158 
1159 
1160 /**
1161  * @brief Retrieve server's reply code
1162  * @param[in] context Pointer to the SMTP client context
1163  * @return SMTP reply code
1164  **/
1165 
1167 {
1168  uint_t replyCode;
1169 
1170  //Make sure the SMTP client context is valid
1171  if(context != NULL)
1172  {
1173  //Get server's reply code
1174  replyCode = context->replyCode;
1175  }
1176  else
1177  {
1178  //The SMTP client context is not valid
1179  replyCode = 0;
1180  }
1181 
1182  //Return SMTP reply code
1183  return replyCode;
1184 }
1185 
1186 
1187 /**
1188  * @brief Gracefully disconnect from the SMTP server
1189  * @param[in] context Pointer to the SMTP client context
1190  * @return Error code
1191  **/
1192 
1194 {
1195  error_t error;
1196 
1197  //Make sure the SMTP client context is valid
1198  if(context == NULL)
1199  return ERROR_INVALID_PARAMETER;
1200 
1201  //Initialize status code
1202  error = NO_ERROR;
1203 
1204  //Execute SMTP command sequence
1205  while(!error)
1206  {
1207  //Check current state
1208  if(context->state == SMTP_CLIENT_STATE_CONNECTED)
1209  {
1210  //Format QUIT command
1211  error = smtpClientFormatCommand(context, "QUIT", NULL);
1212 
1213  //Check status code
1214  if(!error)
1215  {
1216  //Send QUIT command and wait for the server's response
1218  }
1219  }
1220  else if(context->state == SMTP_CLIENT_STATE_SUB_COMMAND_1)
1221  {
1222  //Send QUIT command and wait for the server's response
1223  error = smtpClientSendCommand(context, NULL);
1224 
1225  //Check status code
1226  if(!error)
1227  {
1228  //Update SMTP client state
1230  }
1231  }
1232  else if(context->state == SMTP_CLIENT_STATE_DISCONNECTING)
1233  {
1234  //Shutdown connection
1235  error = smtpClientShutdownConnection(context);
1236 
1237  //Check status code
1238  if(!error)
1239  {
1240  //Close connection
1241  smtpClientCloseConnection(context);
1242  //Update SMTP client state
1244  }
1245  }
1246  else if(context->state == SMTP_CLIENT_STATE_DISCONNECTED)
1247  {
1248  //We are done
1249  break;
1250  }
1251  else
1252  {
1253  //Invalid state
1254  error = ERROR_WRONG_STATE;
1255  }
1256  }
1257 
1258  //Check status code
1259  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1260  {
1261  //Check whether the timeout has elapsed
1262  error = smtpClientCheckTimeout(context);
1263  }
1264 
1265  //Failed to gracefully disconnect from the SMTP server?
1266  if(error != NO_ERROR && error != ERROR_WOULD_BLOCK)
1267  {
1268  //Close connection
1269  smtpClientCloseConnection(context);
1270  //Update SMTP client state
1272  }
1273 
1274  //Return status code
1275  return error;
1276 }
1277 
1278 
1279 /**
1280  * @brief Close the connection with the SMTP server
1281  * @param[in] context Pointer to the SMTP client context
1282  * @return Error code
1283  **/
1284 
1286 {
1287  //Make sure the SMTP client context is valid
1288  if(context == NULL)
1289  return ERROR_INVALID_PARAMETER;
1290 
1291  //Close connection
1292  smtpClientCloseConnection(context);
1293  //Update SMTP client state
1295 
1296  //Successful processing
1297  return NO_ERROR;
1298 }
1299 
1300 
1301 /**
1302  * @brief Release SMTP client context
1303  * @param[in] context Pointer to the SMTP client context
1304  **/
1305 
1307 {
1308  //Make sure the SMTP client context is valid
1309  if(context != NULL)
1310  {
1311  //Close connection
1312  smtpClientCloseConnection(context);
1313 
1314 #if (SMTP_CLIENT_TLS_SUPPORT == ENABLED)
1315  //Release TLS session state
1316  tlsFreeSessionState(&context->tlsSession);
1317 #endif
1318 
1319  //Clear SMTP client context
1320  osMemset(context, 0, sizeof(SmtpClientContext));
1321  }
1322 }
1323 
1324 #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:1193
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:1285
uint16_t totalLength
Definition: ipv4.h:347
#define osStrlen(s)
Definition: os_port.h:168
void tlsFreeSessionState(TlsSessionState *session)
Properly dispose a session state.
Definition: tls.c:3065
@ 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:802
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: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:730
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:487
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:523
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:1306
error_t smtpClientCloseMailBody(SmtpClientContext *context)
Complete email sending process and wait for server's status.
Definition: smtp_client.c:1088
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:1166
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:939
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:562
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:434
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.
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: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:2922
#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.