Edit

kc3-lang/smtp/smtp.c

Branch :

  • smtp.c
  • /**
     * @file
     * @brief SMTP client library.
     * @author James Humphrey (mail@somnisoft.com)
     * @version 1.00
     *
     * This SMTP client library allows the user to send emails to an SMTP server.
     * The user can include custom headers and MIME attachments.
     *
     * This software has been placed into the public domain using CC0.
     */
    
    /**
     * @mainpage smtp-client
     *
     * This section contains documentation generated directly from the source
     * code.
     *
     * To view the repository details, visit the main smtp-client page at
     * <a href='https://www.somnisoft.com/smtp-client'>
     * www.somnisoft.com/smtp-client
     * </a>.
     */
    
    #if defined(_WIN32) || defined(WIN32) || defined(WIN64)
    # define SMTP_IS_WINDOWS
    #endif /* SMTP_IS_WINDOWS */
    
    #ifdef SMTP_IS_WINDOWS
    # include <winsock2.h>
    # include <ws2tcpip.h>
    #else /* POSIX */
    # include <netinet/in.h>
    # include <sys/select.h>
    # include <sys/socket.h>
    # include <netdb.h>
    # include <unistd.h>
    #endif /* SMTP_IS_WINDOWS */
    
    #include <errno.h>
    #include <limits.h>
    #include <signal.h>
    #include <stdarg.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>
    
    #ifdef SMTP_OPENSSL
    # include <openssl/bio.h>
    # include <openssl/err.h>
    # include <openssl/ssl.h>
    # include <openssl/x509.h>
    # include <openssl/x509v3.h>
    #endif /* SMTP_OPENSSL */
    
    /**
     * Get access to the @ref smtp_result_code and @ref smtp_command definitions.
     */
    #define SMTP_INTERNAL_DEFINE
    
    #include "smtp.h"
    
    /**
     * Increment the read buffer size by this amount if the delimiter
     * has not been found.
     */
    #define SMTP_GETDELIM_READ_SZ 1000
    
    /**
     * Stores source and destination email addresses.
     */
    struct smtp_address{
      /**
       * Email address without any special formatting.
       *
       * For example: mail@example.com
       */
      char *email;
    
      /**
       * Description of the email address.
       */
      char *name;
    
      /**
       * Specify from, to, cc, bcc.
       */
      enum smtp_address_type type;
    
      /**
       * Padding structure to align.
       */
      char pad[4];
    };
    
    /**
     * Attachment data which gets placed in the MIME email section.
     */
    struct smtp_attachment{
      /**
       * File name of the attachment.
       */
      char *name;
    
      /**
       * Base64-encoded file data.
       */
      char *b64_data;
    };
    
    /**
     * List of email headers to send before the mail body.
     */
    struct smtp_header{
      /**
       * Header name which will get sorted alphabetically in the header list.
       */
      char *key;
    
      /**
       * Content of the corresponding header key.
       */
      char *value;
    };
    
    /**
     * Main data structure that holds the SMTP client context.
     */
    struct smtp{
      /**
       * Bitwise list of flags controlling the behavior of this SMTP client.
       */
      enum smtp_flag flags;
    
      /**
       * Standard network socket connection.
       */
      int sock;
    
      /**
       * Read buffer and line parsing structure.
       */
      struct str_getdelimfd gdfd;
    
      /**
       * List of headers to print before the mail body.
       */
      struct smtp_header *header_list;
    
      /**
       * Number of headers in header_list.
       */
      size_t num_headers;
    
      /**
       * List of from, to, cc, and bcc email addresses.
       */
      struct smtp_address *address_list;
    
      /**
       * Number of addresses in address_list.
       */
      size_t num_address;
    
      /**
       * List of attachments to send.
       */
      struct smtp_attachment *attachment_list;
    
      /**
       * Number of attachments in attachment_list.
       */
      size_t num_attachment;
    
      /**
       * Timeout in seconds to wait before returning with an error.
       *
       * This applies to both writing to and reading from a network socket.
       */
      long timeout_sec;
    
      /**
       * Status code indicating success/failure.
       *
       * This code gets returned by most of the header functions.
       */
      enum smtp_status_code status_code;
    
      /**
       * Indicates if this context has an active TLS connection.
       *   - Set to 0 if TLS connection inactive.
       *   - Set to 1 if TLS connection currently active.
       */
      int tls_on;
    
      /**
       * Path to certificate file if using self-signed or untrusted certificate
       * not in the default key store.
       */
      const char *cafile;
    
    #ifdef SMTP_OPENSSL
      /**
       * OpenSSL TLS object.
       */
      SSL *tls;
    
      /**
       * OpenSSL TLS context.
       */
      SSL_CTX *tls_ctx;
    
      /**
       * OpenSSL TLS I/O abstraction.
       */
      BIO *tls_bio;
    #endif /* SMTP_OPENSSL */
    };
    
    /**
     * Check if adding a size_t value will cause a wrap.
     *
     * @param[in]  a      Add this value with @p b.
     * @param[in]  b      Add this value with @p a.
     * @param[out] result Save the addition to this buffer. Does not
     *                    perform the addition if set to NULL.
     * @retval 1 Value wrapped.
     * @retval 0 Value did not wrap.
     */
    int
    smtp_si_add_size_t(const size_t a,
                       const size_t b,
                       size_t *const result) {
      int wraps;
    
    #ifdef SMTP_TEST
      if (smtp_test_seam_dec_err_ctr(&g_smtp_test_err_si_add_size_t_ctr)) {
        return 1;
      }
    #endif /* SMTP_TEST */
    
      if (SIZE_MAX - a < b) {
        wraps = 1;
      }
      else{
        wraps = 0;
      }
      if (result) {
        *result = a + b;
      }
      return wraps;
    }
    
    /**
     * Check if subtracting a size_t value will cause wrap.
     *
     * @param[in]  a      Subtract this value by @p b.
     * @param[in]  b      Subtract this value from @p a.
     * @param[out] result Save the subtraction to this buffer. Does not
     *                    perform the subtraction if set to NULL.
     * @retval 1 Value wrapped.
     * @retval 0 Value did not wrap.
     */
    int
    smtp_si_sub_size_t(const size_t a,
                       const size_t b,
                       size_t *const result) {
      int wraps;
    
    #ifdef SMTP_TEST
      if (smtp_test_seam_dec_err_ctr(&g_smtp_test_err_si_sub_size_t_ctr)) {
        return 1;
      }
    #endif /* SMTP_TEST */
    
      if (a < b) {
        wraps = 1;
      }
      else{
        wraps = 0;
      }
      if (result) {
        *result = a - b;
      }
      return wraps;
    }
    
    /**
     * Check if multiplying a size_t value will cause a wrap.
     *
     * @param[in]  a      Multiply this value with @p b.
     * @param[in]  b      Multiply this value with @p a.
     * @param[out] result Save the multiplication to this buffer. Does not
     *                    perform the multiplication if set to NULL.
     * @retval 1 Value wrapped.
     * @retval 0 Value did not wrap.
     */
    int
    smtp_si_mul_size_t(const size_t a,
                       const size_t b,
                       size_t *const result) {
      int wraps;
    
    #ifdef SMTP_TEST
      if (smtp_test_seam_dec_err_ctr(&g_smtp_test_err_si_mul_size_t_ctr)) {
        return 1;
      }
    #endif /* SMTP_TEST */
    
      if (b != 0 && a > SIZE_MAX / b) {
        wraps = 1;
      }
      else{
        wraps = 0;
      }
      if (result) {
        *result = a * b;
      }
      return wraps;
    }
    
    /**
     * Wait until more data has been made available on the socket read end.
     *
     * @param[in] smtp SMTP client context.
     * @retval SMTP_STATUS_OK   If data available to read on the socket.
     * @retval SMTP_STATUS_RECV If the connection times out before any data
     *                          appears on the socket.
     */
    static enum smtp_status_code
    smtp_str_getdelimfd_read_timeout(struct smtp *const smtp) {
      fd_set readfds;
      struct timeval timeout;
      int sel_rc;
    
      FD_ZERO(&readfds);
      FD_SET((unsigned long) smtp->sock, &readfds);
      timeout.tv_sec  = smtp->timeout_sec;
      timeout.tv_usec = 0;
      sel_rc = select(smtp->sock + 1, &readfds, NULL, NULL, &timeout);
      if (sel_rc < 1) {
        return smtp_status_code_set(smtp, SMTP_STATUS_RECV);
      }
      return SMTP_STATUS_OK;
    }
    
    /**
     * This function gets called by the @ref smtp_str_getdelimfd interface when it
     * needs to read in more data.
     *
     * It reads using either the plain socket connection if encryption not
     * enabled, or it reads using OpenSSL if it has an active TLS connection.
     *
     * @param[in]  gdfd  See @ref str_getdelimfd.
     * @param[out] buf   Pointer to buffer for storing bytes read.
     * @param[in]  count Maximum number of bytes to try reading.
     * @retval >=0 Number of bytes read.
     * @retval -1  Failed to read from the socket.
     */
    static long
    smtp_str_getdelimfd_read(struct str_getdelimfd *const gdfd,
                             void *buf,
                             size_t count) {
      struct smtp *smtp;
      long bytes_read;
    
      smtp = gdfd->user_data;
    
      if (smtp_str_getdelimfd_read_timeout(smtp) != SMTP_STATUS_OK) {
        return -1;
      }
    
      bytes_read = 0;
      if (smtp->tls_on) {
    #ifdef SMTP_OPENSSL
        do{
          /*
           * Count will never have a value greater than SMTP_GETDELIM_READ_SZ,
           * so we can safely convert this to an int.
           */
          bytes_read = SSL_read(smtp->tls, buf, (int)count);
        } while(bytes_read <= 0 && BIO_should_retry(smtp->tls_bio));
    #endif /* SMTP_OPENSSL */
      }
      else{
        bytes_read = recv(smtp->sock, buf, count, 0);
      }
      return bytes_read;
    }
    
    /**
     * Find and return the location of the delimiter character in the
     * search buffer.
     *
     * This function gets used by the main socket parsing function which
     * continually reads from the socket and expands the buffer until it
     * encounters the expected delimiter. This function provides the logic
     * to check for the delimiter character in order to simplify the code
     * in the main parse function.
     *
     * @param[in]  buf       Search buffer used to find the delimiter.
     * @param[in]  buf_len   Number of bytes to search for in buf.
     * @param[in]  delim     The delimiter to search for in buf.
     * @param[out] delim_pos If delimiter found in buf, return the delimiter
     *                       position in this parameter.
     * @retval 1 If the delimiter character found.
     * @retval 0 If the delimiter character not found.
     */
    static int
    smtp_str_getdelimfd_search_delim(const char *const buf,
                                     size_t buf_len,
                                     int delim,
                                     size_t *const delim_pos) {
      size_t i;
    
      *delim_pos = 0;
      for(i = 0; i < buf_len; i++) {
        if (buf[i] == delim) {
          *delim_pos = i;
          return 1;
        }
      }
      return 0;
    }
    
    /**
     * Set the internal line buffer to the number of bytes specified.
     *
     * @param[in] gdfd     See @ref str_getdelimfd.
     * @param[in] copy_len Number of bytes to copy to the internal line buffer.
     * @retval  0 Successfully allocated and copied data over to the new
     *            line buffer.
     * @retval -1 Failed to allocate memory for the new line buffer.
     */
    int
    smtp_str_getdelimfd_set_line_and_buf(struct str_getdelimfd *const gdfd,
                                         size_t copy_len) {
      size_t copy_len_inc;
      size_t nbytes_to_shift;
      size_t new_buf_len;
    
      if (gdfd->line) {
        free(gdfd->line);
        gdfd->line = NULL;
      }
    
      if (smtp_si_add_size_t(copy_len, 1, &copy_len_inc) ||
         smtp_si_add_size_t((size_t)gdfd->_buf, copy_len_inc, NULL) ||
         smtp_si_sub_size_t(gdfd->_buf_len, copy_len, &nbytes_to_shift) ||
         (gdfd->line = calloc(1, copy_len_inc)) == NULL) {
        return -1;
      }
      memcpy(gdfd->line, gdfd->_buf, copy_len);
      gdfd->line_len = copy_len;
      memmove(gdfd->_buf, gdfd->_buf + copy_len_inc, nbytes_to_shift);
      if (smtp_si_sub_size_t(nbytes_to_shift, 1, &new_buf_len) == 0) {
        gdfd->_buf_len = new_buf_len;
      }
      return 0;
    }
    
    /**
     * Free memory in the @ref str_getdelimfd data structure.
     *
     * @param[in] gdfd Frees memory stored in this socket parsing structure.
     */
    void
    smtp_str_getdelimfd_free(struct str_getdelimfd *const gdfd) {
      free(gdfd->_buf);
      free(gdfd->line);
      gdfd->_buf = NULL;
      gdfd->_bufsz = 0;
      gdfd->_buf_len = 0;
      gdfd->line = NULL;
      gdfd->line_len = 0;
    }
    
    /**
     * Free the @ref str_getdelimfd and return the @ref STRING_GETDELIMFD_ERROR
     * error code.
     *
     * @param[in] gdfd See @ref str_getdelimfd.
     * @return See @ref str_getdelim_retcode.
     */
    static enum str_getdelim_retcode
    smtp_str_getdelimfd_throw_error(struct str_getdelimfd *const gdfd) {
      smtp_str_getdelimfd_free(gdfd);
      return STRING_GETDELIMFD_ERROR;
    }
    
    /**
     * Read and parse a delimited string using a custom socket read function.
     *
     * This interface handles all of the logic for expanding the buffer,
     * parsing the delimiter in the buffer, and returning each "line"
     * to the caller for handling.
     *
     * @param[in] gdfd See @ref str_getdelimfd.
     * @return See @ref str_getdelim_retcode.
     */
    enum str_getdelim_retcode
    smtp_str_getdelimfd(struct str_getdelimfd *const gdfd) {
      size_t delim_pos;
      long bytes_read;
      void *read_buf_ptr;
      char *buf_new;
      size_t buf_sz_remaining;
      size_t buf_sz_new;
    
      if (gdfd->getdelimfd_read == NULL) {
        return STRING_GETDELIMFD_ERROR;
      }
    
      bytes_read = -1;
    
      while(1) {
        if (smtp_str_getdelimfd_search_delim(gdfd->_buf,
                                            gdfd->_buf_len,
                                            gdfd->delim,
                                            &delim_pos)) {
          if (smtp_str_getdelimfd_set_line_and_buf(gdfd, delim_pos) < 0) {
            return smtp_str_getdelimfd_throw_error(gdfd);
          }
          return STRING_GETDELIMFD_NEXT;
        }else if (bytes_read == 0) {
          if (smtp_str_getdelimfd_set_line_and_buf(gdfd, gdfd->_buf_len) < 0) {
            return smtp_str_getdelimfd_throw_error(gdfd);
          }
          return STRING_GETDELIMFD_DONE;
        }
    
        if (smtp_si_sub_size_t(gdfd->_bufsz, gdfd->_buf_len, &buf_sz_remaining)) {
          return smtp_str_getdelimfd_throw_error(gdfd);
        }
    
        if (buf_sz_remaining < SMTP_GETDELIM_READ_SZ) {
          if (smtp_si_add_size_t(buf_sz_remaining,
                                SMTP_GETDELIM_READ_SZ,
                                &buf_sz_new)) {
            return smtp_str_getdelimfd_throw_error(gdfd);
          }
          buf_new = realloc(gdfd->_buf, buf_sz_new);
          if (buf_new == NULL) {
            return smtp_str_getdelimfd_throw_error(gdfd);
          }
          gdfd->_buf = buf_new;
          gdfd->_bufsz = buf_sz_new;
        }
    
        if (smtp_si_add_size_t((size_t)gdfd->_buf, gdfd->_buf_len, NULL)) {
          return smtp_str_getdelimfd_throw_error(gdfd);
        }
        read_buf_ptr = gdfd->_buf + gdfd->_buf_len;
        bytes_read = (*gdfd->getdelimfd_read)(gdfd,
                                              read_buf_ptr,
                                              SMTP_GETDELIM_READ_SZ);
        if (bytes_read < 0 ||
           smtp_si_add_size_t(gdfd->_buf_len,
                              (size_t)bytes_read,
                              &gdfd->_buf_len)) {
          return smtp_str_getdelimfd_throw_error(gdfd);
        }
      }
    }
    
    /**
     * Copy a string and get the pointer to the end of the copied buffer.
     *
     * This function behaves similar to POSIX stpcpy(), useful for
     * concatenating multiple strings onto a buffer. It always adds a
     * null-terminated byte at the end of the string.
     *
     * @param[in] s1 Destination buffer.
     * @param[in] s2 Null-terminated source string to copy to @p s1.
     * @return Pointer to location in @p s1 after the last copied byte.
     */
    char *
    smtp_stpcpy(char *s1,
                const char *s2) {
      size_t i;
    
      i = 0;
      do{
        s1[i] = s2[i];
      } while(s2[i++] != '\0');
      return &s1[i-1];
    }
    
    /**
     * Reallocate memory with unsigned wrapping checks.
     *
     * @param[in] ptr   Existing allocation buffer, or NULL when allocating a
     *                  new buffer.
     * @param[in] nmemb Number of elements to allocate.
     * @param[in] size  Size of each element in @p nmemb.
     * @retval void* Pointer to a reallocated buffer containing
     *               @p nmemb * @p size bytes.
     * @retval NULL  Failed to reallocate memory.
     */
    void *
    smtp_reallocarray(void *ptr,
                      size_t nmemb,
                      size_t size) {
      void *alloc;
      size_t size_mul;
    
      if (smtp_si_mul_size_t(nmemb, size, &size_mul)) {
        alloc = NULL;
        errno = ENOMEM;
      }
      else{
        alloc = realloc(ptr, size_mul);
      }
      return alloc;
    }
    
    /**
     * Copy a string into a new dynamically allocated buffer.
     *
     * Returns a dynamically allocated string, with the same contents as the
     * input string. The caller must free the returned string when finished.
     *
     * @param[in] s String to duplicate.
     * @retval char* Pointer to a new dynamically allocated string duplicated
     *               from @p s.
     * @retval NULL  Failed to allocate memory for the new duplicate string.
     */
    char *
    smtp_strdup(const char *s) {
      char *dup;
      size_t dup_len;
      size_t slen;
    
      slen = strlen(s);
      if (smtp_si_add_size_t(slen, 1, &dup_len)) {
        dup = NULL;
        errno = ENOMEM;
      }
      else if ((dup = malloc(dup_len)) != NULL) {
        memcpy(dup, s, dup_len);
      }
      return dup;
    }
    
    /**
     * Search for all substrings in a string and replace each instance with a
     * replacement string.
     *
     * @param[in] search  Substring to search for in @p s.
     * @param[in] replace Replace each instance of the search string with this.
     * @param[in] s       Null-terminated string to search and replace.
     * @retval char* A dynamically allocated string with the replaced instances
     *               as described above. The caller must free the allocated
     *               memory when finished.
     * @retval NULL  Memory allocation failure.
     */
    char *
    smtp_str_replace(const char *const search,
                     const char *const replace,
                     const char *const s) {
      size_t search_len;
      size_t replace_len;
      size_t replace_len_inc;
      size_t slen;
      size_t slen_inc;
      size_t s_idx;
      size_t snew_len;
      size_t snew_len_inc;
      size_t snew_sz;
      size_t snew_sz_dup;
      size_t snew_sz_plus_slen;
      size_t snew_replace_len_inc;
      char *snew;
      char *stmp;
    
      search_len    = strlen(search);
      replace_len   = strlen(replace);
      slen          = strlen(s);
      s_idx         = 0;
      snew          = NULL;
      snew_len      = 0;
      snew_sz       = 0;
    
      if (smtp_si_add_size_t(replace_len, 1, &replace_len_inc) ||
         smtp_si_add_size_t(slen, 1, &slen_inc)) {
        return NULL;
      }
    
      if (s[0] == '\0') {
        return smtp_strdup("");
      }
      else if (search_len < 1) {
        return smtp_strdup(s);
      }
    
      while(s[s_idx]) {
        if (smtp_si_add_size_t(snew_len, 1, &snew_len_inc) ||
           smtp_si_add_size_t(snew_sz, snew_sz, &snew_sz_dup) ||
           smtp_si_add_size_t(snew_sz, slen, &snew_sz_plus_slen)) {
          free(snew);
          return NULL;
        }
    
        if (strncmp(&s[s_idx], search, search_len) == 0) {
          if (smtp_si_add_size_t(snew_len, replace_len_inc, &snew_replace_len_inc)) {
            free(snew);
            return NULL;
          }
          if (snew_replace_len_inc >= snew_sz) {
            /* snew_sz += snew_sz + slen + replace_len + 1 */
            if (smtp_si_add_size_t(snew_sz_dup, slen_inc, &snew_sz) ||
               smtp_si_add_size_t(snew_sz, replace_len, &snew_sz) ||
               (stmp = realloc(snew, snew_sz)) == NULL) {
              free(snew);
              return NULL;
            }
            snew = stmp;
          }
          memcpy(&snew[snew_len], replace, replace_len);
          snew_len += replace_len;
          s_idx += search_len;
        }
        else{
          if (snew_len_inc >= snew_sz) {
            /* snew_sz += snew_sz + slen + snew_len + 1 */
            if (smtp_si_add_size_t(snew_sz_dup, slen, &snew_sz) ||
               smtp_si_add_size_t(snew_sz, snew_len_inc, &snew_sz) ||
               (stmp = realloc(snew, snew_sz)) == NULL) {
              free(snew);
              return NULL;
            }
            snew = stmp;
          }
          snew[snew_len] = s[s_idx];
          s_idx += 1;
          snew_len = snew_len_inc;
        }
      }
      snew[snew_len] = '\0';
    
      return snew;
    }
    
    /**
     * Lookup table used to encode data into base64.
     *
     * Base64 encoding takes six bits of data and encodes those bits using this
     * table. Since 2^6 = 64, this array has 64 entries which maps directly from
     * the 6 bit value into the corresponding array value.
     */
    static char g_base64_encode_table[] = {
      'A','B','C','D','E','F','G','H','I','J',
      'K','L','M','N','O','P','Q','R','S','T',
      'U','V','W','X','Y','Z',
      'a','b','c','d','e','f','g','h','i','j',
      'k','l','m','n','o','p','q','r','s','t',
      'u','v','w','x','y','z',
      '0','1','2','3','4','5','6','7','8','9',
      '+','/'
    };
    
    /**
     * Encode a single block of binary data into base64.
     *
     * @param[in]  buf          Buffer with data to encode.
     * @param[in]  buf_block_sz Number of bytes in buf to encode (min 1, max 3).
     * @param[out] b64          Pointer to buffer with at least 4 bytes for
     *                          storing the base64 encoded result.
     */
    static void
    smtp_base64_encode_block(const char *const buf,
                             size_t buf_block_sz,
                             char *const b64) {
      unsigned char inb[3] = {0};
      unsigned char in_idx[4] = {0};
      char outb[5] = {'=', '=', '=', '=', '\0'};
      size_t i;
    
      memcpy(inb, buf, buf_block_sz);
    
      in_idx[0] = ((inb[0] >> 2))                         & 0x3F;
      in_idx[1] = ((inb[0] << 4) | ((inb[1] >> 4) & 0xF)) & 0x3F;
      in_idx[2] = ((inb[1] << 2) | ((inb[2] >> 6) & 0x3)) & 0x3F;
      in_idx[3] = ((inb[2]     ))                         & 0x3F;
      for(i = 0; i < 4; i++) {
        if (i < buf_block_sz + 1) {
          outb[i] = g_base64_encode_table[in_idx[i]];
        }
        b64[i] = outb[i];
      }
    }
    
    /**
     * Encode binary data into a base64 string.
     *
     * @param[in] buf    Binary data to encode in base64.
     * @param[in] buflen Number of bytes in the @p buf parameter, or -1 if
     *                   null-terminated.
     * @retval char* Dynamically allocated base64 encoded string. The caller
     *               must free this string when finished.
     * @retval NULL  Memory allocation failure.
     */
    char *
    smtp_base64_encode(const char *const buf,
                       size_t buflen) {
      char *b64;
      size_t b64_sz;
      size_t buf_i;
      size_t b64_i;
      size_t remaining_block_sz;
      size_t buf_block_sz;
    
      if (buflen == SIZE_MAX) {
        buflen = strlen(buf);
      }
    
      /*
       * base64 size expands by 33%
       * +1 to round integer division up
       * +2 for '=' padding
       * +1 null terminator
       */
      if (smtp_si_mul_size_t(buflen, 4, NULL)) {
        return NULL;
      }
      b64_sz = (4 * buflen / 3) + 1 + 2 + 1;
      if ((b64 = calloc(1, b64_sz)) == NULL) {
        return NULL;
      }
    
      if (buflen == 0) {
        return b64;
      }
    
      buf_i = 0;
      b64_i = 0;
      remaining_block_sz = buflen;
      while(remaining_block_sz > 0) {
        if (remaining_block_sz >= 3) {
          buf_block_sz = 3;
        }
        else{
          buf_block_sz = remaining_block_sz;
        }
    
        smtp_base64_encode_block(&buf[buf_i], buf_block_sz, &b64[b64_i]);
    
        /*
         * Do not need to check for wrapping because these values restricted to
         * range of b64_sz, which has already been checked for wrapping above.
         */
        buf_i += 3;
        b64_i += 4;
    
        remaining_block_sz -= buf_block_sz;
      }
    
      return b64;
    }
    
    #ifdef SMTP_OPENSSL
    /**
     * Lookup table used to decode base64 data.
     *
     * For base64 encoding, every six bits have been encoded using only the ASCII
     * characters from @ref g_base64_encode_table. This table has entries which
     * allow the reversal of that process. It has 128 entries which map over to
     * the index value from the encoding table. If an indexing result ends up
     * with -1 during the decoding process, then that indicates an invalid base64
     * character in the encoded data.
     */
    static signed char
    g_base64_decode_table[] = {
      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1,
      62,                                     /*   +   */
      -1, -1, -1,
      63,                                     /*   /   */
      52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* 0 - 9 */
      -1, -1, -1, -1, -1, -1, -1,
       0,  1,  2,  3,  4,  5,  6,  7,  8,  9, /* A - J */
      10, 11, 12, 13, 14, 15, 16, 17, 18, 19, /* K - T */
      20, 21, 22, 23, 24, 25,                 /* U - Z */
      -1, -1, -1, -1, -1, -1,
      26, 27, 28, 29, 30, 31, 32, 33, 34, 35, /* a - j */
      36, 37, 38, 39, 40, 41, 42, 43, 44, 45, /* k - t */
      46, 47, 48, 49, 50, 51,                 /* u - z */
      -1, -1, -1, -1, -1
    };
    
    /**
     * Decodes a base64 block of up to four bytes at a time.
     *
     * @param[in]  buf    Buffer containing bytes to decode.
     * @param[out] decode Buffer for storing base64 decoded bytes.
     * @retval >0 Length of the decoded block.
     * @retval  0 If the block contains invalid base64 data.
     */
    static size_t
    smtp_base64_decode_block(const unsigned char *const buf,
                             unsigned char *const decode) {
      size_t decode_block_len;
      size_t i;
      signed char decode_table[4];
      unsigned char outb[3];
    
      decode_block_len = 0;
      for(i = 0; i < 4; i++) {
        if (buf[i] == '=') {
          decode_table[i] = 0;
          continue;
        }
        decode_table[i] = g_base64_decode_table[buf[i]];
        if (decode_table[i] < 0) {
          return 0;
        }
      }
    
      outb[0] = ((decode_table[0] << 2) & 0xFC) | ((decode_table[1] >> 4) & 0x03);
      outb[1] = ((decode_table[1] << 4) & 0xF0) | ((decode_table[2] >> 2) & 0x0F);
      outb[2] = ((decode_table[2] << 6) & 0xC0) | ((decode_table[3]     ) & 0x3F);
    
      decode[0] = outb[0];
      decode_block_len += 1;
    
      if (buf[2] == '=') {
        decode[1] = '\0';
      }
      else{
        decode[1] = outb[1];
        decode_block_len += 1;
      }
    
      if (buf[3] == '=') {
        decode[2] = '\0';
      }
      else{
        decode[2] = outb[2];
        decode_block_len += 1;
      }
    
      return decode_block_len;
    }
    
    /**
     * Decode a base64 string.
     *
     * The decode parameter will get dynamically allocated by this function
     * if it successfully completes. Therefore, the caller must free the decode
     * parameter after use.
     *
     * @param[in]  buf    Null-terminated base64 string.
     * @param[out] decode Pointer to buffer which will get dynamically allocated
     *                    and will contain the decoded binary data. This parameter
     *                    will get set to NULL if the memory allocation fails.
     * @retval >=0 Length of the data stored in the decode parameter.
     * @retval -1  Memory allocation failure or invalid base64 byte sequences.
     */
    size_t
    smtp_base64_decode(const char *const buf,
                       unsigned char **decode) {
      size_t buf_len;
      size_t buf_len_inc;
      size_t buf_i;
      unsigned char *b64_decode;
      size_t decode_len;
      size_t decode_block_len;
    
      *decode = NULL;
    
      buf_len = strlen(buf);
      if (buf_len % 4 != 0) {
        return SIZE_MAX;
      }
    
      if (smtp_si_add_size_t(buf_len, 1, &buf_len_inc) ||
         (b64_decode = calloc(1, buf_len_inc)) == NULL) {
        return SIZE_MAX;
      }
    
      decode_len = 0;
      for(buf_i = 0; buf_i < buf_len; buf_i += 4) {
        decode_block_len = smtp_base64_decode_block(
                             (const unsigned char*)&buf[buf_i],
                             &b64_decode[decode_len]);
        if (decode_block_len == 0) {
          free(b64_decode);
          return SIZE_MAX;
        }
        decode_len += decode_block_len;
      }
      *decode = b64_decode;
      return decode_len;
    }
    
    /**
     * Convert binary data to lowercase hexadecimal representation.
     *
     * @param[in] s    Buffer containing binary data to convert.
     * @param[in] slen Number of bytes in @p s.
     * @retval char* Dynamically allocated string consisting of a hexadecimal
     *               representation of binary data in @p s. The caller must free
     *               this memory when finished.
     * @retval NULL  Memory allocation or encoding error.
     */
    char *
    smtp_bin2hex(const unsigned char *const s,
                 size_t slen) {
      char *snew;
      size_t alloc_sz;
      size_t i;
      size_t j;
      unsigned hex;
      int rc;
    
      /* alloc_sz = slen * 2 + 1 */
      if (smtp_si_mul_size_t(slen, 2, &alloc_sz) ||
         smtp_si_add_size_t(alloc_sz, 1, &alloc_sz)) {
        return NULL;
      }
      if ((snew = malloc(alloc_sz)) == NULL) {
        return NULL;
      }
    
      j = 0;
      for(i = 0; i < slen; i++) {
        hex = s[i];
        rc = sprintf(&snew[j], "%02x", hex);
        if (rc < 0 || (size_t)rc >= 3) {
          free(snew);
          return NULL;
        }
        j += 2;
      }
      snew[j] = '\0';
    
      return snew;
    }
    #endif /* SMTP_OPENSSL */
    
    /**
     * Get the length in bytes of a UTF-8 character.
     *
     * This consists of a very simple check and assumes the user provides a valid
     * UTF-8 byte sequence. It gets the length from the first byte in the sequence
     * and does not validate any other bytes in the character sequence or any other
     * bits in the first byte of the character sequence.
     *
     * @param[in] c The first byte in a valid UTF-8 character sequence.
     * @retval >0 Number of bytes for the current UTF-8 character sequence.
     * @retval -1 Invalid byte sequence.
     */
    size_t
    smtp_utf8_charlen(char c) {
      unsigned char uc;
    
      uc = (unsigned char)c;
      if ((uc & 0x80) == 0) {         /* 0XXXXXXX */
        return 1;
      }
      else if ((uc & 0xE0) == 0xC0) { /* 110XXXXX */
        return 2;
      }
      else if ((uc & 0xF0) == 0xE0) { /* 1110XXXX */
        return 3;
      }
      else if ((uc & 0xF8) == 0xF0) { /* 11110XXX */
        return 4;
      }
      else{                        /* invalid  */
        return 0;
      }
    }
    
    /**
     * Check if a string contains non-ASCII UTF-8 characters.
     *
     * Uses the simple algorithm from @ref smtp_utf8_charlen to check for
     * non-ASCII UTF-8 characters.
     *
     * @param[in] s UTF-8 string.
     * @retval 1 String contains non-ASCII UTF-8 characters.
     * @retval 0 String contains only ASCII characters.
     */
    int
    smtp_str_has_nonascii_utf8(const char *const s) {
      size_t i;
      size_t charlen;
    
      for(i = 0; s[i]; i++) {
        charlen = smtp_utf8_charlen(s[i]);
        if (charlen != 1) {
          return 1;
        }
      }
      return 0;
    }
    
    /**
     * Get the number of bytes in a UTF-8 string, or a shorter count if
     * the string exceeds a maximum specified length.
     *
     * See @p maxlen for more information on multi-byte parsing.
     *
     * @param[in] s      Null-terminated UTF-8 string.
     * @param[in] maxlen Do not check more than @p maxlen bytes of string @p s
     *                   except if in the middle of a multi-byte character.
     * @retval strlen(s) If length of s has less bytes than maxlen or the same
     *                   number of bytes as maxlen. See @p maxlen for more details.
     * @retval maxlen    If length of s has more bytes than maxlen.
     * @retval -1        If @p s contains an invalid UTF-8 byte sequence.
     */
    size_t
    smtp_strnlen_utf8(const char *s,
                      size_t maxlen) {
      size_t i;
      size_t utf8_i;
      size_t utf8_len;
    
      for(i = 0; *s && i < maxlen; i += utf8_len) {
        utf8_len = smtp_utf8_charlen(*s);
        if (utf8_len == 0) {
          return SIZE_MAX;
        }
    
        for(utf8_i = 0; utf8_i < utf8_len; utf8_i++) {
          if (!*s) {
            return SIZE_MAX;
          }
          s += 1;
        }
      }
      return i;
    }
    
    /**
     * Get the offset of the next whitespace block to process folding.
     *
     * If a string does not have whitespace before @p maxlen, then the index
     * will get returned past @p maxlen. Also returns the index of NULL character
     * if that fits within the next block. The caller must check for the NULL
     * index to indicate the last block. It will skip past any leading whitespace
     * even if that means going over maxlen.
     *
     * Examples:
     * @ref smtp_fold_whitespace_get_offset ("Subject: Test WS", 1/2/8/9/10/13) -> 8
     * @ref smtp_fold_whitespace_get_offset ("Subject: Test WS", 14/15) -> 13
     * @ref smtp_fold_whitespace_get_offset ("Subject: Test WS", 17/18) -> 16
     *
     * @param[in] s      String to get offset from.
     * @param[in] maxlen Number of bytes for each line in the string (soft limit).
     * @return Index in @p s.
     */
    size_t
    smtp_fold_whitespace_get_offset(const char *const s,
                                    unsigned int maxlen) {
      size_t i;
      size_t offset_i;
    
      i = 0;
      offset_i = 0;
    
      while(s[i] == ' ' || s[i] == '\t') {
        i += 1;
      }
    
      while(s[i]) {
        if (s[i] == ' ' || s[i] == '\t') {
          do{
            i += 1;
          } while(s[i] == ' ' || s[i] == '\t');
          i -= 1;
          if (i < maxlen || !offset_i) {
            offset_i = i;
          }
          else{
            break;
          }
        }
        i += 1;
      }
    
      if (!offset_i || i < maxlen) {
        offset_i = i;
      }
    
      return offset_i;
    }
    
    /**
     * Email header lines should have no more than 78 characters and must
     * not be more than 998 characters.
     */
    #define SMTP_LINE_MAX 78
    
    /**
     * Fold a line at whitespace characters.
     *
     * This function tries to keep the total number of characters per line under
     * @p maxlen, but does not guarantee this. For really long text with no
     * whitespace, the line will still extend beyond @p maxlen and possibly
     * beyond the RFC limit as defined in @ref SMTP_LINE_MAX. This is by design
     * and intended to keep the algorithm simpler to implement. Users sending
     * long headers with no space characters should not assume that will work,
     * but modern email systems may correctly process those headers anyways.
     *
     * Lines get folded by adding a [CR][LF] and then two space characters on the
     * beginning of the next line. For example, this Subject line:
     *
     * Subject: Email[WS][WS]Header
     *
     * Would get folded like this (assuming a small @p maxlen):
     *
     * Subject: Email[WS][CR][LF]
     * [WS][WS]Header
     *
     * @param[in] s      String to fold.
     * @param[in] maxlen Number of bytes for each line in the string (soft limit).
     *                   The minimum value of this parameter is 3 and it will get
     *                   forced to 3 if the provided value is less.
     * @retval char* Pointer to an allocated string with the contents split into
     *               separate lines. The caller must free this memory when done.
     * @retval NULL  Memory allocation failed.
     */
    char *
    smtp_fold_whitespace(const char *const s,
                         unsigned int maxlen) {
      const char *const SMTP_LINE_FOLD_STR = "\r\n ";
      size_t end_slen;
      size_t s_i;
      size_t buf_i;
      size_t bufsz;
      size_t ws_offset;
      char *buf;
      char *buf_new;
    
      if (maxlen < 3) {
        maxlen = 3;
      }
    
      end_slen = strlen(SMTP_LINE_FOLD_STR);
    
      s_i = 0;
      buf_i = 0;
      bufsz = 0;
      buf = NULL;
    
      while(1) {
        ws_offset = smtp_fold_whitespace_get_offset(&s[s_i], maxlen - 2);
    
        /* bufsz += ws_offset + end_slen + 1 */
        if (smtp_si_add_size_t(bufsz, ws_offset, &bufsz) ||
           smtp_si_add_size_t(bufsz, end_slen, &bufsz) ||
           smtp_si_add_size_t(bufsz, 1, &bufsz) ||
           (buf_new = realloc(buf, bufsz)) == NULL) {
          free(buf);
          return NULL;
        }
        buf = buf_new;
        memcpy(&buf[buf_i], &s[s_i], ws_offset);
        buf[buf_i + ws_offset] = '\0';
    
        if (s[s_i + ws_offset] == '\0') {
          break;
        }
    
        buf_i += ws_offset;
        strcat(&buf[buf_i], SMTP_LINE_FOLD_STR);
        buf_i += end_slen;
    
        /*                 WS */
        s_i += ws_offset + 1;
      }
      return buf;
    }
    
    /**
     * Splits a string into smaller chunks separated by a terminating string.
     *
     * @param[in] s        The string to chunk.
     * @param[in] chunklen Number of bytes for each chunk in the string.
     * @param[in] end      Terminating string placed at the end of each chunk.
     * @retval char* Pointer to an allocated string with the contents split into
     *               separate chunks. The caller must free this memory when done.
     * @retval NULL  Memory allocation failure.
     */
    char *
    smtp_chunk_split(const char *const s,
                     size_t chunklen,
                     const char *const end) {
      char *snew;
      size_t bodylen;
      size_t bodylen_inc;
      size_t endlen;
      size_t endlen_inc;
      size_t snewlen;
      size_t chunk_i;
      size_t snew_i;
      size_t body_i;
      size_t body_copy_len;
    
      if (chunklen < 1) {
        errno = EINVAL;
        return NULL;
      }
    
      bodylen = strlen(s);
      endlen = strlen(end);
    
      if (bodylen < 1) {
        return smtp_strdup(end);
      }
    
      /*
       *                                                               \0
       * snewlen = bodylen + (endlen + 1) * (bodylen / chunklen + 1) + 1
       */
      if (smtp_si_add_size_t(endlen, 1, &endlen_inc) ||
         smtp_si_add_size_t(bodylen, 1, &bodylen_inc) ||
         smtp_si_mul_size_t(endlen_inc, bodylen / chunklen + 1, &snewlen) ||
         smtp_si_add_size_t(snewlen, bodylen_inc, &snewlen) ||
         (snew = calloc(1, snewlen)) == NULL) {
        return NULL;
      }
    
      body_i = 0;
      snew_i = 0;
      for(chunk_i = 0; chunk_i < bodylen / chunklen + 1; chunk_i++) {
        body_copy_len = smtp_strnlen_utf8(&s[body_i], chunklen);
        if (body_copy_len == SIZE_MAX) {
          free(snew);
          return NULL;
        }
        memcpy(&snew[snew_i], &s[body_i], body_copy_len);
        snew_i += body_copy_len;
        if (s[body_i] == '\0') {
          snew_i += 1;
        }
        body_i += body_copy_len;
    
        if (endlen > 0) {
          memcpy(&snew[snew_i], end, endlen);
        }
        snew_i += endlen;
      }
    
      return snew;
    }
    
    /**
     * Read the entire contents of a file stream and store the data into a
     * dynamically allocated buffer.
     *
     * @param[in]  stream     File stream already opened by the caller.
     * @param[out] bytes_read Number of bytes stored in the return buffer.
     * @retval char* A dynamically allocated buffer which contains the entire
     *               contents of @p stream. The caller must free this memory
     *               when done.
     * @retval NULL Memory allocation or file read error.
     */
    char *
    smtp_ffile_get_contents(FILE *stream,
                            size_t *bytes_read) {
      char *read_buf;
      size_t bufsz;
      size_t bufsz_inc;
      char *new_buf;
      size_t bytes_read_loop;
      const size_t BUFSZ_INCREMENT = 512;
    
      read_buf = NULL;
      bufsz = 0;
    
      if (bytes_read) {
        *bytes_read = 0;
      }
    
      do{
        if (smtp_si_add_size_t(bufsz, BUFSZ_INCREMENT, &bufsz_inc) ||
           (new_buf = realloc(read_buf, bufsz_inc)) == NULL) {
          free(read_buf);
          return NULL;
        }
        read_buf = new_buf;
        bufsz = bufsz_inc;
    
        bytes_read_loop = fread(&read_buf[bufsz - BUFSZ_INCREMENT],
                                sizeof(char),
                                BUFSZ_INCREMENT,
                                stream);
        if (bytes_read) {
          *bytes_read += bytes_read_loop;
        }
        if (ferror(stream)) {
          free(read_buf);
          return NULL;
        }
      } while(!feof(stream));
    
      return read_buf;
    }
    
    /**
     * Read the entire contents of a file from a given path, and store the data
     * into a dynamically allocated buffer.
     *
     * @param[in]  filename   Path of file to open and read from.
     * @param[out] bytes_read Number of bytes stored in the return buffer.
     * @retval char* A dynamically allocated buffer which has the contents of
     *         the file at @p filename. The caller must free this memory when
     *         done.
     * @retval NULL Memory allocation or file read error.
     */
    char *
    smtp_file_get_contents(const char *const filename,
                           size_t *bytes_read) {
      FILE *fp;
      char *read_buf;
    
      if ((fp = fopen(filename, "rb")) == NULL) {
        return NULL;
      }
    
      read_buf = smtp_ffile_get_contents(fp, bytes_read);
    
      if (fclose(fp) == EOF) {
        free(read_buf);
        read_buf = NULL;
      }
    
      return read_buf;
    }
    
    /**
     * Parse a server response line into the @ref smtp_command data structure.
     *
     * @param[in]  line Server response string.
     * @param[out] cmd  Structure containing the server response data broken up
     *                  into its separate components.
     * @return See @ref smtp_result_code.
     */
    int
    smtp_parse_cmd_line(char *const line,
                        struct smtp_command *const cmd) {
      char *ep;
      char code_str[4];
      size_t line_len;
      unsigned long int ulcode;
    
      line_len = strlen(line);
      if (line_len < 5) {
        cmd->code = SMTP_INTERNAL_ERROR;
        cmd->more = 0;
        cmd->text = line;
        return cmd->code;
      }
    
      cmd->text = &line[4];
    
      memcpy(code_str, line, 3);
      code_str[3] = '\0';
      ulcode = strtoul(code_str, &ep, 10);
      if (*ep != '\0' || ulcode > SMTP_BEGIN_MAIL) {
        cmd->code = SMTP_INTERNAL_ERROR;
      }
      else{
        cmd->code = (enum smtp_result_code)ulcode;
      }
    
      if (line[3] == '-') {
        cmd->more = 1;
      }
      else{
        cmd->more = 0;
      }
      return cmd->code;
    }
    
    /**
     * Prints communication between the client and server to stderr only if
     * the debug flag has been set.
     *
     * @param[in] smtp   SMTP client context.
     * @param[in] prefix Print this prefix before the main debug line text.
     * @param[in] str    Debug text to print out.
     */
    static void
    smtp_puts_dbg(struct smtp *const smtp,
                  const char *const prefix,
                  const char *const str) {
      char *sdup;
      size_t i;
    
      if (smtp->flags & SMTP_DEBUG) {
        if ((sdup = smtp_strdup(str)) == NULL) {
          return;
        }
    
        /* Remove carriage return and newline when printing to stderr. */
        for(i = 0; sdup[i]; i++) {
          if (sdup[i] == '\r' || sdup[i] == '\n') {
            sdup[i] = ' ';
          }
        }
    
        if (fprintf(stderr, "[smtp %s]: %s\n", prefix, sdup) < 0) {
          /* Do not care if this fails. */
        }
        free(sdup);
      }
    }
    
    /**
     * Read a server response line.
     *
     * @param[in] smtp SMTP client context.
     * @return See @ref str_getdelim_retcode.
     */
    static enum str_getdelim_retcode
    smtp_getline(struct smtp *const smtp) {
      enum str_getdelim_retcode rc;
    
      errno = 0;
      rc = smtp_str_getdelimfd(&smtp->gdfd);
      if (errno == ENOMEM) {
        smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
        return rc;
      }
      else if (rc == STRING_GETDELIMFD_ERROR) {
        smtp_status_code_set(smtp, SMTP_STATUS_RECV);
        return STRING_GETDELIMFD_ERROR;
      }
    
      if (smtp->gdfd.line_len > 0) {
        /* Remove the carriage-return character ('\r'). */
        smtp->gdfd.line[smtp->gdfd.line_len - 1] = '\0';
        smtp_puts_dbg(smtp, "Server", smtp->gdfd.line);
      }
      return rc;
    }
    
    /**
     * Loop through all of the server response lines until the last line, and
     * then return the status code from the last response line.
     *
     * @param[in] smtp SMTP client context.
     * @return See @ref smtp_result_code.
     */
    static int
    smtp_read_and_parse_code(struct smtp *const smtp) {
      struct smtp_command cmd;
      enum str_getdelim_retcode rc;
    
      do{
        rc = smtp_getline(smtp);
        if (rc == STRING_GETDELIMFD_ERROR) {
          return SMTP_INTERNAL_ERROR;
        }
    
        smtp_parse_cmd_line(smtp->gdfd.line, &cmd);
      }while (rc != STRING_GETDELIMFD_DONE && cmd.more);
    
      return cmd.code;
    }
    
    /**
     * Send data to the SMTP server.
     *
     * Writes a buffer of length len into either the unencrypted TCP socket or
     * the TLS encrypted socket, depending on the current underlying mode of
     * the socket.
     *
     * @param[in] smtp SMTP client context.
     * @param[in] buf Data to send to the SMTP server.
     * @param[in] len Number of bytes in buf.
     * @return See @ref smtp_status_code.
     */
    enum smtp_status_code
    smtp_write(struct smtp *const smtp,
               const char *const buf,
               size_t len) {
      size_t bytes_to_send;
      long bytes_sent;
      const char *buf_offset;
      int ssl_bytes_to_send;
    
      smtp_puts_dbg(smtp, "Client", buf);
    
      bytes_to_send = len;
      buf_offset = buf;
      while(bytes_to_send) {
        if (bytes_to_send > INT_MAX) {
          return smtp_status_code_set(smtp, SMTP_STATUS_SEND);
        }
    
        if (smtp->tls_on) {
    #ifdef SMTP_OPENSSL
          /* bytes_to_send <= INT_MAX */
          ssl_bytes_to_send = (int)bytes_to_send;
          bytes_sent = SSL_write(smtp->tls, buf_offset, ssl_bytes_to_send);
          if (bytes_sent <= 0) {
            return smtp_status_code_set(smtp, SMTP_STATUS_SEND);
          }
    #else /* !(SMTP_OPENSSL) */
          /* unreachable */
          bytes_sent = 0;
          (void)ssl_bytes_to_send;
    #endif /* SMTP_OPENSSL */
        }
        else{
          bytes_sent = send(smtp->sock, buf_offset, bytes_to_send, 0);
          if (bytes_sent < 0) {
            return smtp_status_code_set(smtp, SMTP_STATUS_SEND);
          }
        }
        bytes_to_send -= (size_t)bytes_sent;
        buf_offset += bytes_sent;
      }
    
      return smtp->status_code;
    }
    
    /**
     * Send a null-terminated string to the SMTP server.
     *
     * @param[in] smtp SMTP client context.
     * @param[in] s    Null-terminated string to send to the SMTP server.
     * @return See @ref smtp_status_code and @ref smtp_write.
     */
    static enum smtp_status_code
    smtp_puts(struct smtp *const smtp,
              const char *const s) {
      return smtp_write(smtp, s, strlen(s));
    }
    
    /**
     * Same as @ref smtp_puts except this function also appends the line
     * terminating carriage return and newline bytes at the end of the string.
     *
     * @param[in] smtp SMTP client context.
     * @param[in] s    Null-terminated string to send to the SMTP server.
     * @return See @ref smtp_status_code and @ref smtp_puts.
     */
    static enum smtp_status_code
    smtp_puts_terminate(struct smtp *const smtp,
                        const char *const s) {
      enum smtp_status_code rc;
      char *line;
      char *concat;
      size_t slen;
      size_t allocsz;
    
      slen = strlen(s);
      if (smtp_si_add_size_t(slen, 3, &allocsz) ||
         (line = malloc(allocsz)) == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
      concat = smtp_stpcpy(line, s);
      smtp_stpcpy(concat, "\r\n");
      rc = smtp_puts(smtp, line);
      free(line);
      return rc;
    }
    
    /**
     * Connect to the server using a standard TCP socket.
     *
     * This function handles the server name lookup to get an IP address
     * for the server, and then to connect to that IP using a normal TCP
     * connection.
     *
     * @param[in] smtp   SMTP client context.
     * @param[in] server Mail server name or IP address.
     * @param[in] port   Mail server port number.
     * @retval  0 Successfully connected to server.
     * @retval -1 Failed to connect to server.
     */
    static int
    smtp_connect(struct smtp *const smtp,
                 const char *const server,
                 const char *const port) {
      struct addrinfo hints;
      struct addrinfo *res0;
      struct addrinfo *res;
    
      /*
       * Windows requires initializing the socket library before we call any
       * socket functions.
       */
    #ifdef SMTP_IS_WINDOWS
      /* Windows global network socket data structure. */
      WSADATA wsa_data;
      if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
        return -1;
      }
    #endif /* SMTP_IS_WINDOWS */
    
      memset(&hints, 0, sizeof(hints));
      hints.ai_family   = AF_UNSPEC;
      hints.ai_socktype = SOCK_STREAM;
      hints.ai_flags    = 0;
      hints.ai_protocol = IPPROTO_TCP;
    
      if (getaddrinfo(server, port, &hints, &res0) != 0) {
        return -1;
      }
    
      for(res = res0; res; res = res->ai_next) {
        smtp->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (smtp->sock < 0) {
          continue;
        }
    
        if (connect(smtp->sock, res->ai_addr, res->ai_addrlen) < 0) {
    #ifdef SMTP_IS_WINDOWS
          closesocket(smtp->sock);
    #else /* POSIX */
          close(smtp->sock);
    #endif /* SMTP_IS_WINDOWS */
          smtp->sock = -1;
        }
        else{
          break;
        }
      }
    
      freeaddrinfo(res0);
      if (smtp->sock < 0) {
        return -1;
      }
    
      return 0;
    }
    
    #ifdef SMTP_OPENSSL
    /**
     * Initialize the TLS library and establish a TLS handshake with the server
     * over the existing socket connection.
     *
     * @param[in] smtp   SMTP client context.
     * @param[in] server Server name or IP address.
     * @retval  0 Successfully established a TLS connection with the server.
     * @retval -1 Failed to establish a TLS connection with the server.
     */
    static int
    smtp_tls_init(struct smtp *const smtp,
                  const char *const server) {
      X509 *X509_cert_peer;
    
      /* Do not need to check the return value since this always returns 1. */
      SSL_library_init();
    
      SSL_load_error_strings();
    #if OPENSSL_VERSION_NUMBER < 0x30000000L || defined(LIBRESSL_VERSION_NUMBER)
      ERR_load_BIO_strings();
    #endif
      OpenSSL_add_all_algorithms();
    
      if ((smtp->tls_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
        return -1;
      }
    
      /* Disable SSLv2, SSLv3, and TLSv1.0. */
      SSL_CTX_set_options(smtp->tls_ctx,
                          SSL_OP_NO_SSLv2 |
                          SSL_OP_NO_SSLv3 |
                          SSL_OP_NO_TLSv1);
    
      SSL_CTX_set_mode(smtp->tls_ctx, SSL_MODE_AUTO_RETRY);
      SSL_CTX_set_verify(smtp->tls_ctx, SSL_VERIFY_PEER, NULL);
    
      /*
       * Set the path to the user-provided CA file or use the default cert paths
       * if not provided.
       */
      if (smtp->cafile) {
        if (SSL_CTX_load_verify_locations(smtp->tls_ctx, smtp->cafile, NULL) != 1) {
          SSL_CTX_free(smtp->tls_ctx);
          return -1;
        }
      }
      else{
        X509_STORE_set_default_paths(SSL_CTX_get_cert_store(smtp->tls_ctx));
        if (ERR_peek_error() != 0) {
          SSL_CTX_free(smtp->tls_ctx);
          return -1;
        }
      }
    
      if ((smtp->tls = SSL_new(smtp->tls_ctx)) == NULL) {
        SSL_CTX_free(smtp->tls_ctx);
        return -1;
      }
    
      if ((smtp->tls_bio = BIO_new_socket(smtp->sock, 0)) == NULL) {
        SSL_CTX_free(smtp->tls_ctx);
        SSL_free(smtp->tls);
        return -1;
      }
    
      SSL_set_bio(smtp->tls, smtp->tls_bio, smtp->tls_bio);
      SSL_set_connect_state(smtp->tls);
      if (SSL_connect(smtp->tls) != 1) {
        SSL_CTX_free(smtp->tls_ctx);
        SSL_free(smtp->tls);
        return -1;
      }
    
      if (SSL_do_handshake(smtp->tls) != 1) {
        SSL_CTX_free(smtp->tls_ctx);
        SSL_free(smtp->tls);
        return -1;
      }
    
      /* Verify matching subject in certificate. */
      if ((X509_cert_peer = SSL_get_peer_certificate(smtp->tls)) == NULL) {
        SSL_CTX_free(smtp->tls_ctx);
        SSL_free(smtp->tls);
        return -1;
      }
      if (X509_check_host(X509_cert_peer, server, 0, 0, NULL) != 1) {
        SSL_CTX_free(smtp->tls_ctx);
        SSL_free(smtp->tls);
        return -1;
      }
      X509_free(X509_cert_peer);
    
      smtp->tls_on = 1;
      return 0;
    }
    #endif /* SMTP_OPENSSL */
    
    /**
     * Send the EHLO command and parse through the responses.
     *
     * Ignores all of the server extensions that get returned. If a server
     * doesn't support an extension we need, then we should receive an error
     * later on when we try to use that extension.
     *
     * @param[in] smtp SMTP client context.
     * @return See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_ehlo(struct smtp *const smtp) {
      if (smtp_puts(smtp, "EHLO smtp\r\n") == SMTP_STATUS_OK) {
        smtp_read_and_parse_code(smtp);
      }
      return smtp->status_code;
    }
    
    /**
     * Authenticate using the PLAIN method.
     *
     *   1. Set the text to the following format: "\0<user>\0<password>",
     *      or as shown in the format string: "\0%s\0%s", email, password.
     *   2. Base64 encode the text from (1).
     *   3. Send the constructed auth text from (2) to the server:
     *      "AUTH PLAIN <b64><CR><NL>".
     *
     * @param[in] smtp SMTP client context.
     * @param[in] user SMTP account user name.
     * @param[in] pass SMTP account password.
     * @retval  0 Successfully authenticated.
     * @retval -1 Failed to authenticate.
     */
    static int
    smtp_auth_plain(struct smtp *const smtp,
                    const char *const user,
                    const char *const pass) {
      size_t user_len;
      size_t pass_len;
      char *login_str;
      size_t login_len;
      char *login_b64;
      size_t login_b64_len;
      char *login_send;
      char *concat;
    
      /* (1) */
      user_len = strlen(user);
      pass_len = strlen(pass);
      /* login_len = 1 + user_len + 1 + pass_len */
      if (smtp_si_add_size_t(user_len, pass_len, &login_len) ||
         smtp_si_add_size_t(login_len, 2, &login_len) ||
         (login_str = malloc(login_len)) == NULL) {
        return -1;
      }
      login_str[0] = '\0';
      memcpy(&login_str[1], user, user_len);
      login_str[1 + user_len] = '\0';
      memcpy(&login_str[1 + user_len + 1], pass, pass_len);
    
      /* (2) */
      login_b64 = smtp_base64_encode(login_str, login_len);
      free(login_str);
      if (login_b64 == NULL) {
        return -1;
      }
    
      /* (3) */
      login_b64_len = strlen(login_b64);
      if (smtp_si_add_size_t(login_b64_len, 14, &login_b64_len) ||
         (login_send = malloc(login_b64_len)) == NULL) {
        free(login_b64);
        return -1;
      }
      concat = smtp_stpcpy(login_send, "AUTH PLAIN ");
      concat = smtp_stpcpy(concat, login_b64);
      smtp_stpcpy(concat, "\r\n");
    
      free(login_b64);
      smtp_puts(smtp, login_send);
      free(login_send);
      if (smtp->status_code != SMTP_STATUS_OK) {
        return -1;
      }
    
      if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS) {
        return -1;
      }
      return 0;
    }
    
    /**
     * Authenticate using the LOGIN method.
     *
     *   1. Base64 encode the user name.
     *   2. Send string from (1) as part of the login:
     *      "AUTH LOGIN <b64_username><CR><NL>".
     *   3. Base64 encode the password and send that by itself on a separate
     *      line: "<b64_password><CR><NL>".
     *
     * @param[in] smtp SMTP client context.
     * @param[in] user SMTP account user name.
     * @param[in] pass SMTP account password.
     * @retval  0 Successfully authenticated.
     * @retval -1 Failed to authenticate.
     */
    static int
    smtp_auth_login(struct smtp *const smtp,
                    const char *const user,
                    const char *const pass) {
      char *b64_user;
      char *b64_pass;
      size_t b64_user_len;
      char *login_str;
      char *concat;
    
      /* (1) */
      if ((b64_user = smtp_base64_encode(user, SIZE_MAX)) == NULL) {
        return -1;
      }
    
      /* (2) */
      b64_user_len = strlen(b64_user);
      if (smtp_si_add_size_t(b64_user_len, 14, &b64_user_len) ||
         (login_str = malloc(b64_user_len)) == NULL) {
        free(b64_user);
        return -1;
      }
      concat = smtp_stpcpy(login_str, "AUTH LOGIN ");
      concat = smtp_stpcpy(concat, b64_user);
      smtp_stpcpy(concat, "\r\n");
    
      free(b64_user);
      smtp_puts(smtp, login_str);
      free(login_str);
      if (smtp->status_code != SMTP_STATUS_OK) {
         return -1;
       }
    
      if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_CONTINUE) {
        return -1;
      }
    
      /* (3) */
      if ((b64_pass = smtp_base64_encode(pass, SIZE_MAX)) == NULL) {
        return -1;
      }
      smtp_puts_terminate(smtp, b64_pass);
      free(b64_pass);
      if (smtp->status_code != SMTP_STATUS_OK) {
        return -1;
      }
    
      if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS) {
        return -1;
      }
      return 0;
    }
    
    #ifdef SMTP_OPENSSL
    /**
     * Authenticate using the CRAM-MD5 method.
     *
     *   1. Send "AUTH CRAM-MD5<CR><NL>" to the server.
     *   2. Decode the base64 challenge response from the server.
     *   3. Do an MD5 HMAC on (2) using the account password as the key.
     *   4. Convert the binary data in (3) to lowercase hex characters.
     *   5. Construct the string: "<user> <(4)>".
     *   6. Encode (5) into base64 format.
     *   7. Send the final string from (6) to the server and check the response.
     *
     * @param[in] smtp SMTP client context.
     * @param[in] user SMTP account user name.
     * @param[in] pass SMTP account password.
     * @retval  0 Successfully authenticated.
     * @retval -1 Failed to authenticate.
     */
    static int
    smtp_auth_cram_md5(struct smtp *const smtp,
                       const char *const user,
                       const char *const pass) {
      struct smtp_command cmd;
      unsigned char *challenge_decoded;
      size_t challenge_decoded_len;
      const char *key;
      int key_len;
      unsigned char hmac[EVP_MAX_MD_SIZE];
      unsigned int hmac_len;
      unsigned char *hmac_ret;
      char *challenge_hex;
      size_t user_len;
      size_t challenge_hex_len;
      char *auth_concat;
      char *concat;
      size_t auth_concat_len;
      char *b64_auth;
    
      /* (1) */
      if (smtp_puts(smtp, "AUTH CRAM-MD5\r\n") != SMTP_STATUS_OK) {
        return -1;
      }
      if (smtp_getline(smtp) == STRING_GETDELIMFD_ERROR) {
        return -1;
      }
      if (smtp_parse_cmd_line(smtp->gdfd.line, &cmd) != SMTP_AUTH_CONTINUE) {
        return -1;
      }
    
      /* (2) */
      challenge_decoded_len = smtp_base64_decode(cmd.text,
                                                 &challenge_decoded);
      if (challenge_decoded_len == SIZE_MAX) {
        return -1;
      }
    
      /* (3) */
      key = pass;
      key_len = (int)strlen(pass);/*********************cast*/
      hmac_ret = HMAC(EVP_md5(),
                      key,
                      key_len,
                      challenge_decoded,
                      challenge_decoded_len,
                      hmac,
                      &hmac_len);
      free(challenge_decoded);
      if (hmac_ret == NULL) {
        return -1;
      }
    
      /* (4) */
      challenge_hex = smtp_bin2hex(hmac, hmac_len);
      if (challenge_hex == NULL) {
        return -1;
      }
    
      /* (5) */
      user_len = strlen(user);
      challenge_hex_len = strlen(challenge_hex);
      /* auth_concat_len = user_len + 1 + challenge_hex_len + 1 */
      if (smtp_si_add_size_t(user_len, challenge_hex_len, &auth_concat_len) ||
         smtp_si_add_size_t(auth_concat_len, 2, &auth_concat_len) ||
         (auth_concat = malloc(auth_concat_len)) == NULL) {
        free(challenge_hex);
        return -1;
      }
      concat = smtp_stpcpy(auth_concat, user);
      concat = smtp_stpcpy(concat, " ");
      smtp_stpcpy(concat, challenge_hex);
      free(challenge_hex);
    
      /* (6) */
      b64_auth = smtp_base64_encode(auth_concat, auth_concat_len - 1);
      free(auth_concat);
      if (b64_auth == NULL) {
        return -1;
      }
    
      /* (7) */
      smtp_puts_terminate(smtp, b64_auth);
      free(b64_auth);
      if (smtp->status_code != SMTP_STATUS_OK) {
        return -1;
      }
    
      if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS) {
        return -1;
      }
      return 0;
    }
    #endif /* SMTP_OPENSSL */
    
    /**
     * Set the timeout for the next socket read operation.
     *
     * @param[in] smtp    SMTP client context.
     * @param[in] seconds Timeout in seconds.
     */
    static void
    smtp_set_read_timeout(struct smtp *const smtp,
                     long seconds) {
      smtp->timeout_sec = seconds;
    }
    
    /**
     * Perform a handshake with the SMTP server which includes optionally
     * setting up TLS and sending the EHLO greeting.
     *
     * At this point, the client has already connected to the SMTP server
     * through its socket connection. In this function, the client will:
     *   1. Optionally convert the connection to TLS (SMTP_SECURITY_TLS).
     *   2. Read the initial server greeting.
     *   3. Send an EHLO to the server.
     *   4. Optionally initiate STARTTLS and resend the EHLO
     *      (SMTP_SECURITY_STARTTLS).
     *
     * @param[in] smtp                SMTP client context.
     * @param[in] server              Server name or IP address.
     * @param[in] connection_security See @ref smtp_connection_security.
     * @return See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_initiate_handshake(struct smtp *const smtp,
                            const char *const server,
                            enum smtp_connection_security connection_security) {
      /* Eliminate unused warnings if not using SMTP_OPENSSL. */
      (void)server;
      (void)connection_security;
    
      /* (1) */
    #ifdef SMTP_OPENSSL
      if (connection_security == SMTP_SECURITY_TLS) {
        if (smtp_tls_init(smtp, server) < 0) {
          return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE);
        }
      }
    #endif /* SMTP_OPENSSL */
    
      /* (2) */
      /* Get initial 220 message - 5 minute timeout. */
      smtp_set_read_timeout(smtp, 60 * 5);
      if (smtp_getline(smtp) == STRING_GETDELIMFD_ERROR) {
        return smtp->status_code;
      }
    
      /* (3) */
      if (smtp_ehlo(smtp) != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      /* (4) */
    #ifdef SMTP_OPENSSL
      if (connection_security == SMTP_SECURITY_STARTTLS) {
        if (smtp_puts(smtp, "STARTTLS\r\n") != SMTP_STATUS_OK) {
          return smtp->status_code;
        }
        if (smtp_read_and_parse_code(smtp) != SMTP_READY) {
          return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE);
        }
        if (smtp_tls_init(smtp, server) < 0) {
          return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE);
        }
        if (smtp_ehlo(smtp) != SMTP_STATUS_OK) {
          return smtp->status_code;
        }
      }
    #endif /* SMTP_OPENSSL */
    
      return smtp->status_code;
    }
    
    /**
       Maximum size of an RFC 2822 date string.
    
       @verbatim
       Thu, 21 May 1998 05:33:29 -0700
       12345678901234567890123456789012
               10        20        30 32 (bytes)
       @endverbatim
    
       Add more bytes to the 32 maximum size to silence compiler warning on the
       computed UTF offset.
     */
    #define SMTP_DATE_MAX_SZ (32 + 15)
    
    /**
     * Convert the time into an RFC 2822 formatted string.
     *
     * Example date format:
     * Thu, 21 May 1998 05:33:29 -0700
     *
     * @param[out] date Buffer that has at least SMTP_DATE_MAX_SZ bytes.
     * @retval  0 Successfully copied the current date to the buffer.
     * @retval -1 Failed to establish the current date or an output
     *            format error occurred.
     */
    int
    smtp_date_rfc_2822(char *const date) {
      time_t t;
      time_t t_local;
      time_t t_utc;
      struct tm tm_local;
      struct tm tm_utc;
      long offset_utc;
      double diff_local_utc;
      int rc;
    
      const char weekday_abbreviation[7][4] = {
        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
      };
    
      const char month_abbreviation[12][4] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
      };
    
      if ((t = time(NULL)) == (time_t)(-1)) {
        return -1;
      }
    
    #ifdef SMTP_IS_WINDOWS
      if (localtime_s(&tm_local, &t) ||
         gmtime_s(&tm_utc, &t)) {
        return -1;
      }
    #else /* POSIX */
      /* Not defined if system does not have localtime_r or gmtime_r. */
    # ifdef SMTP_TIME_NO_REENTRANT
      struct tm *tm;
    
      /* localtime() not thread-safe. */
      if ((tm = localtime(&t)) == NULL) {
        return -1;
      }
      memcpy(&tm_local, tm, sizeof(tm_local));
    
      /* gmtime() not thread-safe. */
      if ((tm = gmtime(&t)) == NULL) {
        return -1;
      }
      memcpy(&tm_utc, tm, sizeof(tm_utc));
    # else /* Reentrant versions: localtime_r() and gmtime_r(). */
      if (localtime_r(&t, &tm_local) == NULL ||
         gmtime_r(&t, &tm_utc) == NULL) {
        return -1;
      }
    # endif /* SMTP_TIME_NO_REENTRANT */
    #endif /* SMTP_IS_WINDOWS */
    
      if ((t_local = mktime(&tm_local)) == (time_t)(-1)) {
        return -1;
      }
    
      if ((t_utc = mktime(&tm_utc)) == (time_t)(-1)) {
        return -1;
      }
    
      /*
       * After computing the offset, it will contain a maximum of 4 digits.
       * For example, PST time zone will have an offset of -800 which will get
       * formatted as -0800 in the sprintf call below.
       */
      diff_local_utc = difftime(t_local, t_utc);
      offset_utc = (long)diff_local_utc;
      offset_utc = offset_utc / 60 / 60 * 100;
    
      rc = sprintf(date,
                   "%s, %02d %s %d %02d:%02d:%02d %0+5ld",
                   weekday_abbreviation[tm_local.tm_wday],
                   tm_local.tm_mday,
                   month_abbreviation[tm_local.tm_mon],
                   tm_local.tm_year + 1900,
                   tm_local.tm_hour,
                   tm_local.tm_min,
                   tm_local.tm_sec, /* 0 - 60 (leap second) */
                   offset_utc);
    
      if (rc + 1 != SMTP_DATE_MAX_SZ - 15) { /* See @ref SMTP_DATE_MAX_SZ for -5. */
        return -1;
      }
    
      return 0;
    }
    
    /**
     * Search function used by bsearch, allowing the caller to check for
     * headers with existing keys.
     *
     * @param v1 String to search for in the list.
     * @param v2 The @ref smtp_header to compare.
     * @retval  0 If the keys match.
     * @retval !0 If the keys do not match.
     */
    static int
    smtp_header_cmp_key(const void *const v1,
                        const void *const v2) {
      const char *key;
      const struct smtp_header *header2;
    
      key = v1;
      header2 = v2;
      return strcmp(key, header2->key);
    }
    
    /**
     * Determine if the header key has already been defined in this context.
     *
     * @param[in] smtp SMTP client context.
     * @param[in] key  Header key value to search for.
     * @retval 1 If the header already exists in this context.
     * @retval 0 If the header does not exist in this context.
     */
    static int
    smtp_header_exists(const struct smtp *const smtp,
                       const char *const key) {
      if (bsearch(key,
                 smtp->header_list,
                 smtp->num_headers,
                 sizeof(*smtp->header_list),
                 smtp_header_cmp_key) == NULL) {
        return 0;
      }
      return 1;
    }
    
    /**
     * Minimum length of buffer required to hold the MIME boundary test:
     * mimeXXXXXXXXXX
     * 123456789012345
     * 1       10   15 bytes
     */
    #define SMTP_MIME_BOUNDARY_LEN 15
    
    /**
     * Generate the MIME boundary text field and store it in a user-supplied
     * buffer.
     *
     * For example:
     * mimeXXXXXXXXXX
     * where each X gets set to a pseudo-random uppercase ASCII character.
     *
     * This uses a simple pseudo-random number generator since we only care
     * about preventing accidental boundary collisions.
     *
     * @param[out] boundary Buffer that has at least SMTP_MIME_BOUNDARY_LEN bytes.
     */
    static void
    smtp_gen_mime_boundary(char *const boundary) {
      size_t i;
      unsigned int seed;
    
      seed = (unsigned int)time(NULL);
      srand(seed);
    
      strcpy(boundary, "mime");
      for(i = 4; i < SMTP_MIME_BOUNDARY_LEN - 1; i++) {
        /* Modulo bias okay since we only need to prevent accidental collision. */
        boundary[i] = rand() % 26 + 'A';
      }
      boundary[SMTP_MIME_BOUNDARY_LEN - 1] = '\0';
    }
    
    /**
     * Print the MIME header and the MIME section containing the email body.
     *
     * @param[in] smtp     SMTP client context.
     * @param[in] boundary MIME boundary text.
     * @param[in] body_dd  Email body with double dots added at the
     *                     beginning of each line.
     * @return See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_print_mime_header_and_body(struct smtp *const smtp,
                                    const char *const boundary,
                                    const char *const body_dd) {
      /* Buffer size for the static MIME text used below. */
      const size_t MIME_TEXT_BUFSZ = 1000;
      size_t data_double_dot_len;
      char *data_header_and_body;
      char *concat;
    
      data_double_dot_len = strlen(body_dd);
      if (smtp_si_add_size_t(data_double_dot_len,
                            MIME_TEXT_BUFSZ,
                            &data_double_dot_len) ||
         (data_header_and_body = malloc(data_double_dot_len)) == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
    
      concat = smtp_stpcpy(data_header_and_body,
                           "MIME-Version: 1.0\r\n"
                           "Content-Type: multipart/mixed; boundary=");
      concat = smtp_stpcpy(concat,
                           boundary);
      concat = smtp_stpcpy(concat,
                           "\r\n"
                           "\r\n"
                           "Multipart MIME message.\r\n"
                           "--");
      concat = smtp_stpcpy(concat,
                           boundary);
      concat = smtp_stpcpy(concat,
                           "\r\n"
                           "Content-Type: text/plain; charset=\"UTF-8\"\r\n"
                           "\r\n");
      concat = smtp_stpcpy(concat,
                           body_dd);
      smtp_stpcpy(concat,
                  "\r\n"
                  "\r\n");
    
      smtp_puts(smtp, data_header_and_body);
      free(data_header_and_body);
      return smtp->status_code;
    }
    
    /**
     * Print a MIME section containing an attachment.
     *
     * @param[in] smtp       SMTP client context.
     * @param[in] boundary   MIME boundary text.
     * @param[in] attachment See @ref smtp_attachment.
     * @return See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_print_mime_attachment(struct smtp *const smtp,
                               const char *const boundary,
                               const struct smtp_attachment *const attachment) {
      /* Buffer size for the static MIME text used below. */
      const size_t MIME_TEXT_BUFSZ = 1000;
      size_t name_len;
      size_t b64_data_len;
      size_t bufsz;
      char *mime_attach_text;
      char *concat;
    
      name_len = strlen(attachment->name);
      b64_data_len = strlen(attachment->b64_data);
      /*
       * bufsz = SMTP_MIME_BOUNDARY_LEN + name_len + b64_data_len + MIME_TEXT_BUFSZ
       */
      if (smtp_si_add_size_t(name_len, b64_data_len, &bufsz) ||
         smtp_si_add_size_t(bufsz,
                            SMTP_MIME_BOUNDARY_LEN + MIME_TEXT_BUFSZ,
                            &bufsz) ||
         (mime_attach_text = malloc(bufsz)) == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
    
      concat = smtp_stpcpy(mime_attach_text,
                           "--");
      concat = smtp_stpcpy(concat,
                           boundary);
      concat = smtp_stpcpy(concat,
                           "\r\n"
                           "Content-Type: application/octet-stream\r\n"
                           "Content-Disposition: attachment; filename=\"");
      concat = smtp_stpcpy(concat,
                           attachment->name);
      concat = smtp_stpcpy(concat,
                           "\"\r\n"
                           "Content-Transfer-Encoding: base64\r\n"
                           "\r\n");
      concat = smtp_stpcpy(concat,
                           attachment->b64_data);
      smtp_stpcpy(concat,
                  "\r\n"
                  "\r\n");
      smtp_puts(smtp, mime_attach_text);
      free(mime_attach_text);
      return smtp->status_code;
    }
    
    /**
     * Prints double hyphen on both sides of the MIME boundary which indicates
     * the end of the MIME sections.
     *
     * @param[in] smtp     SMTP client context.
     * @param[in] boundary MIME boundary text.
     * @return See @ref smtp_status_code and @ref smtp_puts.
     */
    static enum smtp_status_code
    smtp_print_mime_end(struct smtp *const smtp,
                        const char *const boundary) {
      char *concat;
      char mime_end[2 + SMTP_MIME_BOUNDARY_LEN + 4 + 1];
    
      concat = smtp_stpcpy(mime_end, "--");
      concat = smtp_stpcpy(concat, boundary);
      smtp_stpcpy(concat, "--\r\n");
      return smtp_puts(smtp, mime_end);
    }
    
    /**
     * Send the main email body to the SMTP server.
     *
     * This includes the MIME sections for the email body and attachments.
     *
     * @param[in] smtp     SMTP client context.
     * @param[in] body_dd  Email body with double dots added at the
     *                     beginning of each line.
     * @return See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_print_mime_email(struct smtp *const smtp,
                          const char *const body_dd) {
      char boundary[SMTP_MIME_BOUNDARY_LEN];
      size_t i;
      struct smtp_attachment *attachment;
    
      smtp_gen_mime_boundary(boundary);
    
      if (smtp_print_mime_header_and_body(smtp, boundary, body_dd) != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      for(i = 0; i < smtp->num_attachment; i++) {
        attachment = &smtp->attachment_list[i];
        if (smtp_print_mime_attachment(smtp,
                                      boundary,
                                      attachment) != SMTP_STATUS_OK) {
          return smtp->status_code;
        }
      }
    
      return smtp_print_mime_end(smtp, boundary);
    }
    
    /**
     * Print the email data provided by the user without MIME formatting.
     *
     * @param[in,out] smtp     SMTP client context.
     * @param[in]     body_dd  Email body with double dots added at the
     *                         beginning of each line.
     * @return                 See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_print_nomime_email(struct smtp *const smtp,
                            const char *const body_dd) {
      return smtp_puts_terminate(smtp, body_dd);
    }
    
    /**
     * Send the email body to the mail server.
     *
     * @param[in,out] smtp SMTP client context.
     * @param[in]     body Email body text.
     * @return             See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_print_email(struct smtp *const smtp,
                     const char *const body) {
      char *body_double_dot;
    
      /*
       * Insert an extra dot for each line that begins with a dot. This will
       * prevent data in the body parameter from prematurely ending the DATA
       * segment.
       */
      if ((body_double_dot = smtp_str_replace("\n.", "\n..", body)) == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
    
      if (smtp_header_exists(smtp, "Content-Type")) {
        smtp_print_nomime_email(smtp, body_double_dot);
      }
      else{
        smtp_print_mime_email(smtp, body_double_dot);
      }
      free(body_double_dot);
      return smtp->status_code;
    }
    
    /**
     * Convert a header into an RFC 5322 formatted string and send it to the
     * SMTP server.
     *
     * This will adding proper line wrapping and indentation for long
     * header lines.
     *
     * @param[in] smtp   SMTP client context.
     * @param[in] header See @ref smtp_header.
     * @return See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_print_header(struct smtp *const smtp,
                      const struct smtp_header *const header) {
      size_t key_len;
      size_t value_len;
      size_t concat_len;
      char *header_concat;
      char *concat;
      char *header_fmt;
    
      if (header->value == NULL) {
        return smtp->status_code;
      }
    
      key_len = strlen(header->key);
      value_len = strlen(header->value);
    
      /* concat_len = key_len + 2 + value_len + 1 */
      if (smtp_si_add_size_t(key_len, value_len, &concat_len) ||
         smtp_si_add_size_t(concat_len, 3, &concat_len) ||
         (header_concat = malloc(concat_len)) == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
      concat = smtp_stpcpy(header_concat, header->key);
      concat = smtp_stpcpy(concat, ": ");
      smtp_stpcpy(concat, header->value);
    
      header_fmt = smtp_fold_whitespace(header_concat, SMTP_LINE_MAX);
      free(header_concat);
      if (header_fmt == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
    
      smtp_puts_terminate(smtp, header_fmt);
      free(header_fmt);
      return smtp->status_code;
    }
    
    /**
     * Take a FROM, TO, and CC address and add it into the email header list.
     *
     * The following example shows what the final header might look like when
     * the client sends an email to two CC addresses:
     * Cc: mail1\@example.com, mail2\@example.com
     *
     * @param[in] smtp         SMTP client context.
     * @param[in] address_type See @ref smtp_address_type.
     * @param[in] key          Header key value, for example, To From Cc.
     * @return See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_append_address_to_header(struct smtp *const smtp,
                                  enum smtp_address_type address_type,
                                  const char *const key) {
      size_t i;
      size_t num_address_in_header;
      size_t header_value_sz;
      size_t name_slen;
      size_t email_slen;
      size_t concat_len;
      struct smtp_address *address;
      char *header_value;
      char *header_value_new;
      char *concat;
    
      num_address_in_header = 0;
      header_value_sz = 0;
      header_value = NULL;
      concat_len = 0;
      for(i = 0; i < smtp->num_address; i++) {
        address = &smtp->address_list[i];
        if (address->type == address_type) {
          name_slen = 0;
          if (address->name) {
            name_slen = strlen(address->name);
          }
          email_slen = strlen(address->email);
    
          /*
           *                   ', "'      NAME     '" <'      EMAIL     >  \0
           * header_value_sz +=  3  +  name_slen  +  3  +  email_slen + 1 + 1
           */
          if (smtp_si_add_size_t(header_value_sz, name_slen, &header_value_sz) ||
             smtp_si_add_size_t(header_value_sz, email_slen, &header_value_sz) ||
             smtp_si_add_size_t(header_value_sz, 3 + 3 + 1 + 1, &header_value_sz)||
             (header_value_new = realloc(header_value,
                                         header_value_sz)) == NULL) {
            free(header_value);
            return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
          }
          header_value = header_value_new;
          concat = header_value + concat_len;
          if (num_address_in_header > 0) {
            concat = smtp_stpcpy(concat, ", ");
          }
    
          if (name_slen) {
            concat = smtp_stpcpy(concat, "\"");
            concat = smtp_stpcpy(concat, address->name);
            concat = smtp_stpcpy(concat, "\" ");
          }
          concat = smtp_stpcpy(concat, "<");
          concat = smtp_stpcpy(concat, address->email);
          concat = smtp_stpcpy(concat, ">");
          num_address_in_header += 1;
          concat_len = (size_t)(concat - header_value);
        }
      }
    
      if (header_value) {
        smtp_header_add(smtp, key, header_value);
        free(header_value);
      }
      return smtp->status_code;
    }
    
    /**
     * Send envelope MAIL FROM or RCPT TO header address.
     *
     * Examples:
     * MAIL FROM:<mail\@example.com>
     * RCPT TO:<mail\@example.com>
     *
     * @param[in] smtp    SMTP client context.
     * @param[in] header  Either "MAIL FROM" or "RCPT TO".
     * @param[in] address See @ref smtp_address -> email field.
     * @return See @ref smtp_status_code.
     */
    static enum smtp_status_code
    smtp_mail_envelope_header(struct smtp *const smtp,
                              const char *const header,
                              const struct smtp_address *const address) {
      const char *const SMTPUTF8 = " SMTPUTF8";
      const size_t SMTPUTF8_LEN = strlen(SMTPUTF8);
      size_t email_len;
      size_t bufsz;
      char *envelope_address;
      char *concat;
      const char *smtputf8_opt;
    
      email_len = strlen(address->email);
    
      /* bufsz = 14 + email_len + SMTPUTF8_LEN + 1 */
      if (smtp_si_add_size_t(email_len, SMTPUTF8_LEN + 14 + 1, &bufsz) ||
         (envelope_address = malloc(bufsz)) == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
    
      smtputf8_opt = "";
      if (smtp_str_has_nonascii_utf8(address->email)) {
        smtputf8_opt = SMTPUTF8;
      }
    
      concat = smtp_stpcpy(envelope_address, header);
      concat = smtp_stpcpy(concat, ":<");
      concat = smtp_stpcpy(concat, address->email);
      concat = smtp_stpcpy(concat, ">");
      concat = smtp_stpcpy(concat, smtputf8_opt);
      smtp_stpcpy(concat, "\r\n");
      smtp_puts(smtp, envelope_address);
      free(envelope_address);
    
      if (smtp->status_code != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
      smtp_read_and_parse_code(smtp);
      return smtp->status_code;
    }
    
    /**
     * Comparison function for qsort which sorts headers alphabetically based
     * on the key.
     *
     * @param[in] v1 The first @ref smtp_header to compare.
     * @param[in] v2 The second @ref smtp_header to compare.
     * @retval  0 If the keys match.
     * @retval !0 If the keys do not match.
     */
    static int
    smtp_header_cmp(const void *v1,
                    const void *v2) {
      const struct smtp_header *header1;
      const struct smtp_header *header2;
    
      header1 = v1;
      header2 = v2;
      return strcmp(header1->key, header2->key);
    }
    
    /**
     * Validate characters in the email header key.
     *
     * Must consist only of printable US-ASCII characters except colon.
     *
     * @param[in] key Header key to validate.
     * @retval  0 Successful validation.
     * @retval -1 Failed to validate.
     */
    int
    smtp_header_key_validate(const char *const key) {
      unsigned char uc;
      size_t i;
      size_t keylen;
    
      keylen = strlen(key);
      if (keylen < 1) {
        return -1;
      }
    
      for(i = 0; i < keylen; i++) {
        uc = (unsigned char)key[i];
        if (uc <= ' ' || uc > 126 || uc == ':') {
          return -1;
        }
      }
    
      return 0;
    }
    
    /**
     * Validate characters in the email header contents.
     *
     * Must consist only of printable character, space, or horizontal tab.
     *
     * @param[in] value Header value to validate.
     * @retval  0 Successful validation.
     * @retval -1 Failed to validate.
     */
    int
    smtp_header_value_validate(const char *const value) {
      size_t i;
      unsigned char uc;
    
      for(i = 0; value[i]; i++) {
        uc = (unsigned char)value[i];
        if ((uc < ' ' || uc > 126) &&
            uc != '\t' &&
            uc < 0x80) { /* Allow UTF-8 byte sequence. */
          return -1;
        }
      }
      return 0;
    }
    
    /**
     * Validate characters in the email address.
     *
     * The email address must consist only of printable characters excluding
     * the angle brackets (<) and (>).
     *
     * @param[in] email The email address of the party.
     * @retval  0 Successful validation.
     * @retval -1 Failed to validate.
     */
    int
    smtp_address_validate_email(const char *const email) {
      size_t i;
      unsigned char uc;
    
      for(i = 0; email[i]; i++) {
        uc = (unsigned char)email[i];
        if (uc <= ' ' || uc == 127 ||
           uc == '<' || uc == '>') {
          return -1;
        }
      }
      return 0;
    }
    
    /**
     * Validate characters in the email name.
     *
     * Email user name must consist only of printable characters, excluding the
     * double quote character.
     *
     * @param[in] name Email name to validate.
     * @retval  0 Successful validation.
     * @retval -1 Failed to validate.
     */
    int
    smtp_address_validate_name(const char *const name) {
      size_t i;
      unsigned char uc;
    
      for(i = 0; name[i]; i++) {
        uc = (unsigned char)name[i];
        if (uc < ' ' || uc == 127 || uc == '\"') {
          return -1;
        }
      }
      return 0;
    }
    
    /**
     * Validate characters in the attachment file name.
     *
     * Must consist only of printable characters or the space character ( ), and
     * excluding the quote characters (') and (").
     *
     * @param[in] name Filename of the attachment shown to recipients.
     * @retval  0 Successful validation.
     * @retval -1 Failed to validate.
     */
    int
    smtp_attachment_validate_name(const char *const name) {
      size_t i;
      unsigned char uc;
    
      for(i = 0; name[i]; i++) {
        uc = (unsigned char)name[i];
        if (uc < ' ' || uc == 127 ||
           uc == '\'' || uc == '\"') {
          return -1;
        }
      }
      return 0;
    }
    
    /**
     * Special flag value for the SMTP context used to determine if the initial
     * memory allocation failed to create the context.
     */
    #define SMTP_FLAG_INVALID_MEMORY (enum smtp_flag)(0xFFFFFFFF)
    
    /**
     * This error structure used for the single error case where we cannot
     * initially allocate memory. This makes it easier to propagate any
     * error codes when calling the other header functions because the
     * caller will always get a valid SMTP structure returned.
     */
    static struct smtp
    g_smtp_error = {
      SMTP_FLAG_INVALID_MEMORY, /* flags                        */
      0,                        /* sock                         */
      {                         /* gdfd                         */
        NULL,                   /* _buf                         */
        0,                      /* _bufsz                       */
        0,                      /* _buf_len                     */
        NULL,                   /* line                         */
        0,                      /* line_len                     */
        NULL,                   /* getdelimfd_read              */
        NULL,                   /* user_data                    */
        0,                      /* delim                        */
        {0}                     /* pad                          */
      },                        /* gdfd                         */
      NULL,                     /* header_list                  */
      0,                        /* num_headers                  */
      NULL,                     /* address_list                 */
      0,                        /* num_address                  */
      NULL,                     /* attachment_list              */
      0,                        /* num_attachment               */
      0,                        /* timeout_sec                  */
      SMTP_STATUS_NOMEM,        /* smtp_status_code status_code */
      0,                        /* tls_on                       */
      NULL                      /* cafile                       */
    #ifdef SMTP_OPENSSL
      ,
      NULL,                     /* tls                          */
      NULL,                     /* tls_ctx                      */
      NULL                      /* tls_bio                      */
    #endif /* SMTP_OPENSSL */
    };
    
    enum smtp_status_code smtp_open
    (const char *const server, const char *const port,
     enum smtp_connection_security connection_security,
     enum smtp_flag flags, const char *const cafile,
     struct smtp **smtp)
    {
      struct smtp *snew;
    
      if ((snew = calloc(1, sizeof(**smtp))) == NULL) {
        *smtp = &g_smtp_error;
        return smtp_status_code_get(*smtp);
      }
      *smtp = snew;
    
      snew->flags                = flags;
      snew->sock                 = -1;
      snew->gdfd.delim           = '\n';
      snew->gdfd.getdelimfd_read = smtp_str_getdelimfd_read;
      snew->gdfd.user_data       = snew;
      snew->cafile               = cafile;
    
    #ifndef SMTP_IS_WINDOWS
      signal(SIGPIPE, SIG_IGN);
    #endif /* !(SMTP_IS_WINDOWS) */
    
      if (smtp_connect(snew, server, port) < 0) {
        return smtp_status_code_set(*smtp, SMTP_STATUS_CONNECT);
      }
    
      if (smtp_initiate_handshake(snew,
                                 server,
                                 connection_security) != SMTP_STATUS_OK) {
        return smtp_status_code_set(*smtp, SMTP_STATUS_HANDSHAKE);
      }
    
      return snew->status_code;
    }
    
    enum smtp_status_code
    smtp_auth(struct smtp *const smtp,
              enum smtp_authentication_method auth_method,
              const char *const user,
              const char *const pass) {
      int auth_rc;
    
      if (smtp->status_code != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      if (auth_method == SMTP_AUTH_PLAIN) {
        auth_rc = smtp_auth_plain(smtp, user, pass);
      }
      else if (auth_method == SMTP_AUTH_LOGIN) {
        auth_rc = smtp_auth_login(smtp, user, pass);
      }
    #ifdef SMTP_OPENSSL
      else if (auth_method == SMTP_AUTH_CRAM_MD5) {
        auth_rc = smtp_auth_cram_md5(smtp, user, pass);
      }
    #endif /* SMTP_OPENSSL */
      else if (auth_method == SMTP_AUTH_NONE) {
        auth_rc = 0;
      }
      else{
        return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
      }
    
      if (auth_rc < 0) {
        return smtp_status_code_set(smtp, SMTP_STATUS_AUTH);
      }
    
      return smtp->status_code;
    }
    
    enum smtp_status_code
    smtp_mail(struct smtp *const smtp,
              const char *const body) {
      size_t i;
      int has_mail_from;
      struct smtp_address *address;
      char date[SMTP_DATE_MAX_SZ];
    
      if (smtp->status_code != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      /* MAIL timeout 5 minutes. */
      smtp_set_read_timeout(smtp, 60 * 5);
      has_mail_from = 0;
      for(i = 0; i < smtp->num_address; i++) {
        address = &smtp->address_list[i];
        if (address->type == SMTP_ADDRESS_FROM) {
          if (smtp_mail_envelope_header(smtp,
                                       "MAIL FROM",
                                       address) != SMTP_STATUS_OK) {
            return smtp->status_code;
          }
          has_mail_from = 1;
          break;
        }
      }
    
      if (!has_mail_from) {
        return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
      }
    
      /* RCPT timeout 5 minutes. */
      smtp_set_read_timeout(smtp, 60 * 5);
    
      for(i = 0; i < smtp->num_address; i++) {
        address = &smtp->address_list[i];
        if (address->type != SMTP_ADDRESS_FROM) {
          if (smtp_mail_envelope_header(smtp,
                                       "RCPT TO",
                                       address) != SMTP_STATUS_OK) {
            return smtp->status_code;
          }
        }
      }
    
      /* DATA timeout 2 minutes. */
      smtp_set_read_timeout(smtp, 60 * 2);
    
      if (smtp_puts(smtp, "DATA\r\n") != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      /* 354 response to DATA must get returned before we can send the message. */
      if (smtp_read_and_parse_code(smtp) != SMTP_BEGIN_MAIL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_SERVER_RESPONSE);
      }
    
      if (!smtp_header_exists(smtp, "Date")) {
        if (smtp_date_rfc_2822(date) < 0) {
          return smtp_status_code_set(smtp, SMTP_STATUS_DATE);
        }
        if (smtp_header_add(smtp, "Date", date) != SMTP_STATUS_OK) {
          return smtp->status_code;
        }
      }
    
      /* DATA block timeout 3 minutes. */
      smtp_set_read_timeout(smtp, 60 * 3);
    
      if (smtp_append_address_to_header(smtp,
                                       SMTP_ADDRESS_FROM,
                                       "From") != SMTP_STATUS_OK ||
         smtp_append_address_to_header(smtp,
                                       SMTP_ADDRESS_TO,
                                       "To") != SMTP_STATUS_OK ||
         smtp_append_address_to_header(smtp,
                                       SMTP_ADDRESS_CC,
                                       "Cc") != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      for(i = 0; i < smtp->num_headers; i++) {
        if (smtp_print_header(smtp, &smtp->header_list[i]) != SMTP_STATUS_OK) {
          return smtp->status_code;
        }
      }
    
      if (smtp_print_email(smtp, body)) {
        return smtp->status_code;
      }
    
      /* End of DATA segment. */
      if (smtp_puts(smtp, ".\r\n") != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      /* DATA termination timeout 250 return code - 10 minutes. */
      smtp_set_read_timeout(smtp, 60 * 10);
      if (smtp_read_and_parse_code(smtp) != SMTP_DONE) {
        return smtp_status_code_set(smtp, SMTP_STATUS_SERVER_RESPONSE);
      }
    
      return smtp->status_code;
    }
    
    enum smtp_status_code
    smtp_close(struct smtp *smtp) {
      enum smtp_status_code status_code;
    
      status_code = smtp->status_code;
    
      if (smtp->flags == SMTP_FLAG_INVALID_MEMORY) {
        return status_code;
      }
    
      if (smtp->sock != -1) {
        /*
         * Do not error out if this fails because we still need to free the
         * SMTP client resources.
         */
        smtp->status_code = SMTP_STATUS_OK;
        smtp_puts(smtp, "QUIT\r\n");
    
    #ifdef SMTP_OPENSSL
        if (smtp->tls_on) {
          SSL_free(smtp->tls);
          SSL_CTX_free(smtp->tls_ctx);
        }
    #endif /* SMTP_OPENSSL */
    
    #ifdef SMTP_IS_WINDOWS
        closesocket(smtp->sock);
        WSACleanup();
    #else /* POSIX */
        if (close(smtp->sock) < 0) {
          if (smtp->status_code == SMTP_STATUS_OK) {
            smtp_status_code_set(smtp, SMTP_STATUS_CLOSE);
          }
        }
    #endif /* SMTP_IS_WINDOWS */
      }
    
      smtp_str_getdelimfd_free(&smtp->gdfd);
      smtp_header_clear_all(smtp);
      smtp_address_clear_all(smtp);
      smtp_attachment_clear_all(smtp);
      if (status_code == SMTP_STATUS_OK) {
        status_code = smtp->status_code;
      }
      free(smtp);
    
      return status_code;
    }
    
    enum smtp_status_code
    smtp_status_code_get(const struct smtp *const smtp) {
      return smtp->status_code;
    }
    
    enum smtp_status_code
    smtp_status_code_clear(struct smtp *const smtp) {
      enum smtp_status_code old_status;
    
      old_status = smtp_status_code_get(smtp);
      smtp_status_code_set(smtp, SMTP_STATUS_OK);
      return old_status;
    }
    
    enum smtp_status_code
    smtp_status_code_set(struct smtp *const smtp,
                         enum smtp_status_code status_code) {
      if ((unsigned)status_code >= SMTP_STATUS__LAST) {
        return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
      }
      smtp->status_code = status_code;
      return status_code;
    }
    
    const char *
    smtp_status_code_errstr(enum smtp_status_code status_code) {
      const char *const status_code_err_str[] = {
        /* SMTP_STATUS_OK */
        "Success",
        /* SMTP_STATUS_NOMEM */
        "Memory allocation failed",
        /* SMTP_STATUS_CONNECT */
        "Failed to connect to the mail server",
        /* SMTP_STATUS_HANDSHAKE */
        "Failed to handshake or negotiate a TLS connection with the server",
        /* SMTP_STATUS_AUTH */
        "Failed to authenticate with the given credentials",
        /* SMTP_STATUS_SEND */
        "Failed to send bytes to the server",
        /* SMTP_STATUS_RECV */
        "Failed to receive bytes from the server",
        /* SMTP_STATUS_CLOSE */
        "Failed to properly close a connection",
        /* SMTP_STATUS_SERVER_RESPONSE */
        "SMTP server sent back an unexpected status code",
        /* SMTP_STATUS_PARAM */
        "Invalid parameter",
        /* SMTP_STATUS_FILE */
        "Failed to read or open a local file",
        /* SMTP_STATUS_DATE */
        "Failed to get the local date and time",
        /* SMTP_STATUS__LAST */
        "Unknown error"
      };
    
      if ((unsigned)status_code > SMTP_STATUS__LAST) {
        status_code = SMTP_STATUS__LAST;
      }
      return status_code_err_str[status_code];
    }
    
    enum smtp_status_code
    smtp_header_add(struct smtp *const smtp,
                    const char *const key,
                    const char *const value) {
      struct smtp_header *new_header_list;
      struct smtp_header *new_header;
      size_t num_headers_inc;
    
      if (smtp->status_code != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      if (smtp_header_key_validate(key) < 0) {
        return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
      }
    
      if (value && smtp_header_value_validate(value) < 0) {
        return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
      }
    
      if (smtp_si_add_size_t(smtp->num_headers, 1, &num_headers_inc) ||
         (new_header_list = smtp_reallocarray(
                              smtp->header_list,
                              num_headers_inc,
                              sizeof(*smtp->header_list))) == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
      smtp->header_list = new_header_list;
      new_header = &smtp->header_list[smtp->num_headers];
    
      new_header->key = smtp_strdup(key);
      if (value) {
        new_header->value = smtp_strdup(value);
      }
      else{
        new_header->value = NULL;
      }
      if (new_header->key == NULL ||
         (new_header->value == NULL && value)) {
        free(new_header->key);
        free(new_header->value);
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
    
      smtp->num_headers = num_headers_inc;
    
      qsort(smtp->header_list,
            smtp->num_headers,
            sizeof(*smtp->header_list),
            smtp_header_cmp);
    
      return smtp->status_code;
    }
    
    void
    smtp_header_clear_all(struct smtp *const smtp) {
      size_t i;
      struct smtp_header *header;
    
      for(i = 0; i < smtp->num_headers; i++) {
        header = &smtp->header_list[i];
        free(header->key);
        free(header->value);
      }
      free(smtp->header_list);
      smtp->header_list = NULL;
      smtp->num_headers = 0;
    }
    
    enum smtp_status_code
    smtp_address_add(struct smtp *const smtp,
                     enum smtp_address_type type,
                     const char *const email,
                     const char *const name) {
      struct smtp_address *new_address_list;
      struct smtp_address *new_address;
      size_t num_address_inc;
    
      if (smtp->status_code != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      if (smtp_address_validate_email(email) < 0) {
        return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
      }
    
      if (name && smtp_address_validate_name(name) < 0) {
        return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
      }
    
      if (smtp_si_add_size_t(smtp->num_address, 1, &num_address_inc)) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
    
      new_address_list = smtp_reallocarray(smtp->address_list,
                                           num_address_inc,
                                           sizeof(*new_address_list));
      if (new_address_list == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
      new_address = &new_address_list[smtp->num_address];
    
      smtp->address_list = new_address_list;
    
      new_address->type = type;
      new_address->email = smtp_strdup(email);
      if (name) {
        new_address->name = smtp_strdup(name);
      }
      else{
        new_address->name = NULL;
      }
      if (new_address->email == NULL ||
         (new_address->name == NULL && name)) {
        free(new_address->email);
        free(new_address->name);
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
      smtp->num_address = num_address_inc;
    
      return smtp->status_code;
    }
    
    void
    smtp_address_clear_all(struct smtp *const smtp) {
      size_t i;
      struct smtp_address *address;
    
      for(i = 0; i < smtp->num_address; i++) {
        address = &smtp->address_list[i];
        free(address->email);
        free(address->name);
      }
      free(smtp->address_list);
      smtp->address_list = NULL;
      smtp->num_address = 0;
    }
    
    enum smtp_status_code
    smtp_attachment_add_path(struct smtp *const smtp,
                             const char *const name,
                             const char *const path) {
      char *data;
      size_t bytes_read;
    
      if (smtp->status_code != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      errno = 0;
      if ((data = smtp_file_get_contents(path, &bytes_read)) == NULL) {
        if (errno == ENOMEM) {
          return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
        }
        return smtp_status_code_set(smtp, SMTP_STATUS_FILE);
      }
      smtp_attachment_add_mem(smtp, name, data, bytes_read);
      free(data);
      return smtp->status_code;
    }
    
    enum smtp_status_code
    smtp_attachment_add_fp(struct smtp *const smtp,
                           const char *const name,
                           FILE *fp) {
      char *data;
      size_t bytes_read;
    
      if (smtp->status_code != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      errno = 0;
      if ((data = smtp_ffile_get_contents(fp, &bytes_read)) == NULL) {
        if (errno == ENOMEM) {
          return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
        }
        return smtp_status_code_set(smtp, SMTP_STATUS_FILE);
      }
      smtp_attachment_add_mem(smtp, name, data, bytes_read);
      free(data);
      return smtp->status_code;
    }
    
    enum smtp_status_code
    smtp_attachment_add_mem(struct smtp *const smtp,
                            const char *const name,
                            const void *const data,
                            size_t datasz) {
      size_t num_attachment_inc;
      struct smtp_attachment *new_attachment_list;
      struct smtp_attachment *new_attachment;
      char *b64_encode;
    
      if (smtp->status_code != SMTP_STATUS_OK) {
        return smtp->status_code;
      }
    
      if (smtp_attachment_validate_name(name) < 0) {
        return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
      }
    
      if (datasz == SIZE_MAX) {
        datasz = strlen(data);
      }
    
      if (smtp_si_add_size_t(smtp->num_attachment, 1, &num_attachment_inc) ||
         (new_attachment_list = smtp_reallocarray(
                                  smtp->attachment_list,
                                  num_attachment_inc,
                                  sizeof(*new_attachment_list))) == NULL) {
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
      smtp->attachment_list = new_attachment_list;
      new_attachment = &new_attachment_list[smtp->num_attachment];
    
      new_attachment->name = smtp_strdup(name);
      b64_encode = smtp_base64_encode(data, datasz);
      if (new_attachment->name == NULL || b64_encode == NULL) {
        free(new_attachment->name);
        free(b64_encode);
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
    
      new_attachment->b64_data = smtp_chunk_split(b64_encode,
                                                  SMTP_LINE_MAX,
                                                  "\r\n");
      free(b64_encode);
      if (new_attachment->b64_data == NULL) {
        free(new_attachment->name);
        return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
      }
    
      smtp->num_attachment = num_attachment_inc;
      return smtp->status_code;
    }
    
    void
    smtp_attachment_clear_all(struct smtp *const smtp) {
      size_t i;
      struct smtp_attachment *attachment;
    
      for(i = 0; i < smtp->num_attachment; i++) {
        attachment = &smtp->attachment_list[i];
        free(attachment->name);
        free(attachment->b64_data);
      }
      free(smtp->attachment_list);
      smtp->attachment_list = NULL;
      smtp->num_attachment = 0;
    }