new file mode 100644
@@ -0,0 +1,3528 @@
+/**
+ * @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)
+# 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"
+
+/*
+ * The SMTP_TEST converts some library routines into special test seams which
+ * allows the test program to control whether they fail. For example, we can
+ * control when malloc() fails under certain conditions with an out of
+ * memory condition.
+ */
+#ifdef SMTP_TEST
+/**
+ * Declare extern linkage on some functions so we can redefine their behavior
+ * in the external test suite.
+ */
+# define SMTP_LINKAGE extern
+# include "../test/seams.h"
+#else /* !(SMTP_TEST) */
+/**
+ * When not testing, all functions should have static linkage except for those
+ * in the header.
+ */
+# define SMTP_LINKAGE static
+#endif /* SMTP_TEST */
+
+/**
+ * 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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(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.
+ */
+SMTP_LINKAGE 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, ©_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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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();
+ ERR_load_BIO_strings();
+ 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);
+ if((smtp->flags & SMTP_NO_CERT_VERIFY) == 0){
+ 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((smtp->flags & SMTP_NO_CERT_VERIFY) == 0){
+ 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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.
+ */
+SMTP_LINKAGE 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;
+}
+
new file mode 100644
@@ -0,0 +1,635 @@
+/**
+ * @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.
+ */
+#ifndef SMTP_H
+#define SMTP_H
+
+#include <sys/types.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#ifndef SIZE_MAX
+/**
+ * Maximum value of size_t type.
+ */
+# define SIZE_MAX ((size_t)(-1))
+#endif /* SIZE_MAX */
+
+/**
+ * Status codes indicating success or failure from calling any of the
+ * SMTP library functions.
+ *
+ * This code gets returned by all functions in this header.
+ */
+enum smtp_status_code{
+ /**
+ * Successful operation completed.
+ */
+ SMTP_STATUS_OK = 0,
+
+ /**
+ * Memory allocation failed.
+ */
+ SMTP_STATUS_NOMEM = 1,
+
+ /**
+ * Failed to connect to the mail server.
+ */
+ SMTP_STATUS_CONNECT = 2,
+
+ /**
+ * Failed to handshake or negotiate a TLS connection with the server.
+ */
+ SMTP_STATUS_HANDSHAKE = 3,
+
+ /**
+ * Failed to authenticate with the given credentials.
+ */
+ SMTP_STATUS_AUTH = 4,
+
+ /**
+ * Failed to send bytes to the server.
+ */
+ SMTP_STATUS_SEND = 5,
+
+ /**
+ * Failed to receive bytes from the server.
+ */
+ SMTP_STATUS_RECV = 6,
+
+ /**
+ * Failed to properly close a connection.
+ */
+ SMTP_STATUS_CLOSE = 7,
+
+ /**
+ * SMTP server sent back an unexpected status code.
+ */
+ SMTP_STATUS_SERVER_RESPONSE = 8,
+
+ /**
+ * Invalid parameter.
+ */
+ SMTP_STATUS_PARAM = 9,
+
+ /**
+ * Failed to open or read a local file.
+ */
+ SMTP_STATUS_FILE = 10,
+
+ /**
+ * Failed to get the local date and time.
+ */
+ SMTP_STATUS_DATE = 11,
+
+ /**
+ * Indicates the last status code in the enumeration, useful for
+ * bounds checking.
+ *
+ * Not a valid status code.
+ */
+ SMTP_STATUS__LAST
+};
+
+/**
+ * Address source and destination types.
+ */
+enum smtp_address_type{
+ /**
+ * From address.
+ */
+ SMTP_ADDRESS_FROM = 0,
+
+ /**
+ * To address.
+ */
+ SMTP_ADDRESS_TO = 1,
+
+ /**
+ * Copy address.
+ */
+ SMTP_ADDRESS_CC = 2,
+
+ /**
+ * Blind copy address.
+ *
+ * Recipients should not see any of the BCC addresses when they receive
+ * their email. However, some SMTP server implementations may copy this
+ * information into the mail header, so do not assume that this will
+ * always get hidden. If the BCC addresses must not get shown to the
+ * receivers, then send one separate email to each BCC party and add
+ * the TO and CC addresses manually as a header property using
+ * @ref smtp_header_add instead of as an address using
+ * @ref smtp_address_add.
+ */
+ SMTP_ADDRESS_BCC = 3
+};
+
+/**
+ * Connect to the SMTP server using either an unencrypted socket or
+ * TLS encryption.
+ */
+enum smtp_connection_security{
+#ifdef SMTP_OPENSSL
+ /**
+ * First connect without encryption, then negotiate an encrypted connection
+ * by issuing a STARTTLS command.
+ *
+ * Typically used when connecting to a mail server on port 25 or 587.
+ */
+ SMTP_SECURITY_STARTTLS = 0,
+
+ /**
+ * Use TLS when initially connecting to server.
+ *
+ * Typically used when connecting to a mail server on port 465.
+ */
+ SMTP_SECURITY_TLS = 1,
+#endif /* SMTP_OPENSSL */
+
+ /**
+ * Do not use TLS encryption.
+ *
+ * Not recommended unless connecting to the SMTP server locally.
+ */
+ SMTP_SECURITY_NONE = 2
+};
+
+/**
+ * List of supported methods for authenticating a mail user account on
+ * the server.
+ */
+enum smtp_authentication_method{
+#ifdef SMTP_OPENSSL
+ /**
+ * Use HMAC-MD5.
+ */
+ SMTP_AUTH_CRAM_MD5 = 0,
+#endif /* SMTP_OPENSSL */
+ /**
+ * No authentication required.
+ *
+ * Some servers support this option if connecting locally.
+ */
+ SMTP_AUTH_NONE = 1,
+
+ /**
+ * Authenticate using base64 user and password.
+ */
+ SMTP_AUTH_PLAIN = 2,
+
+ /**
+ * Another base64 authentication method, similar to SMTP_AUTH_PLAIN.
+ */
+ SMTP_AUTH_LOGIN = 3
+};
+
+/**
+ * Special flags defining certain behaviors for the SMTP client context.
+ */
+enum smtp_flag{
+ /**
+ * Print client and server communication on stderr.
+ */
+ SMTP_DEBUG = 1 << 0,
+
+ /**
+ * Do not verify TLS certificate.
+ *
+ * By default, the TLS handshake function will check if a certificate
+ * has expired or if using a self-signed certificate. Either of those
+ * conditions will cause the connection to fail. This option allows the
+ * connection to proceed even if those checks fail.
+ */
+ SMTP_NO_CERT_VERIFY = 1 << 1
+};
+
+struct smtp;
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Open a connection to an SMTP server and return the context.
+ *
+ * After successfully connecting and performing a handshake with the
+ * SMTP server, this will return a valid SMTP client context that
+ * the application can use in the other library functions.
+ *
+ * This always returns a valid SMTP client context even if
+ * the server connection or memory allocation fails. In this scenario, the
+ * error status will continue to propagate to future library calls for
+ * the SMTP context while in this failure mode.
+ *
+ * This function will ignore the SIGPIPE signal. Applications that require a
+ * handler for that signal should set it up after calling this function.
+ *
+ * @param[in] server Server name or IP address.
+ * @param[in] port Server port number.
+ * @param[in] connection_security See @ref smtp_connection_security.
+ * @param[in] flags See @ref smtp_flag.
+ * @param[in] cafile Path to certificate file, or NULL to use
+ * certificates in the default path.
+ * @param[out] smtp Pointer to a new SMTP context. When
+ * finished, the caller must free this
+ * context using @ref smtp_close.
+ * @return See @ref smtp_status_code.
+ */
+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);
+
+/**
+ * Authenticate the user using one of the methods listed in
+ * @ref smtp_authentication_method.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] auth_method See @ref smtp_authentication_method.
+ * @param[in] user SMTP user name.
+ * @param[in] pass SMTP password.
+ * @return See @ref smtp_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);
+
+/**
+ * Sends an email using the addresses, attachments, and headers defined
+ * in the current SMTP context.
+ *
+ * The caller must call the @ref smtp_open function prior to this.
+ *
+ * The 'Date' header will automatically get generated here if it hasn't
+ * already been set using @ref smtp_header_add.
+ *
+ * If the application overrides the default 'Content-Type' header, then
+ * this function will output the @p body as raw data just below the email
+ * headers, and it will not output the attachments added using the
+ * smtp_attachment_add_* functions. In other words, the application must
+ * create its own MIME sections (if needed) when overriding the
+ * 'Content-Type' header.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] body Null-terminated string to send in the email body.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_mail(struct smtp *const smtp,
+ const char *const body);
+
+/**
+ * Close the SMTP connection and frees all resources held by the
+ * SMTP context.
+ *
+ * @param[in] smtp SMTP client context.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_close(struct smtp *smtp);
+
+/**
+ * Get the current status/error code.
+ *
+ * @param[in] smtp SMTP client context.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_status_code_get(const struct smtp *const smtp);
+
+/**
+ * Clear the current error code set in the SMTP client context.
+ *
+ * @param[in,out] smtp SMTP client context.
+ * @return Previous error code before clearing.
+ */
+enum smtp_status_code
+smtp_status_code_clear(struct smtp *const smtp);
+
+/**
+ * Set the error status of the SMTP client context and return the same code.
+ *
+ * This allows the caller to clear an error status to SMTP_STATUS_OK
+ * so that previous errors will stop propagating. However, this will only
+ * work correctly for clearing the SMTP_STATUS_PARAM and SMTP_STATUS_FILE
+ * errors. Do not use this to clear any other error codes.
+ *
+ * @deprecated Use @ref smtp_status_code_clear instead.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] new_status_code See @ref smtp_status_code.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_status_code_set(struct smtp *const smtp,
+ enum smtp_status_code new_status_code);
+
+/**
+ * Convert a standard SMTP client status code to a descriptive string.
+ *
+ * @param[in] status_code Status code returned from one of the other
+ * library functions.
+ * @return String containing a description of the @p status_code. The caller
+ * must not free or modify this string.
+ */
+const char *
+smtp_status_code_errstr(enum smtp_status_code status_code);
+
+/**
+ * Add a key/value header to the header list in the SMTP context.
+ *
+ * If adding a header with an existing key, this will insert instead of
+ * replacing the existing header. See @ref smtp_header_clear_all.
+ *
+ * See @ref smtp_mail when overriding the default 'Content-Type' header.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] key Key name for new header. It must consist only of
+ * printable US-ASCII characters except colon.
+ * @param[in] value Value for new header. It must consist only of printable
+ * US-ASCII, space, or horizontal tab. If set to NULL,
+ * this will prevent the header from printing out.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_header_add(struct smtp *const smtp,
+ const char *const key,
+ const char *const value);
+
+/**
+ * Free all memory related to email headers.
+ *
+ * @param[in] smtp SMTP client context.
+ */
+void
+smtp_header_clear_all(struct smtp *const smtp);
+
+/**
+ * Add a FROM, TO, CC, or BCC address destination to this SMTP context.
+ *
+ * @note Some SMTP servers may reject over 100 recipients.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] type See @ref smtp_address_type.
+ * @param[in] email The email address of the party. Must consist only of
+ * printable characters excluding the angle brackets
+ * (<) and (>).
+ * @param[in] name Name or description of the party. Must consist only of
+ * printable characters, excluding the quote characters. If
+ * set to NULL or empty string, no name will get associated
+ * with this email.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_address_add(struct smtp *const smtp,
+ enum smtp_address_type type,
+ const char *const email,
+ const char *const name);
+
+/**
+ * Free all memory related to the address list.
+ *
+ * @param[in] smtp SMTP client context.
+ */
+void
+smtp_address_clear_all(struct smtp *const smtp);
+
+/**
+ * Add a file attachment from a path.
+ *
+ * See @ref smtp_attachment_add_mem for more details.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] name Filename of the attachment shown to recipients. Must
+ * consist only of printable ASCII characters, excluding
+ * the quote characters (') and (").
+ * @param[in] path Path to file.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_attachment_add_path(struct smtp *const smtp,
+ const char *const name,
+ const char *const path);
+
+/**
+ * Add an attachment using a file pointer.
+ *
+ * See @ref smtp_attachment_add_mem for more details.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] name Filename of the attachment shown to recipients. Must
+ * consist only of printable ASCII characters, excluding
+ * the quote characters (') and (").
+ * @param[in] fp File pointer already opened by the caller.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_attachment_add_fp(struct smtp *const smtp,
+ const char *const name,
+ FILE *fp);
+
+/**
+ * Add a MIME attachment to this SMTP context with the data retrieved
+ * from memory.
+ *
+ * The attachment data will get base64 encoded before sending to the server.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] name Filename of the attachment shown to recipients. Must
+ * consist only of printable ASCII characters, excluding
+ * the quote characters (') and (").
+ * @param[in] data Raw attachment data stored in memory.
+ * @param[in] datasz Number of bytes in @p data, or -1 if data
+ * null-terminated.
+ * @return See @ref 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);
+
+/**
+ * Remove all attachments from the SMTP client context.
+ *
+ * @param[in] smtp SMTP client context.
+ */
+void
+smtp_attachment_clear_all(struct smtp *const smtp);
+
+
+/*
+ * The SMTP_INTERNAL DEFINE section contains definitions that get used
+ * internally by the SMTP client library.
+ */
+#ifdef SMTP_INTERNAL_DEFINE
+/**
+ * SMTP codes returned by the server and parsed by the client.
+ */
+enum smtp_result_code{
+ /**
+ * Client error code which does not get set by the server.
+ */
+ SMTP_INTERNAL_ERROR = -1,
+
+ /**
+ * Returned when ready to begin processing next step.
+ */
+ SMTP_READY = 220,
+
+ /**
+ * Returned in response to QUIT.
+ */
+ SMTP_CLOSE = 221,
+
+ /**
+ * Returned if client successfully authenticates.
+ */
+ SMTP_AUTH_SUCCESS = 235,
+
+ /**
+ * Returned when some commands successfully complete.
+ */
+ SMTP_DONE = 250,
+
+ /**
+ * Returned for some multi-line authentication mechanisms where this code
+ * indicates the next stage in the authentication step.
+ */
+ SMTP_AUTH_CONTINUE = 334,
+
+ /**
+ * Returned in response to DATA command.
+ */
+ SMTP_BEGIN_MAIL = 354
+};
+
+/**
+ * Used for parsing out the responses from the SMTP server.
+ *
+ * For example, if the server sends back '250-STARTTLS', then code would
+ * get set to 250, more would get set to 1, and text would get set to STARTTLS.
+ */
+struct smtp_command{
+ /**
+ * Result code converted to an integer.
+ */
+ enum smtp_result_code code;
+
+ /**
+ * Indicates if more server commands follow.
+ *
+ * This will get set to 1 if the fourth character in the response line
+ * contains a '-', otherwise this will get set to 0.
+ */
+ int more;
+
+ /**
+ * The text shown after the status code.
+ */
+ const char *text;
+};
+
+/**
+ * Return codes for the getdelim interface which allows the caller to check
+ * if more delimited lines can get processed.
+ */
+enum str_getdelim_retcode{
+ /**
+ * An error occurred during the getdelim processing.
+ */
+ STRING_GETDELIMFD_ERROR = -1,
+
+ /**
+ * Found a new line and can process more lines in the next call.
+ */
+ STRING_GETDELIMFD_NEXT = 0,
+
+ /**
+ * Found a new line and unable to read any more lines at this time.
+ */
+ STRING_GETDELIMFD_DONE = 1
+};
+
+/**
+ * Data structure for read buffer and line parsing.
+ *
+ * This assists with getting and parsing the server response lines.
+ */
+struct str_getdelimfd{
+ /**
+ * Read buffer which may include bytes past the delimiter.
+ */
+ char *_buf;
+
+ /**
+ * Number of allocated bytes in the read buffer.
+ */
+ size_t _bufsz;
+
+ /**
+ * Number of actual stored bytes in the read buffer.
+ */
+ size_t _buf_len;
+
+ /**
+ * Current line containing the text up to the delimiter.
+ */
+ char *line;
+
+ /**
+ * Number of stored bytes in the line buffer.
+ */
+ size_t line_len;
+
+ /**
+ * Function pointer to a custom read function for the
+ * @ref smtp_str_getdelimfd interface.
+ *
+ * This function prototype has similar semantics to the read function.
+ * The @p gdfd parameter allows the custom function to pull the user_data
+ * info from the @ref str_getdelimfd struct which can contain file pointer,
+ * socket connection, etc.
+ */
+ long (*getdelimfd_read)(struct str_getdelimfd *const gdfd,
+ void *buf,
+ size_t count);
+
+ /**
+ * User data which gets sent to the read handler function.
+ */
+ void *user_data;
+
+ /**
+ * Character delimiter used for determining line separation.
+ */
+ int delim;
+
+ /**
+ * Padding structure to align.
+ */
+ char pad[4];
+};
+
+#endif /* SMTP_INTERNAL_DEFINE */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SMTP_H */
+
deleted file mode 100644
@@ -1,117 +0,0 @@
-/**
- * @file
- * @brief SMTPMail class wrapper for smtp-client library.
- * @author James Humphrey (mail@somnisoft.com)
- * @version 1.00
- *
- * Thin CPP wrapper class around the smtp-client C library.
- *
- * This software has been placed into the public domain using CC0.
- */
-#include "SMTPMail.h"
-
-SMTPMailException::SMTPMailException(enum smtp_status_code status_code){
- this->status_code = status_code;
-}
-
-const char *const
-SMTPMailException::what() throw(){
- return smtp_status_code_errstr(this->status_code);
-}
-
-SMTPMail::SMTPMail(void){
-}
-
-SMTPMail::~SMTPMail(void){
-}
-
-void SMTPMail::open(const char *const server,
- const char *const port,
- enum smtp_connection_security connection_security,
- enum smtp_flag flags,
- const char *const cafile){
- this->rc = smtp_open(server,
- port,
- connection_security,
- flags,
- cafile,
- &this->smtp);
- this->throw_bad_status_code();
-}
-
-void SMTPMail::auth(enum smtp_authentication_method auth_method,
- const char *const user,
- const char *const pass){
- this->rc = smtp_auth(this->smtp, auth_method, user, pass);
- this->throw_bad_status_code();
-}
-
-void SMTPMail::mail(const char *const body){
- this->rc = smtp_mail(this->smtp, body);
- this->throw_bad_status_code();
-}
-
-void SMTPMail::close(void){
- this->rc = smtp_close(this->smtp);
- this->throw_bad_status_code();
-}
-
-int SMTPMail::status_code_get(void){
- return smtp_status_code_get(this->smtp);
-}
-
-void SMTPMail::status_code_set(enum smtp_status_code new_status_code){
- this->rc = smtp_status_code_set(this->smtp, new_status_code);
- this->throw_bad_status_code();
-}
-
-void SMTPMail::header_add(const char *const key,
- const char *const value){
- this->rc = smtp_header_add(this->smtp, key, value);
- this->throw_bad_status_code();
-}
-
-void SMTPMail::header_clear_all(void){
- smtp_header_clear_all(this->smtp);
-}
-
-void SMTPMail::address_add(enum smtp_address_type type,
- const char *const email,
- const char *const name){
- this->rc = smtp_address_add(this->smtp, type, email, name);
- this->throw_bad_status_code();
-}
-
-void SMTPMail::address_clear_all(void){
- smtp_address_clear_all(this->smtp);
-}
-
-void SMTPMail::attachment_add_path(const char *const name,
- const char *const path){
- this->rc = smtp_attachment_add_path(this->smtp, name, path);
- this->throw_bad_status_code();
-}
-
-void SMTPMail::attachment_add_fp(const char *const name,
- FILE *fp){
- this->rc = smtp_attachment_add_fp(this->smtp, name, fp);
- this->throw_bad_status_code();
-}
-
-void SMTPMail::attachment_add_mem(const char *const name,
- const void *const data,
- ssize_t datasz){
- this->rc = smtp_attachment_add_mem(this->smtp, name, data, datasz);
- this->throw_bad_status_code();
-}
-
-void SMTPMail::attachment_clear_all(void){
- smtp_attachment_clear_all(this->smtp);
-}
-
-void SMTPMail::throw_bad_status_code(void){
- if(this->rc != SMTP_STATUS_OK){
- throw SMTPMailException(this->rc);
- }
-}
-
deleted file mode 100644
@@ -1,243 +0,0 @@
-/**
- * @file
- * @brief SMTPMail class wrapper for smtp-client library.
- * @author James Humphrey (mail@somnisoft.com)
- * @version 1.00
- *
- * Thin CPP wrapper class around the smtp-client C library.
- *
- * This software has been placed into the public domain using CC0.
- */
-#ifndef SMTP_MAIL_H
-#define SMTP_MAIL_H
-
-#include <exception>
-
-#include "smtp.h"
-
-/**
- * @class SMTPMailException
- *
- * This exception will get thrown whenever an SMTP client function fails.
- */
-class SMTPMailException : public std::exception{
-public:
- /**
- * Create the exception with the corresponding @p status_code.
- *
- * @param[in] status_code An error status code returned from a previous call
- * to an smtp-client library function.
- */
- SMTPMailException(enum smtp_status_code status_code);
-
- /**
- * Get a description of the smtp-client status code that caused
- * this exception.
- *
- * @return Null-terminated string describing the smtp-client status code.
- */
- virtual const char *const what() throw();
-
-private:
- /**
- * The status code generated by any of the smtp-client library functions.
- */
- enum smtp_status_code status_code;
-};
-
-/**
- * @class SMTPMail
- *
- * Thin CPP wrapper class over the smtp-client C library.
- */
-class SMTPMail{
-public:
- /**
- * Create the @ref SMTPMail object.
- */
- SMTPMail(void);
-
- /**
- * Free the @ref SMTPMail object.
- */
- ~SMTPMail(void);
-
- /**
- * Open a connection to an SMTP server.
- *
- * See @ref smtp_open.
- *
- * @param[in] server Server name or IP address.
- * @param[in] port Server port number.
- * @param[in] connection_security See @ref smtp_connection_security.
- * @param[in] flags See @ref smtp_flag.
- * @param[in] cafile Path to certificate file, or NULL to use
- * certificates in the default path.
- */
- void open(const char *const server,
- const char *const port,
- enum smtp_connection_security connection_security,
- enum smtp_flag flags,
- const char *const cafile);
-
- /**
- * Authenticate the user using one of the methods listed in
- * @ref smtp_authentication_method.
- *
- * See @ref smtp_auth.
- *
- * @param[in] auth_method See @ref smtp_authentication_method.
- * @param[in] user Server authentication user name.
- * @param[in] pass Server authentication user password.
- */
- void auth(enum smtp_authentication_method auth_method,
- const char *const user,
- const char *const pass);
-
- /**
- * Sends an email using the addresses, attachments, and headers defined
- * in the current SMTP context.
- *
- * See @ref smtp_mail.
- *
- * @param[in] body Null-terminated string to send in the email body.
- */
- void mail(const char *const body);
-
- /**
- * Close the SMTP connection and frees all resources held by the
- * SMTP context.
- *
- * See @ref smtp_close.
- */
- void close(void);
-
- /**
- * Get the current status/error code described in @ref smtp_status_code.
- *
- * See @ref smtp_status_code_get.
- *
- * @return @ref smtp_status_code.
- */
- int status_code_get(void);
-
- /**
- * Set the error status of the SMTP client context.
- *
- * See @ref smtp_status_code_set.
- *
- * @param[in] new_status_code See @ref smtp_status_code.
- */
- void status_code_set(enum smtp_status_code new_status_code);
-
- /**
- * Add a key/value header to the header list in the SMTP context.
- *
- * See @ref smtp_header_add.
- *
- * @param[in] key Key name for new header. It must consist only of
- * printable US-ASCII characters except colon.
- * @param[in] value Value for new header. It must consist only of printable
- * US-ASCII, space, or horizontal tab. If set to NULL,
- * this will prevent the header from printing out.
- */
- void header_add(const char *const key,
- const char *const value);
-
- /**
- * Free all memory related to email headers.
- *
- * See @ref smtp_header_clear_all.
- */
- void header_clear_all(void);
-
- /**
- * Add a FROM, TO, CC, or BCC address destination to this SMTP context.
- *
- * See @ref smtp_address_add.
- *
- * @param[in] type See @ref smtp_address_type.
- * @param[in] email The email address of the party. Must consist only of
- * printable characters excluding the angle brackets
- * (<) and (>).
- * @param[in] name Name or description of the party. Must consist only of
- * printable characters, excluding the quote characters. If
- * set to NULL, no name will get associated with this email.
- */
- void address_add(enum smtp_address_type type,
- const char *const email,
- const char *const name);
-
- /**
- * Free all memory related to the address list.
- *
- * See @ref smtp_address_clear_all.
- */
- void address_clear_all(void);
-
- /**
- * Add a file attachment from a path.
- *
- * See @ref smtp_attachment_add_mem.
- *
- * @param[in] name Filename of the attachment shown to recipients.
- * @param[in] path Path of file location to read from.
- */
- void attachment_add_path(const char *const name,
- const char *const path);
-
- /**
- * Add an attachment using a file pointer.
- *
- * See @ref smtp_attachment_add_fp.
- *
- * @param[in] name Filename of the attachment shown to recipients.
- * @param[in] fp File pointer already opened by the caller.
- */
- void attachment_add_fp(const char *const name,
- FILE *fp);
-
- /**
- * Add an attachment with the data pulled from memory.
- *
- * See @ref smtp_attachment_add_mem.
- *
- * @param[in] name Filename of the attachment shown to recipients. Must
- * consist only of printable characters excluding the
- * quote characters (') and ("), or the space character
- * ( ).
- * @param[in] data Raw attachment data stored in memory.
- * @param[in] datasz Number of bytes in @p data, or -1 if data
- * null-terminated.
- */
- void attachment_add_mem(const char *const name,
- const void *const data,
- ssize_t datasz);
-
- /**
- * Remove all attachments from the SMTP client context.
- *
- * See @ref smtp_attachment_clear_all.
- */
- void attachment_clear_all(void);
-
-private:
- /**
- * SMTP client context.
- */
- struct smtp *smtp;
-
- /**
- * Store the last smtp-client library function return code.
- */
- enum smtp_status_code rc;
-
- /**
- * Throw @ref SMTPMailException if the last smtp-client library function
- * failed.
- */
- void throw_bad_status_code(void);
-};
-
-#endif /* SMTP_MAIL_H */
-
deleted file mode 100644
@@ -1,558 +0,0 @@
-/**
- * @file
- * @brief POSIX mailx utility.
- * @author James Humphrey (mail@somnisoft.com)
- * @version 1.00
- *
- * Implementation of POSIX mailx utility in send mode.
- *
- * mailx [-s subject] [[-S option]...] [[-a attachment]...] address...
- *
- * This software has been placed into the public domain using CC0.
- */
-
-/**
- * Required on some POSIX systems to include some standard functions.
- */
-#define _POSIX_C_SOURCE 200809L
-
-#include <err.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "smtp.h"
-
-/**
- * Stores the to and from email addresses.
- */
-struct mailx_address{
- /**
- * See @ref smtp_address_type.
- */
- enum smtp_address_type address_type;
-
- /**
- * Email address.
- */
- char email[1000];
-};
-
-/**
- * The attachment name and path stored for each attachment to send to the
- * recipient.
- */
-struct mailx_attachment{
- /**
- * File name for this attachment to display to the recipient.
- */
- char name[1000];
-
- /**
- * Local file path pointing to the attachment to send.
- */
- char path[1000];
-};
-
-/**
- * The mailx context structure containing the parameters for setting up the
- * SMTP connection and sending the email.
- */
-struct mailx{
- /**
- * SMTP client context.
- */
- struct smtp *smtp;
-
- /**
- * Email subject line.
- */
- const char *subject;
-
- /**
- * Email body text.
- */
- char *body;
-
- /**
- * SMTP server name or IP address.
- */
- char *server;
-
- /**
- * SMTP server port number.
- */
- char *port;
-
- /**
- * SMTP account user name used for authenticating.
- */
- char *user;
-
- /**
- * SMTP account password used for authenticating.
- */
- char *pass;
-
- /**
- * From email address or name.
- */
- char *from;
-
- /**
- * Determine if using a TLS encrypted connection or plain socket.
- */
- enum smtp_connection_security connection_security;
-
- /**
- * SMTP user account authentication method.
- */
- enum smtp_authentication_method auth_method;
-
- /**
- * Miscellaneous control flags for smtp-lib.
- *
- * See @ref smtp_flag for more details.
- */
- enum smtp_flag smtp_flags;
-
- /**
- * List of email addresses to send to.
- */
- struct mailx_address *address_list;
-
- /**
- * Number of email addresses in @ref address_list.
- */
- size_t num_address;
-
- /**
- * List of files to attach in the email.
- */
- struct mailx_attachment *attachment_list;
-
- /**
- * Number of attachments in @ref attachment_list.
- */
- size_t num_attachment;
-};
-
-/**
- * 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.
- */
-static char *
-smtp_ffile_get_contents(FILE *stream,
- size_t *bytes_read){
- char *read_buf;
- size_t bufsz;
- char *new_buf;
- const size_t BUFSZ_INCREMENT = 512;
-
- read_buf = NULL;
- bufsz = 0;
-
- if(bytes_read){
- *bytes_read = 0;
- }
-
- do{
- size_t bytes_read_loop;
- if((new_buf = realloc(read_buf, bufsz + BUFSZ_INCREMENT)) == NULL){
- free(read_buf);
- return NULL;
- }
- read_buf = new_buf;
- bufsz += BUFSZ_INCREMENT;
-
- 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;
-}
-
-/**
- * Append this email to the list of email addresses to send to.
- *
- * @param[in] mailx Append the email address into this mailx context.
- * @param[in] address_type See @ref smtp_address_type.
- * @param[in] email Email address to send to.
- */
-static void
-mailx_address_append(struct mailx *const mailx,
- enum smtp_address_type address_type,
- const char *const email){
- struct mailx_address *new_address;
- size_t new_address_list_sz;
-
- mailx->num_address += 1;
- new_address_list_sz = mailx->num_address * sizeof(*mailx->address_list);
- if((mailx->address_list = realloc(mailx->address_list,
- new_address_list_sz)) == NULL){
- err(1, "realloc");
- }
-
- new_address = &mailx->address_list[mailx->num_address - 1];
- new_address->address_type = address_type;
- strncpy(new_address->email, email, sizeof(new_address->email));
- new_address->email[sizeof(new_address->email) - 1] = '\0';
-}
-
-/**
- * Send the email using the configuration options in the @p mailx context.
- *
- * @param[in] mailx Email context.
- */
-static void
-mailx_send(struct mailx *const mailx){
- int rc;
- size_t i;
- const struct mailx_address *address;
- const struct mailx_attachment *attachment;
-
- smtp_open(mailx->server,
- mailx->port,
- mailx->connection_security,
- mailx->smtp_flags,
- NULL,
- &mailx->smtp);
-
- smtp_auth(mailx->smtp,
- mailx->auth_method,
- mailx->user,
- mailx->pass);
-
- for(i = 0; i < mailx->num_address; i++){
- address = &mailx->address_list[i];
- smtp_address_add(mailx->smtp, address->address_type, address->email, NULL);
- }
-
- for(i = 0; i < mailx->num_attachment; i++){
- attachment = &mailx->attachment_list[i];
- smtp_attachment_add_path(mailx->smtp, attachment->name, attachment->path);
- }
- smtp_header_add(mailx->smtp, "Subject", mailx->subject);
- smtp_mail(mailx->smtp, mailx->body);
-
- rc = smtp_close(mailx->smtp);
-
- if(rc != SMTP_STATUS_OK){
- errx(1, "%s", smtp_status_code_errstr(rc));
- }
-}
-
-/**
- * Attach a file to the @p mailx context.
- *
- * @param[in] mailx Store the attachment details into this mailx context.
- * @param[in] filename File name to display to the recipient.
- * @param[in] path Local path of file to attach.
- */
-static void
-mailx_append_attachment(struct mailx *const mailx,
- const char *const filename,
- const char *const path){
- struct mailx_attachment *new_attachment;
- size_t new_attachment_list_sz;
-
- if(filename == NULL || path == NULL){
- errx(1, "must provide attachment with valid name:path");
- }
-
- new_attachment_list_sz = (mailx->num_attachment + 1) *
- sizeof(*mailx->attachment_list);
- if((mailx->attachment_list = realloc(mailx->attachment_list,
- new_attachment_list_sz)) == NULL){
- err(1, "realloc: attachment list");
- }
- new_attachment = &mailx->attachment_list[mailx->num_attachment];
- mailx->num_attachment += 1;
-
- strncpy(new_attachment->name, filename, sizeof(new_attachment->name));
- new_attachment->name[sizeof(new_attachment->name) - 1] = '\0';
-
- strncpy(new_attachment->path, path, sizeof(new_attachment->path));
- new_attachment->path[sizeof(new_attachment->path) - 1] = '\0';
-}
-
-/**
- * Parse the file name and path and attach it to the @p mailx context.
- *
- * @param[in] mailx Store the attachment details into this mailx context.
- * @param[in] attach_arg String with format: 'filename:filepath'.
- */
-static void
-mailx_append_attachment_arg(struct mailx *const mailx,
- const char *const attach_arg){
- char *attach_arg_dup;
- char *filename;
- char *filepath;
-
- if((attach_arg_dup = strdup(attach_arg)) == NULL){
- err(1, "strdup: %s", attach_arg);
- }
-
- filename = strtok(attach_arg_dup, ":");
- filepath = strtok(NULL, ":");
-
- mailx_append_attachment(mailx, filename, filepath);
-
- free(attach_arg_dup);
-}
-
-/**
- * Parses the -S option which contains a key/value pair separated by an '='
- * character.
- *
- * @param[in] mailx Store the results of the option parsing into the relevant
- * field in this mailx context.
- * @param[in] option String containing key/value option to parse.
- */
-static void
-mailx_parse_smtp_option(struct mailx *const mailx,
- const char *const option){
- char *optdup;
- char *opt_key;
- char *opt_value;
- int rc;
-
- rc = 0;
-
- if((optdup = strdup(option)) == NULL){
- err(1, "strdup: option: %s", option);
- }
-
- if((opt_key = strtok(optdup, "=")) == NULL){
- errx(1, "strtok: %s", optdup);
- }
-
- opt_value = strtok(NULL, "=");
-
- if(strcmp(opt_key, "smtp-security") == 0){
- if(strcmp(opt_value, "none") == 0){
- mailx->connection_security = SMTP_SECURITY_NONE;
- }
-#ifdef SMTP_OPENSSL
- else if(strcmp(opt_value, "tls") == 0){
- mailx->connection_security = SMTP_SECURITY_TLS;
- }
- else if(strcmp(opt_value, "starttls") == 0){
- mailx->connection_security = SMTP_SECURITY_STARTTLS;
- }
-#endif /* SMTP_OPENSSL */
- else{
- rc = -1;
- }
- }
- else if(strcmp(opt_key, "smtp-auth") == 0){
- if(strcmp(opt_value, "none") == 0){
- mailx->auth_method = SMTP_AUTH_NONE;
- }
- else if(strcmp(opt_value, "plain") == 0){
- mailx->auth_method = SMTP_AUTH_PLAIN;
- }
- else if(strcmp(opt_value, "login") == 0){
- mailx->auth_method = SMTP_AUTH_LOGIN;
- }
-#ifdef SMTP_OPENSSL
- else if(strcmp(opt_value, "cram-md5") == 0){
- mailx->auth_method = SMTP_AUTH_CRAM_MD5;
- }
-#endif /* SMTP_OPENSSL */
- else{
- rc = -1;
- }
- }
- else if(strcmp(opt_key, "smtp-flag") == 0){
- if(strcmp(opt_value, "debug") == 0){
- mailx->smtp_flags |= SMTP_DEBUG;
- }
- else if(strcmp(opt_value, "no-cert-verify") == 0){
- mailx->smtp_flags |= SMTP_NO_CERT_VERIFY;
- }
- else{
- rc = -1;
- }
- }
- else if(strcmp(opt_key, "smtp-server") == 0){
- if((mailx->server = strdup(opt_value)) == NULL){
- err(1, "strdup");
- }
- }
- else if(strcmp(opt_key, "smtp-port") == 0){
- if((mailx->port = strdup(opt_value)) == NULL){
- err(1, "strdup");
- }
- }
- else if(strcmp(opt_key, "smtp-user") == 0){
- if((mailx->user = strdup(opt_value)) == NULL){
- err(1, "strdup");
- }
- }
- else if(strcmp(opt_key, "smtp-pass") == 0){
- if((mailx->pass = strdup(opt_value)) == NULL){
- err(1, "strdup");
- }
- }
- else if(strcmp(opt_key, "smtp-from") == 0){
- if((mailx->from = strdup(opt_value)) == NULL){
- err(1, "strdup");
- }
- }
- else{
- rc = -1;
- }
-
- free(optdup);
-
- if(rc < 0){
- errx(1, "invalid argument: %s", option);
- }
-}
-
-/**
- * Initialize and set the default options in the mailx context.
- *
- * See description of -S argument in main for more details.
- *
- * @param[in] mailx The mailx content to initialize.
- */
-static void
-mailx_init_default_values(struct mailx *const mailx){
- memset(mailx, 0, sizeof(*mailx));
- mailx->subject = "";
- mailx->connection_security = SMTP_SECURITY_NONE;
- mailx->auth_method = SMTP_AUTH_NONE;
-}
-
-/**
- * Frees the allocated memory associated with the mailx context.
- *
- * @param[in] mailx The mailx context to free.
- */
-static void
-mailx_free(const struct mailx *const mailx){
- free(mailx->body);
- free(mailx->server);
- free(mailx->port);
- free(mailx->user);
- free(mailx->pass);
- free(mailx->from);
-}
-
-/**
- * Main program entry point for the mailx utility.
- *
- * This program supports the following options:
- * - -a 'name:path' - Attach a file with name to display to recipient and
- * file path pointing to file location on local storage.
- * - -s subject - Email subject line.
- * - -S key=value - A key/value pair to set various configuration options,
- * controlling the behavior of the SMTP client connection.
- *
- * The following list contains possible options for the -S argument:
- * - smtp-security - none, tls, starttls
- * - smtp-auth - none, plain, login, cram-md5
- * - smtp-flag - debug, no-cert-verify
- * - smtp-server - server name or IP address
- * - smtp-port - server port number
- * - smtp-user - server authentication user name
- * - smtp-pass - server authentication user password
- * - smtp-from - from email account
- *
- * The following list shows the default option for -S argument if not provided:
- * - smtp-security - none
- * - smtp-auth - none
- * - smtp-flag - none
- * - smtp-server - localhost
- * - smtp-port - 25
- * - smtp-user - none
- * - smtp-pass - none
- * - smtp-from - none
- *
- * @param[in] argc Number of arguments in @p argv.
- * @param[in] argv String array containing the program name and any optional
- * parameters described above.
- * @retval 0 Email has been sent.
- * @retval 1 An error occurred while sending email. Although unlikely, an email
- * can still get sent even after returning with this error code.
- */
-int main(int argc, char *argv[]){
- int rc;
- int i;
- struct mailx mailx;
-
- mailx_init_default_values(&mailx);
-
- while((rc = getopt(argc, argv, "a:s:S:")) != -1){
- switch(rc){
- case 'a':
- mailx_append_attachment_arg(&mailx, optarg);
- break;
- case 's':
- mailx.subject = optarg;
- break;
- case 'S':
- mailx_parse_smtp_option(&mailx, optarg);
- break;
- default:
- return 1;
- }
- }
- argc -= optind;
- argv += optind;
-
- if(argc < 1){
- errx(1, "must provide at least one email destination address");
- }
-
- if(mailx.from == NULL){
- errx(1, "must provide a FROM address");
- }
-
- if(mailx.server == NULL){
- if((mailx.server = strdup("localhost")) == NULL){
- err(1, "strdup");
- }
- }
-
- if(mailx.port == NULL){
- if((mailx.port = strdup("25")) == NULL){
- err(1, "strdup");
- }
- }
-
- puts("Reading email body from stdin");
- if((mailx.body = smtp_ffile_get_contents(stdin, NULL)) == NULL){
- err(1, "failed to read email body from stdin");
- }
-
- mailx_address_append(&mailx, SMTP_ADDRESS_FROM, mailx.from);
-
- for(i = 0; i < argc; i++){
- mailx_address_append(&mailx, SMTP_ADDRESS_TO, argv[i]);
- }
-
- mailx_send(&mailx);
- mailx_free(&mailx);
- return 0;
-}
-
deleted file mode 100644
@@ -1,3528 +0,0 @@
-/**
- * @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)
-# 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"
-
-/*
- * The SMTP_TEST converts some library routines into special test seams which
- * allows the test program to control whether they fail. For example, we can
- * control when malloc() fails under certain conditions with an out of
- * memory condition.
- */
-#ifdef SMTP_TEST
-/**
- * Declare extern linkage on some functions so we can redefine their behavior
- * in the external test suite.
- */
-# define SMTP_LINKAGE extern
-# include "../test/seams.h"
-#else /* !(SMTP_TEST) */
-/**
- * When not testing, all functions should have static linkage except for those
- * in the header.
- */
-# define SMTP_LINKAGE static
-#endif /* SMTP_TEST */
-
-/**
- * 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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(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.
- */
-SMTP_LINKAGE 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, ©_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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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();
- ERR_load_BIO_strings();
- 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);
- if((smtp->flags & SMTP_NO_CERT_VERIFY) == 0){
- 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((smtp->flags & SMTP_NO_CERT_VERIFY) == 0){
- 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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.
- */
-SMTP_LINKAGE 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;
-}
-
deleted file mode 100644
@@ -1,635 +0,0 @@
-/**
- * @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.
- */
-#ifndef SMTP_H
-#define SMTP_H
-
-#include <sys/types.h>
-#include <stddef.h>
-#include <stdio.h>
-
-#ifndef SIZE_MAX
-/**
- * Maximum value of size_t type.
- */
-# define SIZE_MAX ((size_t)(-1))
-#endif /* SIZE_MAX */
-
-/**
- * Status codes indicating success or failure from calling any of the
- * SMTP library functions.
- *
- * This code gets returned by all functions in this header.
- */
-enum smtp_status_code{
- /**
- * Successful operation completed.
- */
- SMTP_STATUS_OK = 0,
-
- /**
- * Memory allocation failed.
- */
- SMTP_STATUS_NOMEM = 1,
-
- /**
- * Failed to connect to the mail server.
- */
- SMTP_STATUS_CONNECT = 2,
-
- /**
- * Failed to handshake or negotiate a TLS connection with the server.
- */
- SMTP_STATUS_HANDSHAKE = 3,
-
- /**
- * Failed to authenticate with the given credentials.
- */
- SMTP_STATUS_AUTH = 4,
-
- /**
- * Failed to send bytes to the server.
- */
- SMTP_STATUS_SEND = 5,
-
- /**
- * Failed to receive bytes from the server.
- */
- SMTP_STATUS_RECV = 6,
-
- /**
- * Failed to properly close a connection.
- */
- SMTP_STATUS_CLOSE = 7,
-
- /**
- * SMTP server sent back an unexpected status code.
- */
- SMTP_STATUS_SERVER_RESPONSE = 8,
-
- /**
- * Invalid parameter.
- */
- SMTP_STATUS_PARAM = 9,
-
- /**
- * Failed to open or read a local file.
- */
- SMTP_STATUS_FILE = 10,
-
- /**
- * Failed to get the local date and time.
- */
- SMTP_STATUS_DATE = 11,
-
- /**
- * Indicates the last status code in the enumeration, useful for
- * bounds checking.
- *
- * Not a valid status code.
- */
- SMTP_STATUS__LAST
-};
-
-/**
- * Address source and destination types.
- */
-enum smtp_address_type{
- /**
- * From address.
- */
- SMTP_ADDRESS_FROM = 0,
-
- /**
- * To address.
- */
- SMTP_ADDRESS_TO = 1,
-
- /**
- * Copy address.
- */
- SMTP_ADDRESS_CC = 2,
-
- /**
- * Blind copy address.
- *
- * Recipients should not see any of the BCC addresses when they receive
- * their email. However, some SMTP server implementations may copy this
- * information into the mail header, so do not assume that this will
- * always get hidden. If the BCC addresses must not get shown to the
- * receivers, then send one separate email to each BCC party and add
- * the TO and CC addresses manually as a header property using
- * @ref smtp_header_add instead of as an address using
- * @ref smtp_address_add.
- */
- SMTP_ADDRESS_BCC = 3
-};
-
-/**
- * Connect to the SMTP server using either an unencrypted socket or
- * TLS encryption.
- */
-enum smtp_connection_security{
-#ifdef SMTP_OPENSSL
- /**
- * First connect without encryption, then negotiate an encrypted connection
- * by issuing a STARTTLS command.
- *
- * Typically used when connecting to a mail server on port 25 or 587.
- */
- SMTP_SECURITY_STARTTLS = 0,
-
- /**
- * Use TLS when initially connecting to server.
- *
- * Typically used when connecting to a mail server on port 465.
- */
- SMTP_SECURITY_TLS = 1,
-#endif /* SMTP_OPENSSL */
-
- /**
- * Do not use TLS encryption.
- *
- * Not recommended unless connecting to the SMTP server locally.
- */
- SMTP_SECURITY_NONE = 2
-};
-
-/**
- * List of supported methods for authenticating a mail user account on
- * the server.
- */
-enum smtp_authentication_method{
-#ifdef SMTP_OPENSSL
- /**
- * Use HMAC-MD5.
- */
- SMTP_AUTH_CRAM_MD5 = 0,
-#endif /* SMTP_OPENSSL */
- /**
- * No authentication required.
- *
- * Some servers support this option if connecting locally.
- */
- SMTP_AUTH_NONE = 1,
-
- /**
- * Authenticate using base64 user and password.
- */
- SMTP_AUTH_PLAIN = 2,
-
- /**
- * Another base64 authentication method, similar to SMTP_AUTH_PLAIN.
- */
- SMTP_AUTH_LOGIN = 3
-};
-
-/**
- * Special flags defining certain behaviors for the SMTP client context.
- */
-enum smtp_flag{
- /**
- * Print client and server communication on stderr.
- */
- SMTP_DEBUG = 1 << 0,
-
- /**
- * Do not verify TLS certificate.
- *
- * By default, the TLS handshake function will check if a certificate
- * has expired or if using a self-signed certificate. Either of those
- * conditions will cause the connection to fail. This option allows the
- * connection to proceed even if those checks fail.
- */
- SMTP_NO_CERT_VERIFY = 1 << 1
-};
-
-struct smtp;
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-/**
- * Open a connection to an SMTP server and return the context.
- *
- * After successfully connecting and performing a handshake with the
- * SMTP server, this will return a valid SMTP client context that
- * the application can use in the other library functions.
- *
- * This always returns a valid SMTP client context even if
- * the server connection or memory allocation fails. In this scenario, the
- * error status will continue to propagate to future library calls for
- * the SMTP context while in this failure mode.
- *
- * This function will ignore the SIGPIPE signal. Applications that require a
- * handler for that signal should set it up after calling this function.
- *
- * @param[in] server Server name or IP address.
- * @param[in] port Server port number.
- * @param[in] connection_security See @ref smtp_connection_security.
- * @param[in] flags See @ref smtp_flag.
- * @param[in] cafile Path to certificate file, or NULL to use
- * certificates in the default path.
- * @param[out] smtp Pointer to a new SMTP context. When
- * finished, the caller must free this
- * context using @ref smtp_close.
- * @return See @ref smtp_status_code.
- */
-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);
-
-/**
- * Authenticate the user using one of the methods listed in
- * @ref smtp_authentication_method.
- *
- * @param[in] smtp SMTP client context.
- * @param[in] auth_method See @ref smtp_authentication_method.
- * @param[in] user SMTP user name.
- * @param[in] pass SMTP password.
- * @return See @ref smtp_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);
-
-/**
- * Sends an email using the addresses, attachments, and headers defined
- * in the current SMTP context.
- *
- * The caller must call the @ref smtp_open function prior to this.
- *
- * The 'Date' header will automatically get generated here if it hasn't
- * already been set using @ref smtp_header_add.
- *
- * If the application overrides the default 'Content-Type' header, then
- * this function will output the @p body as raw data just below the email
- * headers, and it will not output the attachments added using the
- * smtp_attachment_add_* functions. In other words, the application must
- * create its own MIME sections (if needed) when overriding the
- * 'Content-Type' header.
- *
- * @param[in] smtp SMTP client context.
- * @param[in] body Null-terminated string to send in the email body.
- * @return See @ref smtp_status_code.
- */
-enum smtp_status_code
-smtp_mail(struct smtp *const smtp,
- const char *const body);
-
-/**
- * Close the SMTP connection and frees all resources held by the
- * SMTP context.
- *
- * @param[in] smtp SMTP client context.
- * @return See @ref smtp_status_code.
- */
-enum smtp_status_code
-smtp_close(struct smtp *smtp);
-
-/**
- * Get the current status/error code.
- *
- * @param[in] smtp SMTP client context.
- * @return See @ref smtp_status_code.
- */
-enum smtp_status_code
-smtp_status_code_get(const struct smtp *const smtp);
-
-/**
- * Clear the current error code set in the SMTP client context.
- *
- * @param[in,out] smtp SMTP client context.
- * @return Previous error code before clearing.
- */
-enum smtp_status_code
-smtp_status_code_clear(struct smtp *const smtp);
-
-/**
- * Set the error status of the SMTP client context and return the same code.
- *
- * This allows the caller to clear an error status to SMTP_STATUS_OK
- * so that previous errors will stop propagating. However, this will only
- * work correctly for clearing the SMTP_STATUS_PARAM and SMTP_STATUS_FILE
- * errors. Do not use this to clear any other error codes.
- *
- * @deprecated Use @ref smtp_status_code_clear instead.
- *
- * @param[in] smtp SMTP client context.
- * @param[in] new_status_code See @ref smtp_status_code.
- * @return See @ref smtp_status_code.
- */
-enum smtp_status_code
-smtp_status_code_set(struct smtp *const smtp,
- enum smtp_status_code new_status_code);
-
-/**
- * Convert a standard SMTP client status code to a descriptive string.
- *
- * @param[in] status_code Status code returned from one of the other
- * library functions.
- * @return String containing a description of the @p status_code. The caller
- * must not free or modify this string.
- */
-const char *
-smtp_status_code_errstr(enum smtp_status_code status_code);
-
-/**
- * Add a key/value header to the header list in the SMTP context.
- *
- * If adding a header with an existing key, this will insert instead of
- * replacing the existing header. See @ref smtp_header_clear_all.
- *
- * See @ref smtp_mail when overriding the default 'Content-Type' header.
- *
- * @param[in] smtp SMTP client context.
- * @param[in] key Key name for new header. It must consist only of
- * printable US-ASCII characters except colon.
- * @param[in] value Value for new header. It must consist only of printable
- * US-ASCII, space, or horizontal tab. If set to NULL,
- * this will prevent the header from printing out.
- * @return See @ref smtp_status_code.
- */
-enum smtp_status_code
-smtp_header_add(struct smtp *const smtp,
- const char *const key,
- const char *const value);
-
-/**
- * Free all memory related to email headers.
- *
- * @param[in] smtp SMTP client context.
- */
-void
-smtp_header_clear_all(struct smtp *const smtp);
-
-/**
- * Add a FROM, TO, CC, or BCC address destination to this SMTP context.
- *
- * @note Some SMTP servers may reject over 100 recipients.
- *
- * @param[in] smtp SMTP client context.
- * @param[in] type See @ref smtp_address_type.
- * @param[in] email The email address of the party. Must consist only of
- * printable characters excluding the angle brackets
- * (<) and (>).
- * @param[in] name Name or description of the party. Must consist only of
- * printable characters, excluding the quote characters. If
- * set to NULL or empty string, no name will get associated
- * with this email.
- * @return See @ref smtp_status_code.
- */
-enum smtp_status_code
-smtp_address_add(struct smtp *const smtp,
- enum smtp_address_type type,
- const char *const email,
- const char *const name);
-
-/**
- * Free all memory related to the address list.
- *
- * @param[in] smtp SMTP client context.
- */
-void
-smtp_address_clear_all(struct smtp *const smtp);
-
-/**
- * Add a file attachment from a path.
- *
- * See @ref smtp_attachment_add_mem for more details.
- *
- * @param[in] smtp SMTP client context.
- * @param[in] name Filename of the attachment shown to recipients. Must
- * consist only of printable ASCII characters, excluding
- * the quote characters (') and (").
- * @param[in] path Path to file.
- * @return See @ref smtp_status_code.
- */
-enum smtp_status_code
-smtp_attachment_add_path(struct smtp *const smtp,
- const char *const name,
- const char *const path);
-
-/**
- * Add an attachment using a file pointer.
- *
- * See @ref smtp_attachment_add_mem for more details.
- *
- * @param[in] smtp SMTP client context.
- * @param[in] name Filename of the attachment shown to recipients. Must
- * consist only of printable ASCII characters, excluding
- * the quote characters (') and (").
- * @param[in] fp File pointer already opened by the caller.
- * @return See @ref smtp_status_code.
- */
-enum smtp_status_code
-smtp_attachment_add_fp(struct smtp *const smtp,
- const char *const name,
- FILE *fp);
-
-/**
- * Add a MIME attachment to this SMTP context with the data retrieved
- * from memory.
- *
- * The attachment data will get base64 encoded before sending to the server.
- *
- * @param[in] smtp SMTP client context.
- * @param[in] name Filename of the attachment shown to recipients. Must
- * consist only of printable ASCII characters, excluding
- * the quote characters (') and (").
- * @param[in] data Raw attachment data stored in memory.
- * @param[in] datasz Number of bytes in @p data, or -1 if data
- * null-terminated.
- * @return See @ref 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);
-
-/**
- * Remove all attachments from the SMTP client context.
- *
- * @param[in] smtp SMTP client context.
- */
-void
-smtp_attachment_clear_all(struct smtp *const smtp);
-
-
-/*
- * The SMTP_INTERNAL DEFINE section contains definitions that get used
- * internally by the SMTP client library.
- */
-#ifdef SMTP_INTERNAL_DEFINE
-/**
- * SMTP codes returned by the server and parsed by the client.
- */
-enum smtp_result_code{
- /**
- * Client error code which does not get set by the server.
- */
- SMTP_INTERNAL_ERROR = -1,
-
- /**
- * Returned when ready to begin processing next step.
- */
- SMTP_READY = 220,
-
- /**
- * Returned in response to QUIT.
- */
- SMTP_CLOSE = 221,
-
- /**
- * Returned if client successfully authenticates.
- */
- SMTP_AUTH_SUCCESS = 235,
-
- /**
- * Returned when some commands successfully complete.
- */
- SMTP_DONE = 250,
-
- /**
- * Returned for some multi-line authentication mechanisms where this code
- * indicates the next stage in the authentication step.
- */
- SMTP_AUTH_CONTINUE = 334,
-
- /**
- * Returned in response to DATA command.
- */
- SMTP_BEGIN_MAIL = 354
-};
-
-/**
- * Used for parsing out the responses from the SMTP server.
- *
- * For example, if the server sends back '250-STARTTLS', then code would
- * get set to 250, more would get set to 1, and text would get set to STARTTLS.
- */
-struct smtp_command{
- /**
- * Result code converted to an integer.
- */
- enum smtp_result_code code;
-
- /**
- * Indicates if more server commands follow.
- *
- * This will get set to 1 if the fourth character in the response line
- * contains a '-', otherwise this will get set to 0.
- */
- int more;
-
- /**
- * The text shown after the status code.
- */
- const char *text;
-};
-
-/**
- * Return codes for the getdelim interface which allows the caller to check
- * if more delimited lines can get processed.
- */
-enum str_getdelim_retcode{
- /**
- * An error occurred during the getdelim processing.
- */
- STRING_GETDELIMFD_ERROR = -1,
-
- /**
- * Found a new line and can process more lines in the next call.
- */
- STRING_GETDELIMFD_NEXT = 0,
-
- /**
- * Found a new line and unable to read any more lines at this time.
- */
- STRING_GETDELIMFD_DONE = 1
-};
-
-/**
- * Data structure for read buffer and line parsing.
- *
- * This assists with getting and parsing the server response lines.
- */
-struct str_getdelimfd{
- /**
- * Read buffer which may include bytes past the delimiter.
- */
- char *_buf;
-
- /**
- * Number of allocated bytes in the read buffer.
- */
- size_t _bufsz;
-
- /**
- * Number of actual stored bytes in the read buffer.
- */
- size_t _buf_len;
-
- /**
- * Current line containing the text up to the delimiter.
- */
- char *line;
-
- /**
- * Number of stored bytes in the line buffer.
- */
- size_t line_len;
-
- /**
- * Function pointer to a custom read function for the
- * @ref smtp_str_getdelimfd interface.
- *
- * This function prototype has similar semantics to the read function.
- * The @p gdfd parameter allows the custom function to pull the user_data
- * info from the @ref str_getdelimfd struct which can contain file pointer,
- * socket connection, etc.
- */
- long (*getdelimfd_read)(struct str_getdelimfd *const gdfd,
- void *buf,
- size_t count);
-
- /**
- * User data which gets sent to the read handler function.
- */
- void *user_data;
-
- /**
- * Character delimiter used for determining line separation.
- */
- int delim;
-
- /**
- * Padding structure to align.
- */
- char pad[4];
-};
-
-#endif /* SMTP_INTERNAL_DEFINE */
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* SMTP_H */
-